24 KiB
24 KiB
Frontend status log (append-only)
One block per completed frontend phase. Newest at the top. Frontend lane writes here; backend reads for awareness.
frontend-phase-9-b10 — Checkout, card payment & invoice — 2026-07-10
- Shipped: the money moment — a new
services/paymentdomain (types/keys/constants/ apis[client+mock]/invalidations/5 hooks + barrel) and the customer checkout flow: C6 خلاصه و پرداخت/bookings/checkout?request_id=(acceptance badge, served reconciling service-cost/کارمزد/مالیات/مبلغ کل breakdown, verbatim escrow notice, payment-window countdown, «ادامه پرداخت ←» with an idempotency-key-per-attempt, disabled BNPL seam for f11), the card states (initiating → redirect → dev mock-gateway harness/bookings/checkout/gateway→ return/bookings/checkout/returnwith a backoff pending-callback poll → succeeded/failed/expired), the confirmation/bookings/checkout/confirmation(«مشاهده رزرو» + «دانلود فاکتور»), and the invoice/bookings/[id]/invoice(VAT-on-commission line, read-only مودیان state, pdfUrl download or print receipt). Three shared tested composites:PriceBreakdown,EscrowNotice,PaymentStatusBadge. Newpaymenti18n namespace (53 keys, both locales). - Load-bearing rules honored: money = IRR digit-strings, BigInt only (integer parts-per-10000 rate
math in the mock — zero floats); the breakdown must reconcile (
PriceBreakdowndev-guards it; rows are served, VAT never derived client-side); VAT on the commission only (invoice line labelled accordingly); escrow copy verbatim in fa (pinned by test against fa.json), info tone never error; 409 = benign convergence (re-reads the outcome, never an error toast); poll uses geometric backoff, stops on terminal + bounded attempts; success flips the booking by cache invalidation (exact keys, no refetch storm). - Consumes: dev/contracts/domains/payments.md (b10 — initiate route +
Idempotency-Keyheader +InitiatePaymentResult; status enumpending|succeeded|failed) and the invoice slice of refunds-invoices.md (b11 —GET invoices/{bookingId},InvoiceDto), casing verified against swagger.v1.json. Not served by any contract: a checkout summary, a client transaction read, and the converted request's booking id → REQ-016/017/018. - Mocked client-side:
services/paymentviapaymentMockApi(USE_PAYMENT_MOCK=true, primary) — it is the missing conversion trigger bridging the f7 ↔ f8 mock stores: capture converts the request (converted+ client-augmentedbookingId), inserts a confirmed booking into the f8 store, and auto-issues the b11-shaped invoice, so C5 → C6 → gateway → confirmation → booking detail → invoice runs end-to-end in one session. The dev mock-gateway page is a test harness, not a product feature. RealpaymentClientApi: initiate/invoice = published contract; summary targets the REQ-016 proposed slug; outcome mapsbooking_requests/get(REQ-017). - Gate: npm run check green · npm run test:ci green (204 tests, +9) · production build green.
- Requests filed: frontend/requests/for-backend.md — yes (REQ-016 checkout summary, REQ-017 payment outcome + bookingId, REQ-018 customer invoice availability post-capture).
frontend-phase-8-b9 — Booking detail, sessions & nurse EVV — 2026-07-10
- Shipped: the post-payment engagement — a new
services/bookingsdomain (the sibling ofbookingRequests, NOT a rename): types/keys/constants/apis[client(1:1 b9)+mock+serverApi]/8 hooks + barrel, plus theevv/locationProvider.tsILocationProvider GPS seam. Screens: customer رزروها list/bookings+ booking detail/bookings/[id](BookingDetailView, customer view), nurse ویزیت امروز/nurse/visits(today-sessions EVV feed) + nurse booking detail/nurse/visits/[id](EVV controls + gated care). Seven shared tested composites undersrc/components/booking/:BookingDetailView,BookingStatusTimeline(server-truth 7-status),SessionList→SessionCard(per-session schedule/status/EVV CTA),EvvStatusBanner(advisory in/out-of-range/no-gps),CareInstructionsCard,BookingMoneySummary, +useEvvController. i18nbookingextended (bstatus_*/sstatus_*/evv_*/care_*/money_*/list_*) both locales; new icons (check_in/out, gps, clinical, medication, emergency, lock) +--bal-secondary-softtoken. - Load-bearing rules honored: two-stage disclosure is a UI gate —
useCareInstructionsisenabledonly for the assigned-nurse view on aconfirmed+ booking; the customer NEVER fires it (proven by test). EVV mismatch/GPS-denial is advisory, never a block — out-of-range check-in still succeeds (warning-tokened banner, not error); denial still submits. Timeline = server truth (never advanced client-side); money display-only (gross/commission/payout rendered as sent, never summed/re-split;payoutEligibleAtnever recomputed); single-visit renders one session row through the same card; EVV mutations invalidate detail+sessionEvv+today+list. - Consumes: dev/contracts/domains/bookings-evv.md (b9) + swagger
BookingDetailDto/BookingSessionSummaryDto/VisitVerificationDto/CareInstructionsDto—services/bookings/types.tsderives from these 1:1. - Mocked client-side:
services/bookingsviabookingsMockApi(USE_BOOKINGS_MOCK=true, primary) — seeds 2 confirmed bookings (one 3-session, one single-visit) + care + a check-in/out EVV state machine, because a real booking only exists afterbookings/convertruns on a paid request and both upstreams (bookingRequests mock, card capture b10) aren't real client-side yet. RealbookingsClientApimaps the routes 1:1; swap is one flag. Also the ILocationProvider GPS seam (NEXT_PUBLIC_EVV_MOCK_GPSin_range|out_of_range|denied|off) — the first frontend seam recorded in mocks-registry. - Gate: npm run check green · npm run test:ci green (195 tests, +22). Added a committed
NEXT_PUBLIC_API_URLdefault injest.setup.ts(first test to render a service-hook component pulled@/configat import). - Requests filed: frontend/requests/for-backend.md — yes (REQ-015: confirm the booking/session/EVV enum
string codes + the
checkInAddressMatchtri-state semantics the client unions assume).
frontend-phase-7-b8 — Booking request flow (customer request + nurse inbox) — 2026-07-09
- Shipped: the money-free request phase —
services/bookingRequests(types/keys/constants/apis[client+ mock]/hooks + barrel) and screens C4/bookings/request(patient/variant/address/date+time + a first-class 3-way caregiver-gender toggle + stage-1 notes; client-side validation gates the CTA; domain 400s surface as field/form errors; reuses f2 patients, f3 addresses + map preview, f4/search variants), C5/bookings/request/[id](summary card + 3-step tracker + polled status; response countdown → accept flips to a 30-min payment countdown + checkout CTA / reject/expire/cancel/converted terminal cards; cancel-with-confirm), the nurse inbox/nurse/requests(+ nurse nav item) and detail/nurse/requests/[id](onlycustomerNotes+ masked city/district; accept / reject-with-reason invalidate inbox+detail), and a/bookings/checkoutf9 stub. Two shared tested composites:CountdownTimer(owns its 1s tick — only it re-renders; stops at zero),BookingRequestSummaryCard. i18nbooking(fully fleshed) +nav.requestsin both locales. - Load-bearing rules honored: deadlines are server-frozen UTC (client renders, never recomputes);
required_caregiver_genderis explicit + client-blocks a same-gender mismatch; two-stage disclosure (the nurse UI never renders address/clinical fields —get(id,'nurse')masks); polling stops on a terminal/convertedstatus. - Consumes: dev/contracts/domains/booking-requests.md (b8) — routes
booking_requests/{create,accept/{id}, reject/{id},cancel/{id},list,get/{id}}, wire camelCase, action-style.services/bookingRequests/types.tsderives from it. - Mocked client-side:
services/bookingRequestsviabookingRequestsMockApi(USE_BOOKING_REQUESTS_MOCK =true, primary) — a shared in-memory state machine so create → nurse inbox → accept/reject → C5-poll → lazy expiry all demo end-to-end (b8 is live, but its inputs — search/patients/addresses — are themselves mock-primary; and the DTO omitsvariantPrice, REQ-013). RealbookingRequestsClientApimaps the b8 contract 1:1; swap is one flag. Recorded in the phase report (not mocks-registry — backend DI seams only). - Gate: npm run check green · npm run test:ci green (173 tests, +8) · npm run build green with
NEXT_PUBLIC_API_URL set (the only prerender failure without env is the pre-existing "Missing .env
variable!" — hits the existing
/fa/searchtoo, unrelated to f7). - Requests filed: frontend/requests/for-backend.md — yes (REQ-013 variantPrice on the DTO + nurse avatar; REQ-014 inbox list-item variantLabel + patient age).
frontend-phase-6-b7 — Search & discovery (find a verified, same-gender nurse) — 2026-07-09
- Shipped: the family discovery slice —
services/search(types/keys/constants/apis/hooks + a sharedfilterParams.tsC1↔C2 URL serializer) and screens C1/search(reused category grid + f3 region picker + prominent same-gender facet + Toman price + live-count CTA;useSearchFilterscolocated controller with debounced price), C2/search/results(rating-sortedNurseResultCardlist, all four states incl. "relax filters" empty, load-more), C3/search/nurse/[nurseId](TrustBadge + نظام پرستاری badges, attribute chips,ServicePriceRowservices, latest review, "درخواست رزرو" handoff). Two shared tested components:NurseResultCard,ServicePriceRow./bookings/request= f7 handoff stub. i18nsearch(filled) +booking(seeded) in both locales. Reused f4 category grid, f5 TrustBadge, f3 geo picker, f0 money util — none rebuilt. - Headline caching: the filter object IS the query key — reverting to a prior filter set is a cache hit with zero network (keepPreviousData avoids flashing); price input debounced.
- Consumes: dev/contracts/domains/search.md (b7) + b6 trust badge / b5 variant reads.
- Mocked client-side:
services/searchviasearchMockApi(USE_SEARCH_MOCK=true, primary) — b7's index row + b5/b6 reads don't yet expose nurse name/avatar/distance or an aggregated profile (name/bio/specialties/services list/latest review). RealsearchClientApimaps what exists; swap is one line once REQ-012 lands. Recorded in the phase report (not mocks-registry — that's for backend DI seams). - Gate: npm run check green · npm run test:ci green (165 tests, +8).
npm run buildcompiles + types clean; prerender fails only on the pre-existing f5/nurse/verification"Missing .env variable!" (needs env set — unrelated to f6). - Requests filed: frontend/requests/for-backend.md — yes (REQ-012: search row name/avatar/distance +
GET nurses/{id}/profileaggregation).
frontend-phase-5-b6 — Nurse verification flow (trust engine) — 2026-07-09
- Shipped:
services/verificationdomain (types/keys/constants/validation/apis[client+mock(primary)+seam]/ hooks/index) — ONE cachedstatus()query drives B3+B6, every mutation invalidates it. The nurse verification route subtreenurse/verification/{page(B3),identity(B4),credentials(B5),review(B6)}+ co-locatedVerificationChecklist/verificationSteps(data-driven step rendering, synthetic mobile step). Two shared components with tests:<DocumentUpload>(client type/size validation, progress %, success/ retry, re-upload-on-reject, server-metadata truth + local-capture mode) and<TrustBadge>(verified/ unverified/expired off--bal-*, reused by f6). Publish gate wired onnurse/services(PublishGate— disabled untilapproved); trust badge on the nurse profile (own state fromownBadgeState). 6 new AppIcons, 4 route constants,verificationi18n namespace (123 keys) in both locales. Honest copy: manual steps never claim an automated authority check; a step reads "تاییدشده" only whenpassed. - Consumes: dev/contracts/domains/verification.md + openapi/swagger.v1.json (backend-phase-6). Wire is
camelCase; action-style routes under
api/v1/nurse_verification/*+api/v1/nurses/{id}/trust_badge. - Mocked client-side:
services/verificationviaverificationMockApibehindUSE_VERIFICATION_MOCK(default true — runs the whole journey standalone incl. a dev-only admin-decision sim, since b6 isn't reachable here). One-line swap toverificationClientApi. See mocks-registry. - Gate: npm run check green · npm run test:ci green (157 tests, +9) · en/fa in sync (123 verification keys).
- Requests filed: frontend/requests/for-backend.md — yes (REQ-011 nurse credential-details endpoint).
frontend-phase-4-b5 — Catalog browse (Home A5) & nurse service builder (B7) — 2026-07-05
- Shipped:
services/catalogdomain (types/keys/constants/apis[client+mock+seam]/hooks/index) — the b5 catalog skeleton + nurse pricing layer. Hooks:useServiceCategories,useCategoryOptionGroups(both session-cached reference data, InfinitestaleTime),useMyVariants(paginated, self-scoped),useCreateVariant/useUpdateVariant/useSetVariantActive(mutations invalidatecatalogKeys.myVariantsLists(); updatesetQueryDatas the row). Shared composites (each tested):CategoryTile(data-driven Home tile + builderselectedstate),PriceDisplay(money-util Toman + i18n unit label + unit-aware estimated total),VariantCard(offering card, active/deactivated distinction, no delete). Money util gainedtomanToRial(field-boundary Toman→IRR) +multiplyIrr(integer-safe estimate). Screens: customer Home (A5) — greeting + avatar, search bar (navigates toward f6/search, results deferred), data-driven category grid (loading/empty/error), patient nudge (reuses cached f2usePatients, no new fetch); nurse Services & prices (B7) at/nurse/services(new sidebar tab) — offerings list (active/inactive, edit, soft deactivate w/ confirm, reactivate, empty/skeleton) + 3-step variant builder (category → required/optional options → price+unit+duration; required-group gate; Toman→IRR digit-string submit; live unit-aware total; editable autodisplayName; inline409duplicate warning; locked-category edit form). Addedcatalog/services/searchi18n namespaces +homeadditions +nav.services(both locales); 7 icons (services,category,elderly,post_surgery,infant,chronic,companionship); routesSEARCH,NURSE_SERVICES; deferred/searchplaceholder stub (→ f6). - Consumes: dev/contracts/domains/catalog.md (backend-phase-5). Routes
api/v1/catalog/{categories,option_groups},api/v1/nurse_variants/{create,update/{id},set_active/{id},list,get/{id}}. Wire camelCase;price_unitenum; IRR-string money;409duplicate listing /400missing required dimension. - Mocked client-side:
services/catalogviacatalogMockApibehindUSE_CATALOG_MOCK(defaulttrue) — seeds the 5 real b5 categories + representative option groups (incl. a cross-category one) + the409/400rules; variant store seeded empty so the offerings empty-state demos. RealcatalogClientApiwired for a one-line flip. See mocks-registry + the report (note: the mock seeds option groups the fresh backend does not — an admin authors them). - Gate: npm run check green · npm run test:ci green (147 tests, +18 across 4 suites: catalog components + money) · npm run build green with NEXT_PUBLIC_API_URL set (routes /nurse/services, /search generated; home prerenders).
- Requests filed: frontend/requests/for-backend.md — yes (REQ-010 confirm the list pagination query-param name
pageSizevs the doc'spage_size).
frontend-phase-3-b4 — Addresses, map picker & nurse coverage areas — 2026-07-03
- Shipped: three domain services —
services/geography(cached province→city→district reference lookups; InfinitystaleTime+ sharedgeographyKeys;useProvinces/useCities/useDistricts; seam+mock+client),services/addresses(address book CRUD + set-primary; single-primary invariant; every mutation invalidates the list;useAddresses/useCreateAddress/useUpdateAddress/useDeleteAddress/useSetPrimaryAddress),services/serviceAreas(coverage add/remove;areaExistsdup-guard;useServiceAreas/useAddServiceArea/useRemoveServiceArea). Shared composites (src/components/geography/, each tested):CascadingRegionSelect(drives the cached cascade),AddressMapPicker(map-pin stand-in emitting real lat/lng),AddressForm,AddressCard. Screens: customer address book (/addresses, reached from a profile-hub link — cascade + map pin dialog, set-primary, delete, empty/skeleton), nurse coverage editor (/nurse/coverage, new sidebar tab — chips, whole-city/specific-district scope toggle, inline duplicate block + 409, "won't appear in search" empty warning). Addedgeo/address/coveragei18n namespaces +nav.coverage(both locales);location/delete/coverageicons; routesADDRESSES/NURSE_COVERAGE. - Consumes: dev/contracts/domains/geography-addresses.md (backend-phase-4). Routes
api/v1/geo/{provinces, cities,districts},api/v1/customer_addresses/{list,create,update,set_primary,delete},api/v1/ nurse_service_areas/{list,add,remove}. Wire camelCase; geo query params snake_case; 409 on duplicate coverage. - Mocked client-side:
services/geography(USE_GEOGRAPHY_MOCK),services/addresses(USE_ADDRESSES_MOCK),services/serviceAreas(USE_SERVICE_AREAS_MOCK) — all defaulttrue; real clients wired for a one-line flip. TheAddressMapPickeris a stand-in (no real map tiles). See mocks-registry + the report. - Reviewed: 5-dimension adversarial review → 3 findings fixed (map marker RTL transform;
page_size→pageSizepagination casing on the real list calls; coverage "districts" dead-end on a district-less city). - Gate: npm run check green · npm run test:ci green (129 tests, +17 across 5 suites) · npm run build green with NEXT_PUBLIC_API_URL set (routes /addresses, /nurse/coverage generated).
- Requests filed: frontend/requests/for-backend.md — yes (REQ-008 accept the map pin on address create/update,
REQ-009
provinceIdonCustomerAddressDtofor edit prefill).
frontend-phase-2-b3 — Onboarding & profiles (customer, patient, nurse, bank) — 2026-07-02
- Shipped: three domain services —
services/patients(rewritten to the b3PatientDto+ client-augmentedrelation/conditions; full CRUD seam + mock + real client;usePatients/useCreatePatient/useUpdatePatient/useArchivePatientwith optimistic soft-archive),services/profiles(customer + nurse profile get/upsert + avatar;useCustomerProfile/useUpsertCustomerProfile/useNurseProfile/useUpsertNurseProfile/useUploadAvatar),services/nurse(bank accounts;useNurseBankAccountswith pending-onlyrefetchInterval,useAddNurseBankAccount,useSetPrimaryBankAccount;iban.tsSheba validate/normalize/bank-name). Screens: A3→A4 onboarding wizard ((customer)/onboarding), E1 patients list/CRUD (add/edit dialog reusing the A4 form, soft-archive confirm, empty + skeleton states), A5 Home (first-login redirect into onboarding when 0 patients + "complete patient record" nudge), customer profile (name + preferred language + emergency contact, no national-ID), nurse profile bootstrap (avatar + bio + years, unverified "not bookable" placeholder → verification), nurse bank settings (IBAN form + the three ownership states pending/verified/mismatch). Shared compositesGenderToggle/ConditionChips/RelationSelect/PatientForm/PatientCard/BankStatusPanel(each tested); reused the f0StepperHeader/StatusChip/PhoneNumberField. Addedonboarding/home/profile/nurseProfile/banki18n namespaces +patientsextensions (both locales);--bal-primary-softtoken (both schemes); nurse sidebar gains Profile + Bank. - Consumes: dev/contracts/domains/identity-profiles.md (backend-phase-3). Routes
api/v1/{customer_profiles, nurse_profiles}/{me,upsert},api/v1/patients/{list,get,create,update,archive},api/v1/nurse_bank_accounts/ {list,add,set_primary,verify_ownership}. - Mocked client-side:
services/patients(USE_PATIENTS_MOCK),services/profiles(USE_PROFILES_MOCK),services/nurse(USE_NURSE_BANK_MOCK) — all defaulttrue; real clients wired for a one-line flip. See mocks-registry + the report for exactly what/why (relation/conditions, avatar, customer name/language gaps). - Gate: npm run check green · npm run test:ci green (112 tests, +17) · npm run build green with NEXT_PUBLIC_API_URL set (routes /onboarding, /nurse/profile, /nurse/bank generated).
- Requests filed: frontend/requests/for-backend.md — yes (REQ-005 patient relation/conditions, REQ-006 avatar upload route, REQ-007 customer name/preferred-language).
frontend-phase-1-b2 — Auth: phone-OTP login & role routing — 2026-07-02
- Shipped:
services/authrewritten for phone-OTP (types/keys/apis[client+mock+seam]/hooks:useRequestOtp/useVerifyOtp/useMe/useRefresh/useLogout/useSelectRole/useSessionRoleSync) — the username/password stub is gone; A1/A2 customer login + B1/B2 nurse switch (one OTP flow parameterised by intended role) at/login; the role router (RoleRouter+ pureresolveRoleDestination) → customer→family, nurse→nurse app, no-role→/select-role, admin→admin console (splash while/meloads, no wrong-shell flash);SelectRolescreen at/select-role; silent token refresh in the fetch layer (single-flightattemptTokenRefresh, one retry on 401); widenedAuthState(roles viaSessionUser, hydrated from/mebyuseSessionRoleSyncin the private layout);authi18n namespace +common.brand*in both locales. - Consumes: dev/contracts/domains/identity-auth.md + openapi/swagger.v1.json (backend-phase-2). Wire is
camelCase; routes
api/v1/auth/{request_otp,verify_otp,refresh,logout},api/v1/me,api/v1/me/select_role. - Mocked client-side:
services/authviaauthMockApibehindUSE_AUTH_MOCK(default false — b2 is live; flip true for offline dev, dev code123456). See mocks-registry. - Gate: npm run check green · npm run test:ci green (95 tests, +23) · npm run build green with
NEXT_PUBLIC_API_URL set. Fixed the jest
@/→srcalias (was<rootDir>/$1). - Requests filed: frontend/requests/for-backend.md — yes (REQ-002 OTP length/expiry, REQ-003 verify
error codes + lockout retry-after, REQ-004 multi-role
activeRole?).
frontend-phase-0 — Foundations: app shells, design system & data/contract patterns — 2026-07-02
- Shipped: 3 actor shells (customer bottom-nav / nurse / admin sidebar) + role-aware routing under
(private-routes);useActorRole; theservices/{domain}reference (patients, mocked behind a seam) with deliberate Query caching + invalidation;lib/api/types.ts(envelope/pagination); money + Shamsi-date utils; shared compositesOtpInput/PhoneNumberField/StepperHeader/StatusChip/PlaceholderScreen(each tested); i18nnav/common/shell/patientsin both locales. Removed the demo scaffolding; fixed theBottomBarpathname bug. - Consumes: dev/contracts/conventions/* + openapi/swagger.v1.json (b0 = ping only). No feature contract consumed yet.
- Mocked client-side:
services/patientsviapatientsMockApi(USE_PATIENTS_MOCK=true) — template for f1+. Swap is one line once real endpoints land. - Gate: npm run check green · npm run test:ci green (72 tests) · npm run build green with NEXT_PUBLIC_API_URL set.
- Requests filed: frontend/requests/for-backend.md — yes (REQ-001).