Files
2026-06-28 21:59:59 +03:30

28 KiB
Raw Permalink Blame History

Frontend Phase 5 — Nurse verification flow (mocked vendors)

Mission: build the trust engine's front end — the staged, platform-owned verification flow a nurse walks through before any of their services can go live. A nurse lands on a status checklist (B3), submits identity (B4: national-ID + card image + liveness selfie), submits professional credentials (B5: نظام پرستاری number + license + education + specialties), and waits on the under-review screen (B6) until an admin decides. Documents upload to the object-storage-backed endpoint with type/size validation and progress; each step shows its own status (and its rejection reason); and a trust badge (verified / unverified / expired) renders on the nurse profile. Verified trust is the entire brand — a nurse is not bookable and cannot publish until verified, and the UI must say so honestly and never advertise a check the platform doesn't actually perform.

Track: frontend · Depends on: frontend-phase-4-b5.md (catalog browse + nurse service builder) and the b6 verification contract · Unlocks: a bookable verified nurse + the search trust badge consumed in frontend-phase-6-b7.md Before you start, read ../_shared/agent-operating-rules.md. It is not optional.


1. Context — where this sits

This is the fifth feature slice of the customer/nurse front end and the nurse side's gating step. By f4 a nurse can build service variants, but those variants are inert — they cannot surface in search or be booked until nurse_profiles.is_verified is true. This phase builds the screens that flip that switch: the data-driven verification pipeline (six step types, all vendor calls mocked server-side behind DI seams in b6) rendered as a nurse-facing checklist, the two submission forms, the waiting state, the document uploader, and the public trust badge. When this lands, a nurse can go from "registered" to "verified and publishable", which is the prerequisite for everything downstream — search (f6), booking (f7), and ultimately payout (f12, gated on the bank-account verification step).

What already exists (do not rebuild) — built by prior phases:

  • f0 foundations (frontend-phase-0.md): the three actor app shells + route groups, the nurse shell ("نمای پرستار"), the services/{domain} + TanStack Query caching pattern (keys.ts factory, apis/clientApi.ts, one-hook-per-file), the contracts→types.ts pattern, the money/format utils, and the shared composite components — most importantly the stepper/ progress header and the status chip (verified/pending/…). Reuse both here; do not re-implement a stepper or a status chip. The verification i18n namespace was reserved in f0 — fill it.
  • f1-b2 auth (frontend-phase-1-b2.md): phone-OTP login, the role router, roles in AuthContext. The nurse arrives here already authenticated with the nurse role. Step 1 of the checklist (شماره موبایل — mobile verified) is already satisfied at login — render it as passed, don't ask for it again.
  • f2-b3 onboarding (frontend-phase-2-b3.md): the nurse profile bootstrap (CreateNurseProfileCommand → an unverified nurse_profiles row, is_verified=false) and the nurse bank-account settings screen (IBAN entry → ownership inquiry). The verification pipeline's bank_account_verification step couples to that bank account — link to the existing bank-account screen for that step; do not rebuild the IBAN form here.
  • f3-b4 addresses/geo (frontend-phase-3-b4.md): the nurse coverage-area editor and the cascading geo dropdowns (not used here, but the nurse shell nav points to them).
  • f4-b5 catalog (frontend-phase-4-b5.md): the nurse "add a service" builder (B7) and services/catalog. The "انتشار پروفایل / go live" action that f4 stubs must be gated on verification by this phase — wire the blocked-until-verified state into the publish CTA.

Honesty constraint (load-bearing, from the product doc and GTM notes): vetting is platform-owned and performed at the authoritative source. Never word the UI to advertise a check that isn't performed. The MoH/INO license and criminal-record steps are manual admin review of an uploaded document at launch — copy must say "در حال بررسی" (under review), not "تاییدشده توسط نظام پرستاری" until an admin actually passes it. The civil-registry/Shahkar/liveness checks are real (vendor-mocked) — those may say "استعلام خودکار".

