Review slice 8/8 — Onboarding epic + hardening - #9
Closed
Banyel3 wants to merge 16 commits into
Closed
Conversation
Addresses "cache isn't helping": React Query's cache was in-memory, so a full page reload (F5 / typing the URL) wiped it and every reload re-fetched. - staleTime + gcTime Infinity, refetchOnMount/onFocus off — a page fetches once, then serves from cache; the only refetch is a mutation's invalidateQueries (i.e. when the data actually changed). Dispatch board opts back into live polling (staleTime 0 + refetchInterval + refetchOnMount 'always'). - PersistQueryClientProvider + localStorage persister (SSR-guarded) so the cache survives reloads — first visit fetches, every visit after hydrates instantly. buster 'v1' to invalidate on a future shape change; 1-day maxAge. - Skeleton shimmer loading states (TableSkeleton / CardsSkeleton) replace the bare "Loading…" lines on every page. Type-check + next build clean; all pages still prerender. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Foundation for self-serve shop onboarding: a shop now has a verification status, and customers only ever see verified ones. - schema: ShopStatus enum (DRAFT → SUBMITTED → VERIFIED → REJECTED) + submittedAt, verifiedAt, rejectionReason, and R2 proof keys (permitKey, photoKeys). Migration backfills existing shops to VERIFIED, then flips the column default to DRAFT so future self-serve owners start unverified. - gate: customer discovery + auto shop-match require `active AND status=VERIFIED` (shops.service.listActive + orders.repository.findActiveShopServices). An unverified shop never matches an order. - admin-created shops are trusted → skip straight to VERIFIED (admin CRUD path). AdminShopView exposes status + verification timestamps (feeds the Phase C review queue). Seed shops default VERIFIED. - tests: admin create is VERIFIED, listActive where-clause carries the status filter. 252 api tests; curl-verified (customer sees verified only, admin create → VERIFIED). Rider self-serve (RiderProfile) lands with Phase D. Next: R2 uploads (B1) + portal self-signup/onboarding wizard (B2/B3). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Native-Word doc (same theme as the money/cost/feature DOCX) mapping the self-serve onboarding + verification flow for the team to finalize: three sign-up paths (customer/rider/laundry), the laundry-owner step-by-step (portal signup → proof + map location → admin review), the shop verification state table, admin review queue, rider flow (next phase), and an open-decisions checklist. Output docx is a generated artifact (git-ignored like the others); regenerate with `node e2e/capture/onboarding-docx.mjs`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Direct-to-R2 upload brokering — the API never touches the bytes. Ships INERT: no R2_* env → endpoints 503, same posture as Sentry. - UploadsService: lazy S3 client against the R2 endpoint (region "auto"). POST /uploads/presign → presigned PUT (5-min), key namespaced `uploads/<userId>/…` so a caller can only write under their own prefix; content-type allowlist (jpg/png/webp/pdf). GET /uploads/url → presigned GET, gated to the object owner or an ADMIN (proof review) — private bucket, no public links. - deps: @aws-sdk/client-s3 + s3-request-presigner. Env: R2_ENDPOINT/ACCESS_KEY_ID/ SECRET_ACCESS_KEY/BUCKET documented in both .env examples + DEPLOY table. - tests: content-type validation, key namespacing, owner/admin view gate, inert 503 when unconfigured; route-roles (uploads any-auth, ownership-scoped). 260 api tests; curl-verified (503 inert, 400 bad type). Next: B2/B3 wire these keys into the portal onboarding wizard (needs R2 creds + the TomTom map SDK). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ng B2/C) The owner-driven onboarding flow + the admin review queue — backend for the portal wizard and admin applications page. - ShopOnboarding (self-serve, /shop/onboarding, any-authenticated, ownership-scoped): POST /start (idempotent — become SHOP_OWNER + create a DRAFT shop in one tx), GET (the caller's shop + status, null if none), PATCH (edit draft: name, address, map coords, R2 proof keys — locked once SUBMITTED/VERIFIED), POST /submit (DRAFT/REJECTED → SUBMITTED; enforces the required set: name, address, pinned location, permit). Owner view hides margin fields. - Admin review (AdminShops): GET /admin/shops/applications (SUBMITTED, FIFO), POST :id/verify (→ VERIFIED + active + notify owner SHOP_VERIFIED), POST :id/reject (→ REJECTED + reason + notify owner SHOP_REJECTED). Both tx-wrapped, only act on SUBMITTED (else 409). Wired NotificationsModule into ShopsModule. - domain + api-client: OwnerShopView, onboarding + review + presign methods. - tests: onboarding (start idempotency + role grant, edit lock, submit required-set), verify/reject (state guard + owner notify), route-roles. 271 api tests; full flow curl-verified (start→submit→queue→verify→notify, gated from customers pre-verify). UIs next (need R2 creds + TomTom Web SDK): portal wizard (B3) + admin queue map (C). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rver) The localStorage-persistence change (27edb58) returned children WITHOUT any QueryClientProvider on the SSR/prerender branch (persister is client-only), so every page using useQuery threw 'No QueryClient set' and 500'd server-side. Wrap the SSR branch in a plain QueryClientProvider; persistence still layers on in the browser. All admin pages serve 200 again. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The admin side of self-serve onboarding — clear the queue of submitted shops.
- Applications page: lists SUBMITTED shops with submitted-time, an external map
link to eyeball the pinned location, a "view permit" link (presigned R2 GET,
degrades when R2 unset / no proof), and Approve / Reject-with-reason actions.
Nav entry + hover-prefetch; live-ish (staleTime 15s + refetchOnMount).
- AdminShopView now carries permitKey + photoKeys so the reviewer can open proof.
- seed: one SUBMITTED application ("Ayala Suds (pending)", owner dev-pending-owner)
so the queue has content in dev/e2e.
- browser smoke: renders the pending shop + approve/reject controls (non-mutating,
idempotent). admin-applications green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…y plan Postgres is temporarily on Neon's free tier (Singapore) while the Railway trial is spent; once the Railway Hobby plan is provisioned the DB moves onto Railway alongside the API (Docker deploy, colocated) — a DATABASE_URL change only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…g D backend) The second onboarding pipeline — mirror of shops. Riders sign up, submit documents, and are only dispatchable once an admin verifies them. - schema: RiderProfile (1:1 User) + RiderStatus DRAFT→SUBMITTED→VERIFIED→REJECTED + doc keys (licenseKey, idKey) + vehicle. Migration adds the table + FK. - dispatch GATE: manual assign (riders.service.listRiders) + auto-dispatch (orders.repository.pickAutoDispatchRider) now require riderProfile status VERIFIED — an unverified rider never gets a job. Seed gives dev riders VERIFIED profiles so dispatch keeps working. - self-serve /rider/onboarding (any-auth, ownership-scoped): start (become RIDER + DRAFT profile, idempotent), GET mine, PATCH (docs + vehicle), submit (required set → SUBMITTED). - admin /admin/riders: applications (SUBMITTED, FIFO), :id/verify (→ VERIFIED + notify RIDER_VERIFIED), :id/reject (→ REJECTED + reason + notify). NotificationsModule wired into RidersModule. Distinct paths from the rider-cash routes. - domain + api-client: RiderProfileView, RiderApplicationView + methods. route-roles. - tests: onboarding (start/idempotency/edit-lock/submit), admin verify/reject (state guard + notify), dispatch-filter assertion. 284 api tests; full flow curl-verified — picker 2→3 across verify, gate blocks SUBMITTED both ways. Rider APP UI (photo capture + upload) needs R2 + camera; the backend + admin review work today. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Applications page now reviews riders alongside shops: a "Riders" section
lists submitted rider profiles with vehicle + license/ID proof links and
Approve / Reject-with-reason. Verifying makes the rider dispatchable.
- reuses the ProofLink (presigned R2 GET, labelled license/ID; degrades when R2
unset). Sections split into "Laundries" + "Riders" with counts.
- seed: one pending rider ("Pending Rider", dev-pending-rider, SUBMITTED) so the
queue has content in dev/e2e.
- browser smoke: rider card renders with approve/reject (non-mutating). Both
admin-applications tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The onboarding-D dispatch gate (riderProfile status VERIFIED) excluded the integration test's rider — it had no profile — so auto-dispatch found no candidate and the order stayed BOOKED. Passed locally only because seeded verified riders masked it; CI runs against a fresh (unseeded) DB and failed. - create the test rider with a VERIFIED riderProfile so it's dispatchable. - teardown deletes riderProfile before the user (FK RESTRICT). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reviewers can now see the pinned storefront location without leaving the page — the whole point of verification. Key-free OpenStreetMap embed (no TomTom key, no dep); swap the iframe src for a TomTom tile URL once that SDK lands. External "open larger" link kept. Smoke asserts the map iframe renders; both admin-applications tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The long-standing "map on /shops" — all shops as color-coded pins (green=live, amber=awaiting review, grey=draft/rejected/inactive) over a real map, with a hover tooltip. react-leaflet + CircleMarkers (no icon assets) on free OpenStreetMap tiles — no TomTom key. Loaded client-only via next/dynamic (Leaflet needs window). To move to TomTom later, swap the TileLayer url; nothing else changes. Build clean; admin-shops smoke asserts the Leaflet map renders. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…or filter
Addresses the three findings the /review flagged on this branch.
- AUTH fail-safe hole (P2, security): NODE_ENV is now REQUIRED in the env schema
(was `.default('development')`). @nestjs/config wrote that default back into
process.env, so an unset NODE_ENV read as 'development' and let AUTH_DEV_BYPASS=1
slip past assertAuthConfigSafe → x-dev-uid could impersonate ADMIN on any
non-Docker deploy. Required = such a deploy refuses to boot outright. Dev (.env)
+ prod (Docker ENV) both set it; jest sets test.
- Remittance double-pay (P2, money): closeBatchTx now checks the count from
assignLinesToBatch (which only claims still-unbatched lines). On a concurrent
close the loser claims fewer than it created → count mismatch → throw to roll
back, so no phantom batch (totalPhp set, lines missing) can be marked paid twice.
- Exception filter (P2, coverage): new all-exceptions.filter.spec — Prisma-code→
HTTP mapping + the security guarantee that a 5xx never leaks the underlying
error/stack to the client.
- tests: remittance concurrency guard + the filter spec. 290 api tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…coverage) The remaining low-risk /review items — the branch now has zero open findings. - idempotency actor-scoping (privacy): POST /orders + rider-cash deposits only return a reused-key row when it belongs to the same actor (customerId / riderId match). A guessed/collided key can no longer disclose another user's order or deposit — it falls through to the unique-constraint 409 instead. - Sentry PII: the exception filter reports the request PATH only (strips the query string), so a home address in /geocode?q=… or ?lat&lng never reaches Sentry. - cross-package parity (new parity.spec): domain ORDER_STATUSES must equal the Prisma-generated enum (real schema-drift guard, not a hardcoded copy), and the API + domain load catalogs must agree on keys + estimate kg. - coverage: admin-shops list/get/update success paths now tested. - 298 api tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…PI build) The parity spec value-imported @wash-and-go/domain, which jest tolerated but `nest build` rejected (TS2307) — the API can only TYPE-import the workspace packages (they ship as unbuilt TS source). Read the domain files as text and regex-extract the values instead, so the schema-drift + load parity checks run without a runtime module import. API build green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Collaborator
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Review slice 8/8 — Onboarding epic + hardening — part of the PR #1 split. Review-only; stacked on
review/s7-observability-polish. Do not merge individually — PR #1 is the merge vehicle.Self-serve onboarding (shop + rider): verification lifecycle, R2 uploads, self-serve endpoints, admin review UI, maps. Plus the review P2/P3 fixes and CI/perf work.
Commits
Diffstat