some ui phase improvement planning
This commit is contained in:
@@ -0,0 +1,293 @@
|
||||
# UI Phase 8 — Nurse business & verification
|
||||
|
||||
> **Mission:** the nurse's business tools work, but the go-live journey is scattered across four
|
||||
> disconnected nags, the verification flow runs two competing progress metaphors, and the publish CTA
|
||||
> fires a success snackbar while publishing **nothing**. This phase unifies the setup journey into one
|
||||
> activation checklist, rebuilds verification as a single vertical trust journey with the TrustBadge
|
||||
> payoff visible, and fixes the real defects along the way (rejected-upload feedback, dead-end bank
|
||||
> form, non-hydrating credentials, whole-city double-encoding). Verification is the product's core
|
||||
> ritual; after this phase it should feel like it.
|
||||
>
|
||||
> **Track:** frontend · **Depends on:** [Phases 0–2](ui-phase-2-shells-and-navigation.md);
|
||||
> [Phase 4](ui-phase-4-customer-storefront.md) (reuses its verification-explainer / trust-dossier
|
||||
> components) · **Unlocks:** the supply side can set up, get verified, and go live with confidence
|
||||
> **Before you start, read [../../phases/_shared/agent-operating-rules.md](../../phases/_shared/agent-operating-rules.md)
|
||||
> and invoke the frontend-designer skill — both are mandatory.**
|
||||
|
||||
## 1. Context — where this sits
|
||||
|
||||
Balinyaar is a trust-first marketplace: a nurse's verification badge is what a family buys. The nurse
|
||||
workspace (`client/src/app/[locale]/(private-routes)/nurse/`) is functionally complete — profile,
|
||||
services/variant builder, coverage, bank, and the B3–B6 verification flow all exist and are
|
||||
token-disciplined — but the composition undersells the product. Diagnosed root causes (all verified):
|
||||
|
||||
1. **The publish CTA fakes success.** `nurse/services/PublishGate.tsx:64` —
|
||||
`onClick={() => enqueueSnackbar(t('publish_done'), { variant: 'success' })}`. Nothing is published.
|
||||
The server computes searchability itself: `is_searchable = isVerified && isAcceptingBookings &&
|
||||
status != Suspended && variant.IsActive`, one index row per variant × coverage area
|
||||
(`SearchIndexMaintainer.cs:248` `NurseBookable`) — so ≥1 coverage area is structurally required
|
||||
too. **And `IsAcceptingBookings` defaults to `false`** (`NurseProfileConfig.cs:19`) with a real,
|
||||
*unwired* toggle endpoint (`NurseProfilesController.SetAcceptingBookings`) that reindexes in the
|
||||
same transaction. A fully verified nurse can still be invisible, and no UI surfaces why.
|
||||
2. **Four disconnected go-live nags for one journey:** the blocked-until-verified banner (profile),
|
||||
PublishGate (services), the empty-coverage state, and the bank empty state each warn in isolation.
|
||||
3. **Two competing progress metaphors:** the B3 hub counts 7 checklist steps («X از Y» +
|
||||
`LinearProgress`, inflated by the synthetic mobile step — `verificationSteps.ts:17` `MOBILE_STEP`)
|
||||
while B4/B5/B6 show an unrelated bare 3-step `StepperHeader` (`verification/review/page.tsx:28`).
|
||||
4. **Rejected-upload recovery shows no feedback:** in `components/DocumentUpload/DocumentUpload.tsx`
|
||||
the `{rejected ? (` branch (line 155) wins over `: state === 'uploading' ?` (line 193) — a
|
||||
re-upload stays frozen on the red rejected card, and the re-upload button stays enabled mid-flight.
|
||||
5. **The credentials form doesn't survive re-entry:** `verification/credentials/page.tsx` initialises
|
||||
every field to `''`/`[]` (lines 37–44); submit is `disabled={... || !anyUploaded}` (line 255) where
|
||||
`anyUploaded` reads only this-session local state (line 106); license dates are native Gregorian
|
||||
`type="date"` inputs (lines 221–236) on a trust-critical Persian form.
|
||||
6. **Bank is a dead end once verified:** `nurse/bank/page.tsx:31` —
|
||||
`showFormNow = !isLoading && (accounts.length === 0 || showForm)`; `setShowForm(true)` exists only
|
||||
in the mismatch branch (line 81). A nurse who switches banks cannot add an account.
|
||||
7. **Whole-city is encoded twice:** the coverage scope toggle (`coverage/page.tsx:198–210`) vs
|
||||
`CascadingRegionSelect.tsx:152`'s own `<MenuItem value="">{t('whole_city')}</MenuItem>` — picking
|
||||
the latter under "specific districts" trips the district-required error (`page.tsx:89`).
|
||||
8. **Qualifications aren't editable:** `nurse/profile/page.tsx:58–60` silently round-trips
|
||||
`educationLevel`/`educationField`/`specializationsJson` — yet the server's
|
||||
`UpsertNurseProfileCommand` accepts and persists all three (verified). Pure frontend gap, no REQ.
|
||||
|
||||
**What already exists (do not rebuild):**
|
||||
|
||||
- The `services/verification` seam — ONE cached `useVerificationStatus()` query feeding B3+B6, the
|
||||
data-driven step catalog (`verificationSteps.ts`), the dev-only mock admin sim
|
||||
(`verification/page.tsx:87`). Restructure the *presentation*, keep the architecture.
|
||||
- `DocumentUpload`'s idle→uploading(%)→success/error state machine + rejected variant — fix the
|
||||
branch precedence, don't rewrite the machine. `BankStatusPanel`'s three states with masked
|
||||
`dir="ltr"` IBAN; the pending ownership poll in `services/nurse`.
|
||||
- Tested shared components: `VariantCard`, `PriceDisplay` (BigInt-safe, Toman-at-the-boundary),
|
||||
`CategoryTile`, `CascadingRegionSelect`, `TrustBadge`, `StatusChip`.
|
||||
- Phase 0/1 foundations (themed `StepperHeader`, Jalali date picker, EmptyState/ErrorState kit,
|
||||
PageHeader, card kit) and phase 4's verification-explainer / trust-dossier components — consume,
|
||||
never fork.
|
||||
- Real backends: profiles, catalog, serviceAreas, nurse-bank run `USE_*_MOCK = false`; verification
|
||||
remains mock-primary (`USE_VERIFICATION_MOCK = true`) — leave the flags as they are.
|
||||
|
||||
## 2. Required reading (do this first)
|
||||
|
||||
- [audit/nurse-workspace.md](audit/nurse-workspace.md) and
|
||||
[audit/nurse-trust-ops.md](audit/nurse-trust-ops.md) — the full evidence + keep-lists.
|
||||
- [.claude/skills/frontend-designer/SKILL.md](../../../.claude/skills/frontend-designer/SKILL.md) —
|
||||
the design contract (invoke the skill, don't just read it).
|
||||
- `product/business/02-nurse-verification.md` (automated vs manual checks, honesty constraints);
|
||||
`product/business/03-service-catalog-and-pricing.md` + `04-search-and-matching.md` (searchability).
|
||||
- Code: the nurse route tree (`profile`, `services`, `coverage`, `bank`, `verification/**`),
|
||||
`components/DocumentUpload/`, `components/geography/CascadingRegionSelect.tsx`,
|
||||
`services/verification/types.ts` (nurse-facing `VerificationStatus` has **no** `submittedAt`;
|
||||
`NurseCredential` **never** carries `credentialNumber`), `services/profiles/types.ts`
|
||||
(`NurseProfileDto.isAcceptingBookings` is read-only today; `UpsertNurseProfileInput` lacks it).
|
||||
- Phase 7's report (`../../shared-working-context/reports/ui-phase-7-report.md`) — where the nurse
|
||||
dashboard left its setup-status slot — and the REQ tracker
|
||||
[for-backend.md](../../shared-working-context/frontend/requests/for-backend.md) (REQ-001…038 taken;
|
||||
confirm the high-water mark before filing).
|
||||
|
||||
## 3. Scope — build this
|
||||
|
||||
### 3.1 Activation checklist («راهاندازی») + an honest, *real* go-live gate
|
||||
|
||||
- Build `src/components/ActivationChecklist/` (shared, tested): one tracker composing the five
|
||||
scattered states from already-cached queries — تأیید هویت و مدارک ✓ (`useVerificationStatus`),
|
||||
تکمیل نمایه ✓ (bio + avatar), حداقل یک خدمت فعال ✓ (`useMyVariants`), محدودهٔ پوشش ✓
|
||||
(`useServiceAreas`), شبای تأییدشده ✓ (`useNurseBankAccounts`) — each row a StatusChip-style state +
|
||||
a deep link to its fix. Distinguish the tiers honestly: the first four drive **search visibility**;
|
||||
bank drives **getting paid** (not part of `is_searchable` — label it «برای دریافت درآمد»).
|
||||
- Mount it on the services page (above `MyServicesList`) and in the phase-7 dashboard's setup slot
|
||||
(replace any placeholder card phase 7 left there — one shared component, not a fork). Collapse it to
|
||||
a compact «فعال در جستجو» state once every row passes and accepting-bookings is on.
|
||||
- **Replace PublishGate's fake success with the real switch.** Wire the existing, unwired
|
||||
`POST nurse_profiles/set_accepting_bookings` through the profiles seam (types + clientApi + mockApi +
|
||||
a `useSetAcceptingBookings` hook that invalidates the profile query). The gate becomes state-driven:
|
||||
unmet conditions → guidance («برای نمایش در جستجو: …» listing exactly the unmet real conditions);
|
||||
met but `isAcceptingBookings === false` → «شروع پذیرش رزرو» calling the real endpoint; live → the on
|
||||
state + «توقف موقت پذیرش». Success copy only after the mutation succeeds — never a no-op snackbar.
|
||||
|
||||
### 3.2 Unified verification journey (one spine, one metaphor)
|
||||
|
||||
- Rebuild the B3 hub (`verification/page.tsx` + `VerificationChecklist.tsx`) as ONE vertical journey:
|
||||
grouped step cards — **هویت** (identity KYC + Shahkar + mobile), **مدارک حرفهای** (MoH license, INO
|
||||
membership, criminal record), **بانک** (IBAN-owner match) — on a single spine, each group folding the
|
||||
data-driven steps from `verificationSteps.ts` (keep the catalog + synthetic-mobile-step architecture
|
||||
— do-not-regress; regroup presentation only).
|
||||
- Remove the competing 3-step `StepperHeader` from B4/B5/B6; those pages get a journey-context header
|
||||
(group name + «بازگشت به مسیر تأیید»). One progress answer everywhere.
|
||||
- The payoff: a live TrustBadge preview panel on the hub — «این نشان را خانوادهها میبینند» — that
|
||||
fills as groups pass, reusing phase 4's verification-explainer/trust-dossier components for the
|
||||
framing (consume, don't duplicate).
|
||||
- B6 under-review: submitted timestamp (Shamsi) + a what-happens-next timeline (بررسی توسط کارشناس →
|
||||
نتیجه در ۲۴–۴۸ ساعت → فعالسازی نشان) + the same journey header as siblings. `VerificationStatus`
|
||||
carries no `submittedAt` today → file the REQ (§4); omit the line when absent, never fake a time.
|
||||
|
||||
### 3.3 DocumentUpload fixes + capture guidance
|
||||
|
||||
- **Fix the precedence bug:** `state === 'uploading'` (and the success flash) must win over the
|
||||
`rejected` prop so a re-upload shows live progress. Keep the rejection reason visible *above* the
|
||||
progress UI during re-upload, and disable the re-upload button while in flight. Update the
|
||||
co-located test to cover rejected→re-upload→progress→success.
|
||||
- Capture guidance where cheap: a frame-overlay illustration for the ID-card/selfie local-capture mode
|
||||
(B4) + static hints («نور کافی، بدون تاری، چهار گوشهٔ کارت داخل کادر»). Client-side too-dark/blurry
|
||||
heuristics only if trivially cheap; no new dependencies.
|
||||
|
||||
### 3.4 Credentials form (B5) — survives re-entry, Persian dates
|
||||
|
||||
- Hydrate from server state: steps already `in_review`/`passed` render as submitted summaries (from
|
||||
`useVerificationStatus`), not blank inputs; derive the submit gate from server + session state so a
|
||||
returning nurse never sees a dead disabled button with no explanation. The raw INO/credential number
|
||||
is **never returned by design** (encrypted server-side) — render a submitted state («شمارهٔ نظام ثبت
|
||||
شد»), never re-prompt as if lost. If no nurse-facing read-back of the structured details
|
||||
(authority/dates/specialties) exists, file the REQ (§4) and hydrate mock-tolerantly.
|
||||
- Replace both native `type="date"` fields with the phase-1 Jalali picker — license issue/expiry feed
|
||||
the credential-expiry sweep; wrong dates are a correctness risk, not a style nit.
|
||||
- Specialty chips polish: selected/unselected states off tokens, custom-specialty entry kept.
|
||||
|
||||
### 3.5 Services & variant builder
|
||||
|
||||
- Step 3 live preview: render the real `VariantCard` as «اینگونه در جستجو دیده میشوید» composing the
|
||||
entered name/category/price — the nurse is composing a listing; show the listing.
|
||||
- Replace the wrapped `ToggleButtonGroup` option values (`VariantBuilder.tsx:424`
|
||||
`sx={{ flexWrap: 'wrap' }}` — grouped-button borders/corners break on wrap) with a chip group.
|
||||
- Fix the duplicate-warning contrast: `VariantBuilder.tsx:263` sets body text to `var(--bal-warning)`
|
||||
(amber on paper fails light-mode contrast) — restyle as an accent-edge panel with `text.primary`
|
||||
body, warning reserved for the edge/icon; add an "edit the existing listing" affordance on the 409
|
||||
duplicate. The themed `StepperHeader` arrives free from phase 0/1 — just consume it.
|
||||
|
||||
### 3.6 Coverage — one control owns whole-city (+ cheap map viz)
|
||||
|
||||
- Collapse the double encoding: exactly one control owns the whole-city/districts choice. Preferred:
|
||||
drop the separate scope toggle and let `CascadingRegionSelect`'s district level own it (its
|
||||
«کل شهر» empty option *is* the choice — `districtId=null` = whole city both ways, matching the
|
||||
serviceAreas contract); alternatively keep the toggle and add a prop suppressing the select's own
|
||||
whole-city item. Either way the district-required error can no longer be triggered by a choice the
|
||||
UI itself offered. Other `CascadingRegionSelect` consumers (addresses, search) must be unaffected —
|
||||
prop-gate any change; run its test.
|
||||
- Optional static map visualization of covered areas consuming `components/geography` — keep it cheap.
|
||||
Real tile rendering (DEFERRED → [Phase 9](ui-phase-9-customer-account-and-care-circle.md)'s map
|
||||
picker; reuse what it lands).
|
||||
- Give `removeArea.mutate` an `onError` toast (currently silent).
|
||||
|
||||
### 3.7 Bank — an accounts section, not a one-shot form
|
||||
|
||||
- Restructure `nurse/bank/page.tsx` as an accounts section with a persistent «افزودن حساب دیگر» CTA
|
||||
(evidence §1.6). Change-IBAN path: add new → pending inquiry → verified → make primary → old account
|
||||
remains listed. No delete affordance unless the seam supports it (it doesn't — don't invent).
|
||||
- Surface the pending ownership poll explicitly: «در حال استعلام صحت شبا، معمولاً چند دقیقه طول
|
||||
میکشد…» instead of a silent pending chip.
|
||||
- Fix the error→false-empty hazard: a failed `useNurseBankAccounts` query renders the phase-1
|
||||
ErrorState with retry — never the "no account yet" empty state + open form (which invites a
|
||||
duplicate-IBAN submission).
|
||||
- Keep the three-state `BankStatusPanel` design exactly as is (do-not-regress).
|
||||
|
||||
### 3.8 Profile — qualifications editable + public preview
|
||||
|
||||
- Make education level/field and specializations real form fields (select + chips), submitted through
|
||||
the existing upsert — the server accepts them today (§1.8); no REQ, no server change.
|
||||
- Avatar upload + profile save get the phase-1 mutation-error convention (`onError` toasts — both are
|
||||
silent today), and warn before navigating away with a staged-but-unsaved avatar.
|
||||
- «نمایهٔ عمومی من»: a preview screen (`/nurse/profile/preview`) composing phase 4's C3 trust-dossier
|
||||
pieces (TrustBadge, attribute chips, `ServicePriceRow` list, coverage chips) **from the nurse's own
|
||||
data** (own profile + `useMyVariants` + `useServiceAreas` + own badge) — no dependency on the search
|
||||
index, so it works pre-publish. Link it from profile and services pages: it is the strongest
|
||||
motivator to complete bio/photo/credentials.
|
||||
|
||||
## 4. Mocks & seams in this phase
|
||||
|
||||
**No new mocks or seams.** All work stays behind the existing `services/{domain}` seams; do not flip
|
||||
any `USE_*_MOCK` flag (verification is deliberately still mock-primary). The one seam *extension* is
|
||||
adding `setAcceptingBookings` to the profiles seam (types + clientApi + mockApi in lockstep — the
|
||||
endpoint is real; the mock mirrors the flip).
|
||||
|
||||
Backend gaps become REQ entries appended to
|
||||
[for-backend.md](../../shared-working-context/frontend/requests/for-backend.md) — REQ-001…038 are
|
||||
taken; check the tracker's high-water mark (other UI phases may have filed more) and number onward.
|
||||
Expected filings, both rendered mock-tolerantly (present → render, absent → degrade gracefully):
|
||||
|
||||
- **`submittedAt` on the nurse-facing `VerificationStatusDto`** (B6 timestamp — the data exists; the
|
||||
admin queue DTO already serves it).
|
||||
- **Nurse-facing read-back of submitted credential details** (issuing authority, dates, specialties;
|
||||
masked/type-only for the number — never the raw encrypted `credentialNumber`) so B5 hydrates on
|
||||
re-entry. Verify against Swagger first — file only if it truly doesn't exist.
|
||||
|
||||
## 5. Critical rules you must not get wrong
|
||||
|
||||
- **Verification status is server truth.** The client NEVER flips `is_verified`, never fakes a step
|
||||
result, never derives "verified" from anything but the aggregate. The mock admin sim stays dev-only.
|
||||
- **`is_searchable` conditions are server-side.** The activation checklist *reflects* them; the search
|
||||
index flips only via server writes (the accepting-bookings endpoint reindexes in-transaction). Never
|
||||
claim "you are now visible" from a client-side condition check alone.
|
||||
- **No fake success — anywhere.** A CTA either performs a real mutation or is guidance. This is the
|
||||
bug this phase exists to kill.
|
||||
- **Honest-automation copy stays:** only genuinely automated checks say «استعلام خودکار»; manual-review
|
||||
steps never claim an authority check. TrustBadge `verified` renders only from the approved
|
||||
aggregate; `expired` stays visually distinct from never-verified.
|
||||
- **Masked IBAN + `dir="ltr"` stays** on every bank/IBAN render; national-ID and price inputs keep
|
||||
their LTR-pinned `textAlign:'start'` treatment.
|
||||
- **Keep-lists from both audits:** token discipline (zero hexes), `borderInlineStart` accents +
|
||||
logical props, money via `PriceDisplay`/BigInt (never a total from rate alone), soft-deactivate-only
|
||||
`VariantCard`, edit-mode locking of variant identity fields, `CascadingRegionSelect`'s cached geo
|
||||
queries + prefill guard, belt-and-braces duplicate coverage handling, locale digits + Shamsi dates.
|
||||
- **Design contract:** i18n in both catalogs, dark mode via tokens, MUI v9 API only (no `flexWrap` as
|
||||
a `Stack` prop), `App*` wrappers + icon registry (new icons in `AppIcon/config.ts`, lowercase),
|
||||
co-located tests for every shared component touched (`DocumentUpload`, `CascadingRegionSelect`, new
|
||||
`ActivationChecklist`), fetch/cookies rules untouched (`clientFetch` via the seam, never raw `fetch`).
|
||||
|
||||
## 6. Definition of Done
|
||||
|
||||
On top of the shared [definition-of-done.md](../../phases/_shared/definition-of-done.md):
|
||||
|
||||
- [ ] `npm run check` green; `npm run test:ci` green including updated `DocumentUpload` tests
|
||||
(rejected→re-upload→progress) and new `ActivationChecklist` tests.
|
||||
- [ ] `en.json`/`fa.json` in sync for every new key; no hard-coded strings.
|
||||
- [ ] The publish CTA performs a real `set_accepting_bookings` mutation; grep proves the
|
||||
`publish_done`-snackbar-with-no-effect pattern is gone.
|
||||
- [ ] Exactly ONE progress metaphor across B3–B6; `verificationSteps.ts` still drives rendering
|
||||
(no hard-coded step lists).
|
||||
- [ ] Re-uploading a rejected document shows live progress with the rejection reason still visible.
|
||||
- [ ] Returning to the credentials page with steps `in_review` shows submitted state — not blank
|
||||
fields with a dead submit button; license dates are Jalali inputs.
|
||||
- [ ] A verified-account nurse can add another bank account; a failed accounts query shows an error
|
||||
state, never the empty-state form.
|
||||
- [ ] The whole-city choice is owned by exactly one control; the district-required error can no
|
||||
longer be triggered by picking a UI-offered option.
|
||||
- [ ] Education/specializations round-trip through the real upsert and re-render after reload.
|
||||
- [ ] Visual verification on all four axes (`/fa` + `/en` × light + dark), mobile + desktop, for
|
||||
every touched screen — `/fa` first.
|
||||
|
||||
## 7. How to test (what a human can verify after this phase)
|
||||
|
||||
1. As the seeded **unverified** nurse → `/nurse/services`: the activation checklist shows unmet rows,
|
||||
each deep-linking to its page; the go-live CTA is guidance, not a button that toasts success.
|
||||
2. Complete verification via the dev admin sim → rows flip; the CTA becomes «شروع پذیرش رزرو»; click
|
||||
it → Network tab shows `POST nurse_profiles/set_accepting_bookings`; the panel shows the live/pause
|
||||
state. Searching as a customer now finds the nurse (server-side flip).
|
||||
3. `/nurse/verification`: one vertical journey with grouped cards (هویت / مدارک حرفهای / بانک) and a
|
||||
TrustBadge payoff preview; B4/B5 show no 3-step Stepper anywhere; B6 shows what-happens-next (+
|
||||
timestamp once the REQ lands).
|
||||
4. In B5, upload a doc, have it rejected (mock), re-upload → the progress bar animates while the
|
||||
rejection reason stays visible; the re-upload button is disabled mid-flight. Leave and return →
|
||||
submitted steps render as summaries; submit is not silently dead; dates open a Jalali picker.
|
||||
5. New variant: step-2 options render as chips (no broken grouped borders); step 3 shows the live
|
||||
`VariantCard` preview; a duplicate yields a readable warning + "edit existing" path.
|
||||
6. `/nurse/coverage`: no second whole-city affordance that errors; whole-city adds via the single
|
||||
control; (if built) the map shows covered areas.
|
||||
7. `/nurse/bank` with a verified account → «افزودن حساب دیگر» opens the form; pending shows the
|
||||
explicit inquiry copy; kill the API and reload → error state with retry, not the empty form.
|
||||
8. `/nurse/profile`: edit education + specializations, save, reload → values persist; navigate away
|
||||
with an unsaved avatar → warning. «نمایهٔ عمومی من» renders the own-data listing with TrustBadge +
|
||||
prices + coverage.
|
||||
9. Repeat the key screens on `/en`, dark mode, and a ~390px viewport.
|
||||
|
||||
## 8. Hand off & document (close the phase)
|
||||
|
||||
- Update `client/CLAUDE.md` (Project Structure) in the same change: the `/nurse/profile/preview`
|
||||
route, `components/ActivationChecklist/`, the profiles-seam `setAcceptingBookings` addition, and
|
||||
the reshaped verification hub.
|
||||
- Write `dev/shared-working-context/reports/ui-phase-8-report.md`: what shipped per §3 subsection,
|
||||
the REQ numbers actually filed, the PublishGate→real-toggle decision with its server evidence, and
|
||||
any foundation files you extended (per the README ownership rules — minimally, noted, never forked).
|
||||
- File the REQs in the tracker (§4) with `filed by ui-phase-8` attribution.
|
||||
- Save a memory note per operating-rules §8: the activation checklist's two-tier honesty (search
|
||||
visibility vs getting paid), the accepting-bookings wiring, the unified-journey decision, the
|
||||
DocumentUpload precedence fix.
|
||||
Reference in New Issue
Block a user