2. Required reading (do this first)

  • ../_shared/agent-operating-rules.md and ../_shared/frontend-conventions-checklist.md — how you work, the gate, the contract/handoff lanes, the mock-then-swap rule (§6).
  • ../../../client/CLAUDE.md — the engineering contract (RSC/client boundary, layouts, the services/{domain} shape, i18n, theme, cookies, the fetch services). Non-negotiable.
  • Invoke the frontend-designer skill before any visual work — it is the design/brand contract (palette, tokens, typography, the App* library, layout shells, the hard UI rules). Every screen, chip, uploader, and badge in this phase goes through it. The wireframe's status legend is green = automatic/verified, amber = pending, grey = manual/next, terracotta = financial — encode those as token-driven chip variants.
  • Product — the source of truth for the rules:
    • ../../../product/business/02-nurse-verification.md — the six steps, what each verifies, why it's manual vs automated, the structured-credential-registry rationale, continuous re-verification, and the "never advertise a check you don't perform" rule.
    • ../../../product/wireframes/index.htmlSection B, screens B3B6 are the visual baseline you implement: B3 status meter "۲ از ۵" + stepped checklist with status badges; B4 identity (کد ملی field, upload national-ID card, liveness selfie, "استعلام خودکار از ثبت احوال" note); B5 professional credentials (شماره نظام پرستاری, license upload, education cert shown uploaded ✓, specialty chips سالمندان/ICU/+افزودن); B6 "در حال بررسی" (2448h, mini-checklist). Also note B7's "انتشار پروفایل" — the publish gate this phase enforces, and C2/C3's "✓ تاییدشده" badge this phase's trust-badge component feeds.
  • The contract you consume (the authoritative server shapes):
    • ../../contracts/domains/verification.md — written by backend-phase-6. The exact request/response shapes, routes, status enums, and the per-step status codes. Do not guess shapes — derive types.ts from this doc + the published swagger.json. If a shape you need is missing, append a request to ../../shared-working-context/frontend/requests/for-backend.md and mock behind the services/verification seam meanwhile (operating-rules §6).
    • ../../contracts/conventions/api-conventions.md and money-and-types.md — the envelope (OperationResultApiResult, already unwrapped by clientFetch), snake_case routes/properties, pagination, enums as stable string codes (mirror them as string-literal unions; labels are i18n keys, never derived from the code), UTC timestamps with Shamsi display client-side (credential issue/expiry dates).
  • Code to mirror:
    • The services/catalog and services/onboarding domains from f4/f2 — the exact types.ts/keys.ts/ apis/clientApi.ts/hooks/use*.ts/index.ts layout your services/verification copies.
    • The shared stepper/progress header and status chip from f0 (src/components/…) — extend, don't fork. The nurse-profile bank-account screen from f2 — you deep-link into it for the bank step.
    • The existing document/image handling, if any, in App* (AppImage); the clientFetch multipart path.

3. Scope — build this

A new services/verification domain, the nurse verification route subtree under the nurse shell, a reusable document-upload component, a trust-badge component, and the publish-gate wiring. Build RTL-first, both locales, query-cached, minimal re-renders.

3.1 services/verification domain (the data layer)

Copy the f0/f4 service shape exactly. Types come from ../../contracts/domains/verification.md — do not invent.

  • types.ts — string-literal unions mirroring the contract enums: the aggregate status (not_started | pending | in_review | approved | rejected | suspended), the per-step status (not_started | pending | in_review | passed | rejected), the step codes (identity_kyc · shahkar_match · moh_competency_license · ino_membership · criminal_record · bank_account_verification), verification_method (manual | portal | api), and the badge state (verified | unverified | expired). Plus the DTOs the contract returns: VerificationStatus (aggregate + ordered steps[] each with code, status, is_automated, rejection_reason?, expires_at?), VerificationStep, VerificationDocument (storage key, file name, uploaded-at — metadata only, never bytes), NurseCredential (type, masked number, holder name, issuing authority, issued_at/expires_at).
  • keys.ts — a query-key factory: verificationKeys.status(), .documents(stepCode), .badge(nurseId). Deliberate staleTime (status is moderately fresh — e.g. 30s — because submitting a step changes it; the badge is longer-lived).
  • apis/clientApi.ts wrapping clientFetch — one function per contract endpoint (see §3.2). The document upload uses the multipart path against the IObjectStorage-backed endpoint.
  • hooks/ — one hook per file: useVerificationStatus (query), useStartVerification, useSubmitIdentity, useSubmitCredentials (or per-credential submit if the contract splits MoH/INO/criminal), useUploadVerificationDocument (mutation with progress), useNurseTrustBadge (query). Every mutation invalidates verificationKeys.status() (and the badge where relevant) so the checklist re-renders from cache without a manual refetch. Don't toast 401/403/5xx (the fetch layer does) — only domain 4xx (e.g. "ownership mismatch", "shared-SIM", "national-ID format").

3.2 Endpoints consumed (per the b6 contract — confirm exact routes/shapes there)

