app.locations.new.tsx's "Location name"/"Address" fields and
app.blackouts._index.tsx's "Date"/"Reason" fields were missing
value/onChange — Polaris TextField is fully controlled, so without that
wiring every keystroke gets overwritten back to an empty string on
re-render, making the field appear frozen. Every other form in the app
already followed the value/onChange + useState pattern; these four fields
were the only ones missed, and went uncaught until live browser testing
was actually possible tonight (shopify.web.toml was missing until now).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add real Shopify Billing API integration: Free/Starter/Growth/Pro plans
(app/lib/billing-plans.ts, priced per PRODUCT_STRATEGY.md §6) wired into
shopify.server.ts's billing config, a merchant-facing plan page
(app/routes/app.billing.tsx) using billing.request/billing.cancel, and
webhooks.app_subscriptions.update.tsx as the durable sync path for
Shop.tier (fires even when a merchant cancels from Shopify's own billing
page, not just from this app).
Gate the features actually built so far in both loader and action (never
just hidden in the UI, so a direct POST can't bypass a tier limit):
delivery zones/rates require Growth+, the dispatch dashboard requires
Starter+, and location count is capped per tier (Free=1, Starter=3,
Growth/Pro=unlimited). Split pure tier logic (app/lib/billing-plans.ts)
from DB-backed reads/writes (app/services/billing.server.ts) so the
client-rendered UpsellState component can import the Tier type without
pulling server code into the client bundle — same split as currency.ts.
Covered by tests/unit/billing-plans.test.ts (pure tier ranking/mapping)
and tests/integration/billing.test.ts (tier persistence and location-limit
enforcement against live Postgres).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds the scheduling core to Prisma (Shop, Location, Method enum,
SlotTemplate, SlotOverride, BlackoutDate — IMPLEMENTATION_PLAN.md §4) and
Polaris admin screens to manage them:
- /app/locations: list/create/edit/delete locations, one-click vertical
template seeding (bakery/florist/grocer)
- /app/slots: weekly slot template editor, scoped per location
- /app/blackouts: blackout dates, scoped to one location or all of them
services/templates.server.ts keeps the vertical presets as a pure,
unit-tested function (getVerticalTemplate) separate from the thin I/O
wrapper (seedVerticalTemplate) that does the actual Prisma writes, per
CLAUDE.md's "pure functions, inject data, no I/O in the math" rule.
Switched dev DB from SQLite to Postgres (docker-compose.yml, ports 5433/6380
to avoid clashing with other local projects already on 5432/6379): SQLite
doesn't support Prisma enums at all, and IMPLEMENTATION_PLAN.md's schema
relies on them (Method) plus Postgres-only array fields in later phases
(Zone.postalCodes, ProductRule.allowedLocationIds). Only one throwaway
migration existed, so switching now avoids compounding the rework later.
Also fixed a Remix/Polaris integration issue hit while building these forms:
this Polaris version's TextField/Checkbox are fully controlled (no
defaultValue/defaultChecked), and `data()` imported from `@remix-run/react`
(rather than `@remix-run/node`) breaks useActionData's type inference —
both are now handled correctly across the new routes.
Verified: lint, typecheck, unit tests (incl. template-seeding fixtures),
build, and a live end-to-end run of seedVerticalTemplate against the
Postgres container all pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>