10 KiB
Frontend Phase 15 (b15) — Admin backoffice & partner-center consoles — report
Date: 2026-07-10 · Track: frontend · Status: ✅ complete — this is the final frontend phase; MVP is complete.
Gate: npm run check green (0 errors / 0 warnings) · npm run test:ci green (325 tests, +36) · npm run build green (all 18 new routes compile) · admin/partner i18n in sync · i18n key audit clean.
1. What was built
The internal operational cockpit that runs the marketplace: a role-gated admin backoffice in the f0 desktop
sidebar shell, plus a separately-scoped partner-center portal. All of it is a read-and-act surface over data
other domains own — the client renders the contract's values and issues the sanctioned commands; it never
computes eligibility, money decomposition, holiday shifts, the is_verified flip, or the review aggregate (§5).
Two new domain services (the auth-service shape)
services/admin— config, holidays, audit, support-alerts, RBAC. 12 hooks; filters + page in every key so each worklist filter/page caches independently. Mock-primary.services/partnerCenter— admin-side center management + the center-scoped portal reads. 12 hooks. Mock-primary.
Admin-endpoint additions to 5 existing domains (the staff lens — not new domains)
- verification (b6):
useVerificationQueue/useVerificationCase/useVerificationDocumentUrl(on-demand signed URL) /useDecideStep/useApproveVerification/useRejectVerification. - refunds (b11):
useRefundPreview/useInitiateRefund/useApproveRefund/useRejectRefund(ticket-linked). - payouts (b13):
usePayoutBatches/usePayoutBatchDetail/usePreviewPayoutBatch/useRunPayoutBatch(idempotency-keyed) /useRetryPayout/useRecordTransferReference. - reviews (b14):
useModerationQueue/useModerateReview. - tickets (b15):
useAdminTickets/useAdminTicket/useAdminTicketThread/usePostAdminMessage. The admin ticket types carryisInternal; the user-app types deliberately do not — the two surfaces stay distinct so an internal note can never bleed into a user view (§5).
Shared composites — @/components/admin (13, each with a co-located *.test.tsx; 36 tests)
AdminPageHeader, AdminEmptyState, AdminErrorState, AdminPager, ConfirmDialog (optional required-reason),
AdminDataTable (generic dense worklist table, horizontal-scroll-contained), ConfigRow, AuditLogRow (expandable
diff), SupportAlertCard, PartnerSettlementRow (reconciling commission/VAT breakdown via the f0 PriceBreakdown),
DocumentViewer (signed-URL on-demand + expired→re-request), RefundPanel (server-computed preview → initiate →
provider-failure retry / reject), AdminMessageBubble (internal-note styled distinctly).
Screens (18 new routes)
- Admin (
/admin/*, role-gated): overview landing · verification (queue +[nurseId]case) · tickets (queue +[id]thread w/ internal-note composer + RefundPanel) · payouts (dashboard +[batchId]detail) · reviews (moderation) · config · holidays · alerts · audit · partners (list +[id]detail) · roles (DEFERRED-IF-MISSING). - Partner portal (
/partner/*, separate scope): home · nurses · bookings · settlement (MoR-gated).
Foundation
useAdminCapabilities()(@/hooks) — a memoized selector over the session's fine-grainedroleCodes(added toSessionUser, hydrated from/mebyuseSessionRoleSync). Drives per-consolecan*booleans that hide/disable controls a role can't use (server still authorizes every command).PartnerLayout+ the(private-routes)/partner/route group (a separate authz scope).- The
admin+partneri18n namespaces in both locales (incl. پروانه تأسیس / مسئول فنی / نماد اعتماد الکترونیکی / سامانه مودیان and the enum-label prefixes keyed off stable codes). - New
AppIconregistry entries; newROUTES.ADMIN_*/ROUTES.PARTNER_*+ path helpers.
2. What is now testable, and exactly how
Run npm run dev, sign in as an admin (mock-primary, so no live backend needed). The §7 acceptance steps:
- Verify a nurse.
/admin/verification→ pick a pending nurse → the case loads each document via a signed URL (DocumentViewer; sentineldocumentId 9999exercises the error→re-request path). Pass the steps (reject one with a required reason), enter a structured credential on a credential-bearing step; Approve is disabled until every step ispassed→ confirm → the nurse leaves the queue. The client never wroteis_verified. - Process a refund.
/admin/tickets→ open the refund ticket (bookingId + refundId seeded) → RefundPanel shows the server-computed tiered % + fee/payout decomposition (reconciles to the rial) + channel + BNPL ETA; the post-payout booking shows the read-only clawback notice; the decline sentinel shows retry. - Preview + run a payout batch.
/admin/payouts→ preview next batch → eligibility breakdown (eligible + no-IBAN-skipped + clawback line + holiday-shifted processing date) → run batch (confirm requires an idempotency key — a double-click can't double-pay) →processing.[batchId]detail → a failed payout → retry; reconcile atransfer_reference. - Moderate a review.
/admin/reviews→ apending_moderationreview (low-rating flag) → publish/hide/reject (reject/hide require a reason) → it leaves the queue; the client never computed the aggregate. - Edit a config value.
/admin/config→ editvat_rate→ the input validates 0–1 (1.5blocked) → save → the dialog states it is audited, immediate, non-retroactive → the history drawer shows old→new + actor + Shamsi time. - Resolve a support alert.
/admin/alerts→ filter to open → assign to me → resolve with a note. It appears in no customer/nurse/partner view. - Holiday manager.
/admin/holidays→ add a holiday withis_bank_closedon → listed (the client never computes the shift; the payout preview in step 3 shows the server-shifted date). - Audit viewer.
/admin/audit→ filter by entity/actor/date → paginated; expand a row for thechangedFieldsdiff. No edit/delete control exists. - Partner portal. Sign in mapping to a center admin →
/partnershows onboarding state + license + MoR;/partner/nurses+/partner/bookingsshow only that center's data;/partner/settlementrenders the commission/VAT invoices + signed-URL PDF + masked IBAN only when merchant-of-record (a non-MoR center shows the "via Balinyaar" state). Back as an admin →/admin/partners→ create/verify/activate + assign a nurse; IBAN is write-then-masked (submit full, only last-4 ever shows). - RBAC + i18n + RTL + caching. A
supportrole sees no payout/refund controls; amoderationrole sees only moderation (drive viauseAdminCapabilities).fa↔entranslates every label (incl. the Persian legal terms) and the sidebar mirrors. Switching worklist filters/pages caches separately (filters+page in the key) — no refetch of loaded data.
3. What is mocked, and how to make it real
All f15 data is mock-primary (the real clientApi in each domain maps the live routes and targets proposed slugs
for the gaps; the swap is a one-line flag flip per domain — no hook/component change). Full detail in
reports/mocks-registry.md (three new/updated rows). Seams + flags:
services/admin—USE_ADMIN_MOCK(config/holidays/audit/support-alerts live at b1; RBAC + config audit columns + rich audit filters are gaps → REQ-029/030/031).services/partnerCenter—USE_PARTNER_MOCK(admin CRUD/verify/sponsor live at b15; portal split reads + activate/suspend + invoice total are gaps → REQ-032/033).- verification/refunds/payouts/reviews/tickets admin methods — the owning domain's flag (REQ-034/035/036/037).
No backend seams were introduced — IObjectStorage/IBankTransferProvider/IBnplProvider/IMoadianClient/
ILicenseVerificationService/the audit interceptor/the notification dispatcher are all server-side (b1/b6/b11/b13/b14/b15);
the frontend never touches them.
4. Contracts consumed
messaging-notifications-admin.md (b15, primary) + verification.md (b6) + refunds-invoices.md (b11) +
payouts.md (b13) + reviews-records.md (b14) + config-reference.md (b1). All types derived from these — none guessed.
5. Requests filed (frontend/requests/for-backend.md)
REQ-029 config updatedAt/updatedBy · REQ-030 audit actor/action/date filters · REQ-031 RBAC role endpoints ·
REQ-032 partner-portal split reads + activate/suspend + IBAN write-then-masked · REQ-033 center-scoped invoice list
- invoice
totalIrr· REQ-034 verification nurse-level queue + on-demand doc URL + whole-verification approve/reject · REQ-035 refund preview + explicit approve/reject · REQ-036 payout single-preview +holidayShifted+ record-transfer-reference · REQ-037 moderationtagCodes.
6. MVP-complete closeout
This is the final frontend phase — every customer, nurse, admin, and partner surface in the build plan now exists.
- Deferred-if-missing: the RBAC roles console (
/admin/roles) is built against the mock and tagged DEFERRED-IF-MISSING; it activates when the b15 role endpoints land (REQ-031). It is not on the testable acceptance path. - Modeled-but-inactive, no UI (as specified):
organizations/organization_nurses(employer model),fraud_flags(ML console),recurring_booking_schedules(recurrence) — data-model/13. Also no UI for: full سامانه مودیان e-invoice automation / digital-signature (the portal only views the issued invoice ref + PDF), push/SMS channels, an analytics-warehouse dashboard, on-demand/instant payout, per-nurse payout-frequency settings. - Minor i18n substitutions the screen agents flagged (all use existing keys; cosmetic, non-blocking): the payout
retry-success toast reuses
admin.saved; the partner nurse verified/unverified column borrowscenter_state_*labels; partner booking-status renders the raw stable status code. Add dedicated keys later if desired.