Wire each via clientApi; the names below are the expected commands/queries from the digest — bind to whatever the contract publishes:

  • GET .../nurse/verificationGetVerificationStatusQuery — the aggregate + per-step list driving B3/B6.
  • POST .../nurse/verification/startStartNurseVerificationCommand — seeds the steps (call from the B3 "start / continue" CTA when status is not_started).
  • POST .../nurse/verification/identityRunIdentityKycCommand — national-ID + card image + liveness selfie; drives B4. (Server also runs shahkar_match off the bound national-ID — surface both steps.)
  • POST .../nurse/verification/moh-license (+ .../ino, .../criminal-record if split) → the credential-submit commands behind B5.
  • POST .../nurse/verification/document (or a presigned-URL flow) → the IObjectStorage-backed upload used by every document step.
  • The bank step (bank_account_verification) is submitted on the existing f2 bank-account screen — this phase only renders its status in the checklist and deep-links to that screen.
  • GET .../nurse/{id}/badge (public) → GetVerifiedBadgeQuery — feeds the trust badge.

3.3 Screens (under the nurse shell (private-routes) nurse subtree)

Invoke frontend-designer for each. RTL-first; the screens live under the nurse route group from f0.

  • B3 · وضعیت احراز هویت — status checklist (the hub).

    • Overall progress meter "X از Y" (X = passed required steps, Y = total required) — reuse the f0 stepper/progress header.
    • An ordered, stepped checklist, one row per step from steps[], each with a status chip (reuse the f0 status chip) in the five per-step states: not_started/locked-next (grey "بعدی"), pending (amber "در انتظار"), in_review (amber "در حال بررسی"), passed (green "تاییدشده"), rejected (red "رد شد" + the reason). Render step 1 (mobile) as passed from auth state.
    • A "what's blocking go-live" summary + a single continue CTA ("ادامه مرحله N") that routes to the next actionable step. When not_started, the CTA calls useStartVerification first.
    • States: loading skeleton, error, and the terminal approved state (all passed → "احراز هویت تکمیل شد، می‌توانید پروفایل را منتشر کنید" with a link to publish).
  • B4 · تایید هویت — identity submit.

    • کد ملی (national-ID) field with format validation (10-digit, checksum); upload national-ID card image (camera/gallery via the document uploader §3.4); liveness selfie capture (camera; handle permission-denied / retry / vendor-timeout states honestly).
    • The honest auto note "استعلام خودکار از ثبت احوال" (auto civil-registry query) — this check is performed (vendor-mocked), so the copy is allowed.
    • Submit → useSubmitIdentity; on success the identity_kyc (and server-run shahkar_match) steps move to pending/in_review, the cache invalidates, and the user returns to B3. Surface the shared-SIM Shahkar failure as a clear, non-accusatory message, and the national-ID-mismatch failure on its step.
  • B5 · مدارک حرفه‌ای — professional credentials.

    • شماره نظام پرستاری (INO number) field; upload پروانه/کارت نظام پرستاری (the MoH competency license — the single most important credential) via the uploader; education certificate upload (shown with the uploaded-✓ state when present); specialty chips (multi-select: سالمندان, ICU, + افزودن — an add-your-own chip input).
    • Structured fields the registry needs where the contract asks for them (license number, issuing authority, holder name as printed, issue/expiry date — Shamsi date picker, stored UTC).
    • Submit → useSubmitCredentials; the credential steps move to in_review (manual admin review). Copy must reflect manual review, not an automated authority confirmation.
  • B6 · در حال بررسی — under review.

    • The waiting state: "مدارک شما در حال بررسی است" + the 2448h expectation + a mini-checklist summarizing which steps are passed vs in-review (identity verified / professional docs in review / bank account pending). Reuses the same per-step chips as B3, condensed. CTA "مشاهده وضعیت" → B3.
    • This is the post-submission resting screen; B3 is the canonical source — B6 is a focused view of the same VerificationStatus, not a second fetch with different state.
  • Trust badge on the nurse profile. Render the verified / unverified / expired badge (the "✓ تاییدشده" mark) on the nurse's own profile (f2) and export a small <TrustBadge status=…/> shared component so search results (C2) and the public nurse profile (C3) in f6 reuse it. Source it from GetVerifiedBadgeQuery. expired (a required credential lapsed) shows distinctly from unverified.

  • Publish gate. Wire the f4 "انتشار پروفایل / go-live" action: when the aggregate status is not approved, the publish CTA is disabled with a blocked-until-verified explanation ("برای انتشار، احراز هویت را کامل کنید") that links to B3. This is the front-end enforcement of "not bookable until verified" — the server enforces it too; the UI must not let a nurse believe they're live when they're not.

