# Frontend Phase 5 — Nurse verification flow (trust engine) — Report (2026-07-09) Builds the trust engine's front end: the staged, platform-owned verification a nurse walks before any service can go live. A nurse lands on a **status checklist (B3)**, submits **identity (B4)**, submits **professional credentials (B5)**, and waits on **under-review (B6)** until an admin decides — then a **trust badge** renders and the **publish gate** unlocks. Consumes the b6 `verification` contract. Unlocks a bookable verified nurse + the `` f6 reuses. ## What was built ### `services/verification` domain (`client/src/services/verification/`) - **`types.ts`** — DTOs mirrored from [`verification.md`](../../contracts/domains/verification.md) (**camelCase** wire): `VerificationStatus` (aggregate `status` + `isBookable` + `blockingSteps` + ordered `steps[]`), `VerificationStep`, `RunStepResult`, `UploadUrlResult`, `DocumentConfirmedResult`, `VerificationDocument` (metadata only), `NurseCredential`, `TrustBadge`, the `VerificationApi` seam, and the string-literal enums (aggregate/per-step status, the six step codes, credential type, verification method, `BadgeState`). Helpers: `isApproved`, `ownBadgeState` (own-profile, computes `expired`), `publicBadgeState` (public/search — verified/unverified only), `SPECIALTY_PRESETS`. - **`validation.ts`** — `isValidNationalId` (10-digit mod-11 checksum; rejects all-same-digit). - **`keys.ts`** — `verificationKeys.status()` (the single cached source B3+B6 read), `.documents(code)`, `.badge(nurseId)`. - **`constants.ts`** — `USE_VERIFICATION_MOCK` (**default true**), the status `staleTime` (30 s) + badge `staleTime`/`gcTime`, the document type/size caps (jpg/png/pdf, 5 MB), `NATIONAL_ID_LENGTH`. - **`apis/`** — `clientApi.ts` (real HTTP, action-style routes, XHR signed-URL PUT for upload progress + SHA-256 integrity hash), `mockApi.ts` (**primary**, full journey + dev-only admin sim), selecting `index.ts`. Both implement `VerificationApi`; swap is one line. - **`hooks/`** — one per file: `useVerificationStatus` (query), `useStartVerification` (setQueryData), `useSubmitIdentity` (runs KYC → chained Shahkar), `useRunBankVerification`, `useUploadVerificationDocument` (progress via vars), `useSubmitCredentials`, `useNurseTrustBadge`. **Every mutation invalidates `status()`** (badge where relevant), so the checklist re-renders from cache with no manual refetch. ### Screens — nurse verification route subtree (`app/[locale]/(private-routes)/nurse/verification/`) - **B3 `page.tsx`** — the hub: loading skeleton, error+retry, `not_started` start-CTA, the in-progress **"X از Y" meter + data-driven checklist** (`VerificationChecklist` reusing the shared `StatusChip`), a **blocking summary**, a single **continue CTA** that routes to the next actionable step (calls `useStartVerification` first when `not_started`), and the terminal **`approved`** state (publish link). - **B4 `identity/page.tsx`** — national-ID field (checksum) + national-ID card + liveness selfie **local captures** (identity stores no server document — they feed the automated KYC), the honest auto-registry note, submit → `useSubmitIdentity`. Handles national-ID mismatch (`failed`) and the **non-accusatory shared-SIM** Shahkar failure distinctly. - **B5 `credentials/page.tsx`** — INO number + specialty chips (presets + add-your-own) + **a `` per manual credential step (data-driven from `status`)** each moving its step to `in_review`, + an optional education-cert local upload + optional registry fields; submit → `useSubmitCredentials`, lands on B6. Copy reflects **manual review**, never an automated authority check. - **B6 `review/page.tsx`** — the under-review resting screen: "در حال بررسی" + the 24–48h note + a **condensed mini-checklist** (reusing `StatusChip`) — a focused **second view of the same cached `status()` query**, not a second fetch. CTA back to B3. - **`verificationSteps.ts`** — the data-driven glue: `displaySteps` (prepends a synthetic passed **mobile** step), `progressCounts`, `stepLabelKey`/`stepDescriptionKey`, `stepStatusChip` (the green/amber/grey/red legend), `routeForStep`, `nextActionRoute`/`nextActionableIndex`. - **Journey stepper:** B4/B5/B6 reuse the shared `StepperHeader` (identity → credentials → review). ### Shared components (with co-located tests) - **``** — the reusable uploader: client type/size validation **before** upload, the full idle → uploading(%) → success(✓ + name / local image preview) → error(retry) machine, re-upload on reject, server-metadata as the "uploaded" truth, and a **local-capture mode** (B4). Its state chrome uses the `verification` namespace; the caller passes the field label/hint. - **``** — verified / unverified / expired off `--bal-*` tokens. Rendered on the nurse's own profile; **exported so f6 reuses it** in search results + the public profile. ### Wiring - **Publish gate** — `nurse/services/PublishGate.tsx` (in `MyServicesList`): the go-live CTA is **disabled with a blocked-until-verified explanation** (+ link to B3) until the aggregate is `approved`. Mirrors the server's guarded `is_verified` flip. - **Trust badge on the nurse profile** — sourced from the own `VerificationStatus` via `ownBadgeState`; the unverified banner now shows only until `verified`. - **6 AppIcons** (`upload/document/refresh/identity/license/publish`), **4 route constants**, the `verification` **i18n namespace (123 keys)** in both locales in sync. ## What is now testable, and exactly how Run `npm run dev`, sign in as a **nurse** (mock auth code `123456` if `USE_AUTH_MOCK`), open **/nurse/verification**: 1. **Checklist:** B3 shows "X از Y" with the mobile step already green, the rest `not_started`. The continue CTA calls `start` (seeds steps) then routes to B4. 2. **Identity (B4):** a valid کد ملی + card image (watch the uploader progress → ✓) + selfie → submit → the `identity_kyc` + `shahkar_match` steps update on B3 **without a refresh**. National-ID `0000000000` → failed KYC; national-ID `1111111111` → the non-accusatory **shared-SIM** Shahkar message. 3. **Credentials (B5):** INO number + upload each manual document (→ `in_review`) + specialty chips → submit → lands on **B6** ("در حال بررسی", 24–48h, mini-checklist). 4. **Approval flips verified:** the **dev-only "Simulate admin review" panel** on B3/B6 (mock-flag-gated — stands in for the deferred f15 admin queue) → *Approve all* → B3 shows `approved`, the **trust badge** on the nurse profile shows **verified**, and the **publish CTA on /nurse/services is enabled**. 5. **Rejected step:** *Reject a document* → the step shows its **rejection reason** + a working re-upload path; the publish CTA stays blocked. 6. React Query Devtools shows **one `verification.status` query** feeding B3 + B6 and invalidating on each mutation. Toggle `/en`↔`/fa` (RTL) and dark mode — strings + layout flip. ## What is mocked / waiting on a real service `services/verification` runs behind **`verificationMockApi`** (`USE_VERIFICATION_MOCK = true`) — b6 isn't reachable in this environment, so the mock drives the full journey (identity/Shahkar/bank runs, document uploads, admin-decision sim). The **swap is one line** (`USE_VERIFICATION_MOCK = false`): `verificationClientApi` is wired to every b6 route (`nurse_verification/*`, `nurses/{id}/trust_badge`), same hook signatures + query keys, no call-site change. See the mocks-registry row for the deterministic test triggers. Vendor calls (KYC/Shahkar/credential/IBAN/object-storage) are mocked **server-side** by b6 — the front end consumes them as if real. ## Contracts consumed + gaps filed - **Consumed:** [`verification.md`](../../contracts/domains/verification.md) + `openapi/swagger.v1.json` (b6). - **Filed:** **REQ-011** — a nurse-facing endpoint for the structured credential details (INO number, specialties, license fields) B5 collects; the contract records these only on the admin `decide`, so the real `submitCredentialDetails` no-ops until it lands (the document uploads are contract-backed). Also flagged: `VerificationStepDto` has no `isRequired` (the client treats every seeded step as required). ## Follow-ups for later phases - **f6** reuses `` (import from `@/components`) on search results (C2) + the public nurse profile (C3), sourced from `useNurseTrustBadge(nurseId)` / `publicBadgeState`. The `expired` state is own-profile only (the public badge payload carries `isVerified` only). - **f12 payout** depends on the `bank_account_verification` step (this phase deep-links it to the f2 bank screen). - The **admin verification review queue** (pass/reject, doc viewer, credential entry) is **f15** — the dev-only mock sim here is a stand-in, gated on `USE_VERIFICATION_MOCK`. - Deliver **REQ-011** then wire `submitCredentialDetails` + flip `USE_VERIFICATION_MOCK=false`.