ui phase 3
This commit is contained in:
+21
-9
@@ -132,7 +132,6 @@ client/
|
||||
│ │ │ │ ├── useSearchFilters.ts # C1 colocated filter controller (debounced Toman price → IRR; derives the canonical NurseSearchFilters)
|
||||
│ │ │ │ ├── results/page.tsx # C2 results — rating-sorted NurseResultCard list; all four states (skeleton/empty-relax/error/populated); load-more; filters live in the URL (the cache key)
|
||||
│ │ │ │ └── nurse/[nurseId]/page.tsx # C3 nurse profile — badges (TrustBadge + نظام پرستاری) + attribute chips + a f13 tab strip: «خدمات» (ServicePriceRow list) / «نظرات» (ReviewsPanel — published-only aggregate+count + infinite list via services/reviews); "درخواست رزرو" hands off to /bookings/request (f7)
|
||||
│ │ │ ├── onboarding/page.tsx # /onboarding — A3→A4 wizard (relation → first patient)
|
||||
│ │ │ ├── bookings/
|
||||
│ │ │ │ ├── page.tsx # Thin RSC — generateMetadata (booking.list_title) + renders BookingsScreen
|
||||
│ │ │ │ ├── BookingsScreen.tsx # 'use client' — f8 رزروها list body (useBookingList('customer')); rows → booking detail
|
||||
@@ -207,12 +206,19 @@ client/
|
||||
│ │ ├── nurses/page.tsx # /partner/nurses — the center's sponsored nurses (verification badge)
|
||||
│ │ ├── bookings/page.tsx # /partner/bookings — the bookings the center legally covers (read-only summaries)
|
||||
│ │ └── settlement/page.tsx # /partner/settlement — rendered ONLY when is_merchant_of_record: per-booking commission invoices (commission/VAT decomposition via PartnerSettlementRow, signed-URL PDF, masked IBAN); non-MoR shows the "settlement via Balinyaar" state
|
||||
│ ├── (customer-focused)/ # ui-phase-3 — chrome-free counterpart to (customer) for can't-tab-away flows; same URL space (route groups add no segment)
|
||||
│ │ ├── layout.tsx # 'use client' — RoleGuard(expected=customer) → FocusedLayout (no BottomBar/bell/sidebar)
|
||||
│ │ └── onboarding/ # /onboarding — moved here from (customer) so the A3→A4 wizard can't be tabbed away from mid-setup
|
||||
│ │ ├── page.tsx # Thin RSC — generateMetadata (onboarding.welcome_title) + renders OnboardingScreen
|
||||
│ │ └── OnboardingScreen.tsx # 'use client' — welcome moment (not a stepper step) → relation (4 distinct icons: elderly/favorite/infant/account) → patient (StepperHeader 2 steps)
|
||||
│ └── (public-routes)/
|
||||
│ ├── layout.tsx # 'use client' — wraps PublicLayout
|
||||
│ ├── loading.tsx # Auth-card-shaped skeleton (brand mark + a card-sized block)
|
||||
│ └── login/ # /login — phone-OTP login (A1/A2 customer, B1/B2 nurse switch)
|
||||
│ ├── page.tsx # Thin RSC — generateMetadata (auth.customer_title) + renders LoginScreen
|
||||
│ └── LoginScreen.tsx # 'use client' — the actual LoginFlow body
|
||||
│ ├── login/ # /login — phone-OTP login (A1/A2 customer, B1/B2 nurse switch)
|
||||
│ │ ├── page.tsx # Thin RSC — generateMetadata (auth.customer_title) + renders LoginScreen
|
||||
│ │ └── LoginScreen.tsx # 'use client' — the actual LoginFlow body
|
||||
│ ├── terms/page.tsx # /terms — draft Terms of Service (ui-phase-3; DRAFT COPY, needs human/legal review before launch)
|
||||
│ └── privacy/page.tsx # /privacy — draft Privacy Policy (ui-phase-3; DRAFT COPY, needs human/legal review before launch)
|
||||
├── components/ # Shared UI components (each with .test.tsx if imported >1 place)
|
||||
│ ├── common/ # Foundational primitives (import from @/components or @/components/common)
|
||||
│ │ ├── AppButton/, AppIconButton/, AppIcon/, AppLink/, AppAlert/, AppLoading/ # house-default MUI wrappers (see frontend-designer skill §4)
|
||||
@@ -270,7 +276,7 @@ client/
|
||||
│ ├── geography/ # F3 geo composites: CascadingRegionSelect, AddressMapPicker (map-pin stand-in), AddressForm, AddressCard (each tested)
|
||||
│ ├── messaging/ # f14 tickets composites (import from @/components/messaging). Screens shared by the customer+nurse pages (role decides chrome): TicketInboxScreen, TicketThreadScreen (+ TicketMessageList), ContactSupportDialog (new-ticket → shows referenceCode), MessageComposer (optimistic send, draft-preserving), BookingSupportEntry (page-local glue on f8 booking detail — reuses the cached booking + care query, no refetch). Pure/tested: MessageBubble (mine/theirs, RTL-mirrored, never any internal-note styling), TicketListCard (prominent referenceCode + unread indicator + null-safe link), EmergencyBanner (post-confirmation tel: playbook, no VoIP seam). Helpers: statusKind.ts, authorLabel.ts
|
||||
│ ├── notifications/ # f14 notification composites (import from @/components/notifications). NotificationBell (chrome container — subscribes to the polling count so only it re-renders) → NotificationBellView (pure, tested), NotificationRow (pure, tested: unread emphasis + server title/body), NotificationCenter (shared page body: unread-first, mark-read-on-open + mark-all, deep-links via notificationDeepLink). Helper: notificationIcon.ts
|
||||
│ └── auth/ # Auth-flow composites: LoginFlow, PhoneStep, OtpStep, RoleRouter, SelectRole, AuthCard, BrandMark, AuthSplash, RoleGuard (role-aware shell guard, tested), AuthAccountError (/me-failed recovery), useCountdown
|
||||
│ └── auth/ # Auth-flow composites: LoginFlow, PhoneStep, OtpStep, RoleRouter, SelectRole, AuthCard, BrandMark, AuthSplash, RoleGuard (role-aware shell guard, tested), AuthAccountError (/me-failed recovery), useCountdown, useWebOtp (ui-phase-3 WebOTP autofill seam), AuthIllustration + TrustBullets (ui-phase-3 CSS/SVG login-hero treatment)
|
||||
├── i18n/
|
||||
│ ├── routing.ts # defineRouting — locales: ['en', 'fa'], defaultLocale: 'fa'
|
||||
│ ├── request.ts # getRequestConfig — loads messages/${locale}.json
|
||||
@@ -282,6 +288,7 @@ client/
|
||||
│ ├── AdminLayout.tsx # 'use client' — admin shell via TopBarAndSideBarLayout: sectioned sidebar (اعتماد/مالی/پشتیبانی/سیستم, useAdminCapabilities-gated, unchanged gating), TopBar identity chip (fine-grained role) + bell
|
||||
│ ├── PartnerLayout.tsx # 'use client' — partner portal via TopBarAndSideBarLayout; TopBar identity chip shows the center's own name (useMyPartnerCenter, skeleton while resolving)
|
||||
│ ├── PublicLayout.tsx # unauthenticated shell — minimal corner strip (logo + LocaleSwitcher + dark toggle), no sidebar/bottom bar; AuthCard renders its own larger BrandMark
|
||||
│ ├── FocusedLayout.tsx # ui-phase-3 — chrome-free shell for can't-tab-away flows (today: onboarding): a slim logo strip + content, no BottomBar/bell/sidebar; the route group above it still applies RoleGuard
|
||||
│ ├── TopBarAndSideBarLayout.tsx # 'use client' — the nurse/admin/partner engine: a fixed TopBar (useRouteTitle) + SideBar rendered as flex-row siblings (mobile temporary Drawer + desktop `variant="permanent"` Drawer switched by CSS `sx` breakpoints only — no `useIsMobile` structural branching, so desktop first paint already has the sidebar); optional `identity`/`sidebarIdentity`/`mobileBottomBar` slots
|
||||
│ ├── routeTitle.tsx # ui-2 static route→title map (longest-prefix over ROUTES.*, off the `nav` namespace) + `PageTitleProvider`/`usePageTitleOverride` per-page dynamic-title slot (area phases feed real names in later) + `useRouteTitle`; `isCustomerRootTab`/`CUSTOMER_ROOT_TABS` for the customer header's brand-lockup-vs-title branch
|
||||
│ ├── matchActivePath.ts # ui-2 shared longest-prefix, winner-takes-all active-path matcher (tested) — used by SideBarNavList and BottomBar so a nested route still lights up its parent tab, never a sibling
|
||||
@@ -316,7 +323,7 @@ client/
|
||||
│ ├── client.ts # getClientCookie, setClientCookie, deleteClientCookie
|
||||
│ └── index.ts # Re-exports constants ONLY (never server/client)
|
||||
├── services/ # Domain services — no top-level barrel; import directly from the file
|
||||
│ ├── auth/ # Phone-OTP auth: requestOtp/verifyOtp/refresh/logout/me/selectRole + role router (routing.ts) + useSessionRoleSync + useRoleHydration (resolved-vs-pending role state for RoleGuard)
|
||||
│ ├── auth/ # Phone-OTP auth: requestOtp/verifyOtp/refresh/logout/me/selectRole + role router (routing.ts: resolveRoleDestination + ui-phase-3's resolvePostLoginDestination for the validated `?next=` returnUrl) + useSessionRoleSync + useRoleHydration (resolved-vs-pending role state for RoleGuard)
|
||||
│ ├── patients/ # Care-recipient CRUD (b3 PatientDto + client-augmented relation/conditions), soft-archive; age.ts helper
|
||||
│ ├── profiles/ # Customer + nurse profile get/upsert + avatar (behind the ProfilesApi seam)
|
||||
│ ├── nurse/ # Nurse payout bank accounts + IBAN(Sheba) util (iban.ts) + ownership-inquiry states
|
||||
@@ -456,7 +463,7 @@ async function MyServerComponent() {
|
||||
- `'common'` — `DarkModeButton.tsx` (dark/light labels), shared words (loading, retry, currency_toman, …)
|
||||
- `'shell'` — actor-shell titles + the not-yet-built placeholder body
|
||||
- `'patients'` — the E1 patient list/CRUD (list, card, add/edit dialog, archive)
|
||||
- `'onboarding'` — the A3→A4 wizard + the shared enum labels (relation/condition/gender codes → labels)
|
||||
- `'onboarding'` — the A3→A4 wizard (ui-phase-3 added `welcome_*` for the pre-wizard welcome moment) + the shared enum labels (relation/condition/gender codes → labels)
|
||||
- `'home'` — the A5 family home (greeting + avatar, search bar, category grid, record/profile nudges)
|
||||
- `'profile'` — the customer profile + emergency contact
|
||||
- `'nurseProfile'` — the nurse B7 profile bootstrap (photo/bio/years + unverified placeholder)
|
||||
@@ -477,7 +484,8 @@ async function MyServerComponent() {
|
||||
- `'records'` — the f13 E2 care-record viewer + the nurse visit-note panel: the ownership banner, the four tab labels (`tab_{medications,routine,history,tasks}`), the access-denied + not-found cards, the editable-record field labels (`med_*`/`routine_*`/`task_*`) + empty states, the paged-history controls (`prev`/`next`/`page_of`) + visit-note author fallback, and the nurse composer copy (`notes_title`/`tasks_checklist_title`/`note_*`/`continuity_title`); shared enum labels (relation/gender/condition) are REUSED from `onboarding`/`patients`, never re-keyed; consumed by the E2 record page + `NurseVisitNotesPanel` + `VisitNoteCard`
|
||||
- `'tickets'` — the f14 messaging surface (tickets are the only post-booking channel): the inbox (`title`/`contact_support`/`empty_*`/`error_body`), the category + status labels keyed off the code (`category_{support,coordination,refund,emergency}`/`status_{open,closed}`), the linked-entity hints (`linked_booking`/`linked_refund` with `{id}`), `ref_code_label`, the new-ticket dialog (`new_ticket_title`/`category_label`/`subject_label`/`message_label`/`submit`/`created_*`/`view_thread`), the thread (`back_to_tickets`/`thread_*`/`closed_notice`), the composer (`sending`/`send`/`send_failed`/`composer_placeholder`), the author-role labels (`author_{customer,nurse,support,system}` — `admin`→support), and the **emergency playbook** (`emergency_title`/`emergency_body`/`emergency_call {name}`/`emergency_call_generic`/`emergency_open_ticket`) + `open_from_booking`; consumed by the ticket screens, `MessageBubble`/`TicketListCard`/`EmergencyBanner`/`ContactSupportDialog`/`MessageComposer`/`BookingSupportEntry`
|
||||
- `'notifications'` — the f14 notification center + bell: `title`, `empty_*`, `error_body`, `retry`, `mark_all_read`, `load_more`, and the polled-bell aria (`bell_aria` with `{count, number}`); the row `title`/`body` are **server-rendered** copy, not keys. Consumed by `NotificationCenter` + `NotificationBell`
|
||||
- `'auth'` — the phone-OTP login flow, role router, RoleGuard (loading/`account_error_*`/`guard_denied`), and SelectRole screen (`common.brand`/`brand_tagline` for the wordmark)
|
||||
- `'auth'` — the phone-OTP login flow, role router, RoleGuard (loading/`account_error_*`/`guard_denied`), and SelectRole screen (`common.brand`/`brand_tagline` for the wordmark); ui-phase-3 added the login-hero `trust_*` bullets, the consent line (`consent_line`, `t.rich` with `<terms>`/`<privacy>` tags), and select-role's `role_add_later_note`
|
||||
- `'legal'` — ui-phase-3's `/terms`/`/privacy` static pages: `terms_title`/`privacy_title`, `draft_banner` (the human/legal-review flag shown on-page), `terms_intro`/`privacy_intro`, and `terms_sections`/`privacy_sections` (arrays of `{title, body}` read via `t.raw`, not flat keys — the one namespace with structured JSON values). Consumed only by the two legal pages
|
||||
- `'admin'` — the f15 backoffice consoles: verification queue/case, refund panel, payout dashboard/detail, review moderation, config editor + change-history, holiday manager, support-alert board, audit viewer, admin ticket queue/thread, RBAC grid, and admin-side partner management. Includes the **Persian legal terms** (پروانه تأسیس / مسئول فنی / نماد اعتماد الکترونیکی) and the enum-label prefixes keyed off the stable code (`step_*`/`agg_*`/`atype_*`/`astatus_*`/`sev_*`/`htype_*`/`dtype_*`/`batch_status_*`/`pstatus_*`/`channel_*`/`rstatus_*`/`mstatus_*`/`center_state_*`/`role_*`/`tcat_*`/`tstatus_*`). Consumed by the `/admin/*` screens + the `@/components/admin` composites
|
||||
- `'partner'` — the f15 partner-center portal (a separate authz scope): center home/onboarding-state, sponsored nurses/bookings, and the merchant-of-record settlement/invoice view (سامانه مودیان, commission/VAT decomposition). Consumed by the `/partner/*` screens + `PartnerSettlementRow`
|
||||
|
||||
@@ -862,7 +870,11 @@ private-routes layout) hydrates `currentUser.roles` from `/me` — the single so
|
||||
on a 401 and retries the request once; a failed refresh (unknown/expired/reused token → the server revokes
|
||||
the session) clears tokens and redirects to `/login`. The refresh/OTP endpoints are excluded from this retry.
|
||||
|
||||
**Middleware** (`middleware.ts`) gates private routes with the same `isTokenAlive` helper before render.
|
||||
**Middleware** (`middleware.ts`) gates private routes with the same `isTokenAlive` helper before render. On
|
||||
redirect it appends the attempted locale-stripped path + query as `?next=` (`RETURN_URL_PARAM`) so a deep
|
||||
link survives the round trip; `LoginFlow` reads it and `RoleRouter` resolves it via
|
||||
`resolvePostLoginDestination` (`services/auth/routing.ts`) — same-origin-relative + role-permitting only,
|
||||
else it falls back to `resolveRoleDestination` (never an open redirect).
|
||||
|
||||
**Security posture — current limits and best-practice follow-ups.** The flow above is the intended
|
||||
client design, but some hardening needs *server* coordination — don't silently "fix" it client-only:
|
||||
|
||||
Reference in New Issue
Block a user