3.4 Document uploader (shared composite component)

Build a reusable <DocumentUpload> at the shared level (src/components/…, co-located *.test.tsx) composed from MUI/App* primitives — used by every document step (national-ID card, license, education, criminal record):

  • Client-side validation before upload: file type (jpg/png/pdf per the contract) and size cap; reject with a clear field error.
  • Upload states: idle → selecting → uploading (progress %) → processing/hashing → success (thumbnail/✓ + file name) → error (retry). Wire progress from the upload mutation.
  • Re-upload on reject: when a step is rejected, the uploader shows the prior file's status and a re-upload affordance.
  • Returns the server's stored document metadata (storage key, name) — never holds or displays raw bytes beyond a local preview.

3.5 i18n namespace verification (both locales)

Fill the f0-reserved verification namespace in both messages/en.json and messages/fa.json, in sync: step labels and per-step status labels (one key per code and per status — never derive a label from the enum string), the B3/B4/B5/B6 screen copy, the uploader states, the honesty-sensitive copy (manual-review vs auto-query), the blocked-until-verified message, the trust-badge labels, and the shared-SIM / mismatch / format error messages. fa is default and RTL.

Deferred (do not build here)

  • The admin verification review queue (pass/reject a manual step, the doc viewer, credential-entry form) → (DEFERRED to frontend-phase-15-b15.md), the admin backoffice. This phase only triggers the mock approval to observe the state change (see §7); it does not build the admin UI.
  • Credential renewal/expiry prompts beyond rendering the expired badge state → the nurse renewal prompt UI is (DEFERRED); the server's CredentialExpiryScannerJob (b6) raises the alert.
  • Video interview (B6 mentions it) — not a built step at MVP; reflect it only as a grey "next" row if the contract returns it; otherwise omit. Do not invent an interview-scheduling flow (DEFERRED).
  • Customer national-ID KYC — out of scope (the product gates only nurses) (DEFERRED).

4. Mocks & seams in this phase

This phase introduces no new front-end seam of its own beyond the standard services/{domain} data seam — the verification vendors (identity KYC, Shahkar, MoH/INO license, criminal record, IBAN ownership, object storage) are mocked server-side behind DI seams owned by backend-phase-6 (IIdentityKycProvider, IShahkarVerifier, ICredentialVerifier, IBankAccountOwnershipVerifier, IObjectStorage) — the front end consumes them only through the b6 contract, exactly as if they were real. Reuse the services/{domain} mock-clientApi pattern from f0: if the b6 contract or a specific shape isn't published when you start, build a mock clientApi behind the services/verification seam that returns realistic, contract-shaped VerificationStatus (e.g. identity → in_review, then flippable to passed), record it in your phase report + the mock registry, and append the gap to for-backend.md. The swap to the real endpoint must be implementation-only (same hook signatures, same queryKeys) — no call-site changes.

