8.9 KiB
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 fromverification.md(camelCase wire):VerificationStatus(aggregatestatus+isBookable+blockingSteps+ orderedsteps[]),VerificationStep,RunStepResult,UploadUrlResult,DocumentConfirmedResult,VerificationDocument(metadata only),NurseCredential,TrustBadge, theVerificationApiseam, and the string-literal enums (aggregate/per-step status, the six step codes, credential type, verification method,BadgeState). Helpers:isApproved,ownBadgeState(own-profile, computesexpired),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 statusstaleTime(30 s) + badgestaleTime/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), selectingindex.ts. Both implementVerificationApi; 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 invalidatesstatus()(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_startedstart-CTA, the in-progress "X از Y" meter + data-driven checklist (VerificationChecklistreusing the sharedStatusChip), a blocking summary, a single continue CTA that routes to the next actionable step (callsuseStartVerificationfirst whennot_started), and the terminalapprovedstate (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 fromstatus) each moving its step toin_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 (reusingStatusChip) — a focused second view of the same cachedstatus()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 theverificationnamespace; 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(inMyServicesList): the go-live CTA is disabled with a blocked-until-verified explanation (+ link to B3) until the aggregate isapproved. Mirrors the server's guardedis_verifiedflip. - Trust badge on the nurse profile — sourced from the own
VerificationStatusviaownBadgeState; the unverified banner now shows only untilverified. - 6 AppIcons (
upload/document/refresh/identity/license/publish), 4 route constants, theverificationi18n 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:
- Checklist: B3 shows "X از Y" with the mobile step already green, the rest
not_started. The continue CTA callsstart(seeds steps) then routes to B4. - Identity (B4): a valid کد ملی + card image (watch the uploader progress → ✓) + selfie → submit → the
identity_kyc+shahkar_matchsteps update on B3 without a refresh. National-ID0000000000→ failed KYC; national-ID1111111111→ the non-accusatory shared-SIM Shahkar message. - Credentials (B5): INO number + upload each manual document (→
in_review) + specialty chips → submit → lands on B6 ("در حال بررسی", 24–48h, mini-checklist). - 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. - Rejected step: Reject a document → the step shows its rejection reason + a working re-upload path; the publish CTA stays blocked.
- React Query Devtools shows one
verification.statusquery 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+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 realsubmitCredentialDetailsno-ops until it lands (the document uploads are contract-backed). Also flagged:VerificationStepDtohas noisRequired(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 fromuseNurseTrustBadge(nurseId)/publicBadgeState. Theexpiredstate is own-profile only (the public badge payload carriesisVerifiedonly). - f12 payout depends on the
bank_account_verificationstep (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+ flipUSE_VERIFICATION_MOCK=false.