# 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 `<TrustBadge>` 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
  `<DocumentUpload>` 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)
- **`<DocumentUpload>`** — 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.
- **`<TrustBadge state=…>`** — 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 `<TrustBadge>` (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`.