5. Critical rules you must not get wrong

  • A nurse is NOT bookable and cannot publish until verified. When the aggregate status is not approved, the publish/go-live CTA is disabled with the blocked-until-complete explanation, and every service variant stays inert. This front-end gate mirrors the server's guarded is_verified flip — never let the UI imply a nurse is live before verification completes.
  • Honest copy — never advertise a check that isn't performed. Manual steps (MoH/INO license, criminal record) say "در حال بررسی / آپلود شد"; only the genuinely-performed automated checks (identity liveness, civil-registry, Shahkar) may say "استعلام خودکار". A step is "تاییدشده" only when its status is passed. This is a product constraint, not a style preference.
  • Per-step rejected-with-reason. A rejected step renders its rejection_reason and a clear re-submit/re-upload path — never a dead end. The shared-SIM Shahkar failure is an explicit, handled, non-accusatory state, not a generic error.
  • The data is data-driven — render steps[] from the contract, don't hardcode the six steps. The pipeline is rows server-side; the UI iterates the returned ordered list and maps each code/status to a label + chip. A new step type appearing in the response must render without a code change.
  • Reuse the shared stepper and status chip from f0 — do not re-implement them. Concrete MUI primitives stay MUI; the new shared pieces (<DocumentUpload>, <TrustBadge>) live at the shared level with tests.
  • Caching is a feature. B3 and B6 read the same VerificationStatus query — one cached source, two views; every submit/upload mutation invalidates the status (and badge) so the checklist updates from cache, never an extra manual refetch. Minimise re-renders (stable refs, select for slices).
  • RSC/client boundary + RTL + both locales + tokens. No next/headers/next-intl/server in client components; design RTL-first (fa default); every string a key in both locale files; colours from tokens.css (the chip variants map to the wireframe's green/amber/grey/red legend); MUI v9 API only.
  • Document bytes are never shown from the API — uploads return metadata only; a local preview before upload is fine, but the rendered "uploaded" state is driven by server metadata, not retained bytes. The national-ID and credential numbers are sensitive — show masked values where the contract masks them.
  • Dates: credential issue/expiry are UTC on the wire, Shamsi for display (use the f0 date util).

6. Definition of Done

The shared definition-of-done.md, plus:

  • services/verification exists with the f0/f4 shape; types derived from verification.md (not guessed); mutations invalidate the status/badge query; one hook per file; no raw fetch().
  • B3 (status checklist), B4 (identity), B5 (credentials), B6 (under review) render under the nurse shell, RTL, both locales in sync, reusing the f0 stepper + status chip.
  • <DocumentUpload> and <TrustBadge> are shared components with co-located *.test.tsx; uploader enforces type/size, shows progress, and supports re-upload on reject.
  • The publish/go-live CTA from f4 is gated: disabled + blocked-until-verified message until approved.
  • Honesty copy verified: manual steps never claim an automated authority check; a step shows "تاییدشده" only when passed.
  • Rejected steps show their reason and a re-submit path; shared-SIM is a clear non-accusatory state.
  • npm run check green; npm run test:ci green (shared components added/touched); en.json/fa.json in sync.
  • client/CLAUDE.md Project Structure updated for the new route subtree + services/verification + the new shared components.

7. How to test (what a human can verify after this phase)

Run npm run dev (and the b6 server, or the mock clientApi if b6 isn't merged). Sign in as a nurse.

  • Checklist hub: open the nurse verification screen → B3 shows the "X از Y" meter, step 1 (mobile) already passed/green, the rest not_started. The continue CTA routes to the next step (calling start first when not_started).
  • Submit identity: B4 → enter a valid کد ملی, upload a national-ID card image (watch the uploader's progress → success), capture a liveness selfie, submit → the identity_kyc and shahkar_match steps move to pending/in_review on B3 without a manual refresh (cache invalidation). Try an invalid national-ID → field error; trigger the mock shared-SIM number → a clear non-accusatory Shahkar message.
  • Submit credentials: B5 → enter the نظام پرستاری number, upload the license + education cert, add specialty chips, submit → the credential steps move to in_review; the UI lands on B6 showing "در حال بررسی", the 2448h note, and the mini-checklist.
  • Approval flips verified: trigger the b6 mock admin approval (the mock provider/test endpoint, or set the mock clientApi to return approved) → re-open B3: aggregate is approved, all required steps passed, the trust badge shows verified on the nurse profile, and the publish CTA is now enabled (was blocked-until-verified before).
  • Rejected step: make a step return rejected (mock) → its row shows the rejection reason and a working re-upload/re-submit path; the publish CTA stays blocked.
  • Quality: npm run check and npm run test:ci pass; toggle locale → RTL/strings flip correctly; dark mode renders; React Query Devtools shows one verification.status query feeding both B3 and B6 and invalidating on each mutation.

8. Hand off & document (close the phase)

  • Docs: update the Project Structure tree in ../../../client/CLAUDE.md for the nurse verification route subtree, services/verification, and the new shared components (<DocumentUpload>, <TrustBadge>); add a one-line note that the trust badge is the reusable component f6 consumes. If you discover a verification rule the product docs don't capture, record it in ../../../product/business/02-nurse-verification.md (don't invent — record decisions).
  • Contracts: consume ../../contracts/domains/verification.md (frontend produces none). Any missing/ambiguous shape (e.g. whether credential submit is one endpoint or split, the exact document-upload flow, the badge payload) → append to ../../shared-working-context/frontend/requests/for-backend.md; do not edit backend files.
  • Handoff & report: append your phase summary to ../../shared-working-context/frontend/STATUS.md; write ../../shared-working-context/reports/frontend-phase-5-report.md — what was built, what is now testable and exactly how (the §7 steps), what is mocked client-side (the services/verification mock clientApi, if used) and how it swaps to the real b6 endpoint, the contract consumed + gaps filed, and the follow-up that f6 reuses the <TrustBadge>. Update the mock registry for any client-side mock.
  • Memory: save a project memory note for the non-obvious decisions — the data-driven step rendering, the single-status-query-two-views (B3/B6) caching choice, the reusable <TrustBadge>/<DocumentUpload> seams, and the publish-gate wiring — with a one-line pointer in MEMORY.md.