add build development phases
This commit is contained in:
@@ -0,0 +1,337 @@
|
||||
# Frontend Phase 7 — Booking request flow (customer request + nurse inbox)
|
||||
|
||||
> **Mission:** turn a nurse profile into a *sent request* and close the request loop on both sides. The
|
||||
> customer fills the **request form** (C4) — patient, service variant, address, date/time, and the
|
||||
> first-class **caregiver-gender preference** — and lands on the **awaiting-acceptance** screen (C5) with
|
||||
> a live countdown to the nurse's response deadline and a 3-step status tracker. The nurse opens an
|
||||
> **incoming-requests inbox**, sees a request showing *only* the customer's notes (two-stage clinical
|
||||
> disclosure), and accepts or rejects it. On accept, the customer flips to a 30-minute **payment-deadline**
|
||||
> countdown that hands off to checkout (f9). This is the money-free request phase — no payment, no booking
|
||||
> row yet — and it is where the platform's trust contract (same-gender match, deadlines, terminal states)
|
||||
> becomes visible to both actors.
|
||||
>
|
||||
> **Track:** frontend · **Depends on:** [frontend-phase-6-b7](./frontend-phase-6-b7.md) (discovery: search/results/nurse profile) · [frontend-phase-3-b4](./frontend-phase-3-b4.md) (addresses & map picker) · backend **b8** contract ([booking-requests.md](../../contracts/domains/booking-requests.md)) · **Unlocks:** [frontend-phase-8-b9](./frontend-phase-8-b9.md) (booking detail · sessions · EVV)
|
||||
> **Before you start, read [`../_shared/agent-operating-rules.md`](../_shared/agent-operating-rules.md).** It is not optional.
|
||||
|
||||
---
|
||||
|
||||
## 1. Context — where this sits
|
||||
|
||||
We are at the hinge of the customer journey: discovery is done (f6), the customer is looking at a nurse
|
||||
profile (C3) and taps **درخواست رزرو**. This phase builds the *request phase* of the booking lifecycle —
|
||||
the deliberately money-free intent that lives in `booking_requests` and **only** becomes a `bookings` row
|
||||
later, after the nurse accepts *and* payment captures (f9 / b9). Nothing here touches money or creates a
|
||||
booking. The product framing: a family requests a specific nurse for a specific patient at a specific
|
||||
address and time, the nurse retains accept/reject autonomy (a deliberate worker-classification stance),
|
||||
and both sides see frozen deadlines so the engagement can't hang forever.
|
||||
|
||||
**What already exists (do not rebuild):**
|
||||
|
||||
- **f0 foundations** ([frontend-phase-0](./frontend-phase-0.md)): the three actor shells (customer
|
||||
mobile + 5-tab bottom nav, nurse shell, admin), the `services/{domain}` + TanStack Query caching
|
||||
pattern (template = `src/services/auth/*`: `types.ts` / `keys.ts` / `apis/clientApi.ts` /
|
||||
`hooks/use*.ts` / `index.ts`), `clientFetch`/`serverFetch` + `ApiError` (`@/lib/api`), the
|
||||
contracts→types convention, the money/Shamsi format utils in `src/utils/`, and the shared composites
|
||||
(stepper/progress header, status chip, OTP/phone inputs). **Reuse these — do not re-create the
|
||||
pattern.**
|
||||
- **f3 addresses** ([frontend-phase-3-b4](./frontend-phase-3-b4.md)): the customer **address book**,
|
||||
the **map picker**, and the cascading province/city/district selectors, all behind
|
||||
`services/addresses` (or the f3 domain name). The request form **reuses** the address picker/list — it
|
||||
does not build a new one. Patient `customer_addresses` already carry coordinates from f3's geocode.
|
||||
- **f2 onboarding** ([frontend-phase-2-b3](./frontend-phase-2-b3.md)): the **patient** list/CRUD behind
|
||||
`services/patients`. The request form's patient selector **reads** that list; it does not add a new
|
||||
patient-creation path (link out to the f2 "add patient" flow for the empty case).
|
||||
- **f4 catalog** ([frontend-phase-4-b5](./frontend-phase-4-b5.md)): a nurse's **service variants**
|
||||
(`nurse_service_variants` — name, price unit `per_hour`/`per_session`/`per_half_day`/`per_day`/`per_24h`,
|
||||
price). The service-type selector **reads** the chosen nurse's published variants.
|
||||
- **f6 discovery** ([frontend-phase-6-b7](./frontend-phase-6-b7.md)): search (C1), results (C2), and the
|
||||
**nurse profile (C3)** with its **درخواست رزرو** CTA. This phase is the destination of that CTA — wire
|
||||
the navigation from C3 into the request form, passing the `nurse_id` (and optionally a pre-selected
|
||||
variant).
|
||||
|
||||
> **Money/booking note:** there is **no payment and no `bookings` row** in this phase. The "pay & confirm"
|
||||
> step (C6 summary, escrow notice, card/BNPL) is **(DEFERRED → [f9](./frontend-phase-9-b10.md))**. Booking
|
||||
> detail, sessions, and EVV are **(DEFERRED → [f8](./frontend-phase-8-b9.md))**. Build only up to the two
|
||||
> countdowns (response deadline, then payment deadline) and the hand-off CTA into checkout.
|
||||
|
||||
## 2. Required reading (do this first)
|
||||
|
||||
- [`../_shared/agent-operating-rules.md`](../_shared/agent-operating-rules.md) and
|
||||
[`../_shared/frontend-conventions-checklist.md`](../_shared/frontend-conventions-checklist.md).
|
||||
- [`client/CLAUDE.md`](../../../client/CLAUDE.md) in full — the RSC/client boundary, layouts (never above
|
||||
`[locale]`), i18n, theme/tokens, cookies, the `services/{domain}` fetch pattern, anti-patterns. Mirror
|
||||
the **`auth`** service exactly when you create `services/bookingRequests`.
|
||||
- **Invoke the `frontend-designer` skill** — mandatory for all visual work in this phase (C4 form, C5
|
||||
awaiting screen + tracker, the nurse inbox list + detail). It is the brand/design contract: palette
|
||||
(teal `#1d4a40`, terracotta `#d98c6a`, cream), tokens, typography, the `App*` library, the layout
|
||||
shells, and the hard RTL/dark-mode rules. Do not hand-pick colours in `sx`.
|
||||
- [`product/wireframes/index.html`](../../../product/wireframes/index.html) — the visual baseline. Study
|
||||
**C3 → C4 → C5** and the nurse "نمای پرستار" framing. The screens this phase implements:
|
||||
- **C4 · فرم درخواست** — patient selector (dropdown), service-type selector, address (map block, منزل),
|
||||
date + time pickers, nurse-gender preference (**خانم / آقا / فرقی ندارد**). CTA: **ارسال درخواست**.
|
||||
- **C5 · در انتظار تایید پرستار** — ⏳ status, "درخواست برای پرستار ارسال شد"; a summary card (nurse +
|
||||
time); the 3-step tracker **درخواست ثبت شد → در انتظار تایید پرستار → پرداخت و تایید نهایی**; a
|
||||
countdown to `nurse_response_deadline_at`. No CTA in the waiting state; on accept it shows the
|
||||
payment-deadline countdown + a "continue to payment" CTA.
|
||||
- **Nurse request inbox** — there is no dedicated wireframe panel, so design it consistently with the
|
||||
nurse shell: a list of pending requests each with a per-request countdown, and a request-detail
|
||||
showing **only** `customer_notes`, with accept / reject (reason) actions.
|
||||
- [`product/business/05-booking-and-scheduling.md`](../../../product/business/05-booking-and-scheduling.md)
|
||||
— the request→accept→pay→confirm lifecycle, the frozen deadlines, the two-table split, same-gender
|
||||
matching, and the two-stage clinical-disclosure rule. **These are decisions, not guesses — read them.**
|
||||
- **The contract you consume:** [`../../contracts/domains/booking-requests.md`](../../contracts/domains/booking-requests.md)
|
||||
(from **backend-phase-8**) — the exact request/response shapes, routes, status codes, and enums.
|
||||
Plus the shared conventions [`api-conventions.md`](../../contracts/conventions/api-conventions.md) and
|
||||
[`money-and-types.md`](../../contracts/conventions/money-and-types.md) (envelope, snake_case routes,
|
||||
pagination, enums-as-codes, UTC timestamps + Shamsi display, IRR-as-string, gender as load-bearing).
|
||||
- The latest backend handoff `dev/shared-working-context/backend/handoff/after-backend-phase-8.md` — what
|
||||
b8 shipped, which endpoints are live, and what (if anything) is still mocked server-side.
|
||||
- The f6/f3 frontend reports in `dev/shared-working-context/reports/` — to reuse the patient/address/
|
||||
variant query keys and the discovery navigation rather than re-fetching or re-deriving them.
|
||||
|
||||
## 3. Scope — build this
|
||||
|
||||
### 3.1 The `services/bookingRequests` domain (consume b8)
|
||||
|
||||
Create `src/services/bookingRequests/` by copying the `auth` template structure exactly:
|
||||
|
||||
- **`types.ts`** — string-literal union types mirroring the **booking-requests.md** contract (do **not**
|
||||
guess shapes). At minimum:
|
||||
- `RequiredCaregiverGender = 'male' | 'female' | 'any'` (the wire codes behind خانم/آقا/فرقی ندارد).
|
||||
- `BookingRequestStatus = 'pending_nurse_response' | 'accepted_awaiting_payment' | 'rejected_by_nurse'
|
||||
| 'expired_no_response' | 'payment_deadline_expired' | 'converted'`.
|
||||
- `BookingRequestDto` (id, `nurse_id`, `patient_id`, `nurse_service_variant_id`, `customer_address_id`,
|
||||
`scheduled_start_at` (UTC), `required_caregiver_gender`, `customer_notes`, `status`,
|
||||
`nurse_response_deadline_at` (UTC), `payment_deadline_at` (UTC, nullable until accept),
|
||||
`nurse_rejection_reason` (nullable), plus the display fields the contract returns — nurse name/avatar,
|
||||
variant name + price-unit, patient name, address label). Money values (variant price) are **IRR digit
|
||||
strings**, parsed via the f0 money util — never floats.
|
||||
- `CreateBookingRequestPayload`, `RejectBookingRequestPayload` (reason).
|
||||
- **`keys.ts`** — a query-key factory: `bookingRequestKeys.lists(role, statusFilter)`,
|
||||
`bookingRequestKeys.detail(id)`, and the **nurse inbox** list key `bookingRequestKeys.nurseInbox(filter)`.
|
||||
- **`apis/clientApi.ts`** — wrap `clientFetch` for each endpoint the contract defines (names from
|
||||
**booking-requests.md** — expected, snake_cased per api-conventions):
|
||||
- `POST .../booking_requests/create_booking_request` → create (customer).
|
||||
- `GET .../booking_requests/list_booking_requests` → customer's requests, paginated, `status` filter.
|
||||
- `GET .../booking_requests/get_booking_request` → one request (polled on C5).
|
||||
- `GET .../booking_requests/list_nurse_requests` (or the contract's nurse-inbox route) → nurse's
|
||||
incoming requests, paginated, default `status=pending_nurse_response`.
|
||||
- `POST .../booking_requests/accept_booking_request` → nurse accept.
|
||||
- `POST .../booking_requests/reject_booking_request` → nurse reject (with `nurse_rejection_reason`).
|
||||
- **`hooks/` — one hook per file:**
|
||||
- `useCreateBookingRequest.ts` (`useMutation`) — on success, navigate to C5 with the new id and
|
||||
`setQueryData`/invalidate the customer list.
|
||||
- `useBookingRequest.ts` (`useQuery`) — the C5 detail; **polls** while status is non-terminal
|
||||
(`refetchInterval` ~ every 15–30s while `pending_nurse_response` / `accepted_awaiting_payment`, and
|
||||
**stops** on a terminal/`converted` status via a `select`/enabled guard) so the customer sees the
|
||||
accept/reject/expire transition without a refresh.
|
||||
- `useNurseRequestInbox.ts` (`useQuery`) — the nurse list, with light polling for new requests.
|
||||
- `useAcceptBookingRequest.ts` / `useRejectBookingRequest.ts` (`useMutation`) — **invalidate the nurse
|
||||
inbox list and the request detail** on success so the request leaves the pending list immediately.
|
||||
- **`index.ts`** — barrel.
|
||||
|
||||
> If any shape the screens need is **missing** from booking-requests.md (e.g. the contract doesn't return
|
||||
> the nurse's display name on the request DTO, or omits the price-unit), **append the gap** to
|
||||
> `dev/shared-working-context/frontend/requests/for-backend.md` and **mock that field behind the
|
||||
> `services/bookingRequests` clientApi seam** meanwhile (operating-rules §6). Record the mock in your
|
||||
> report so it swaps out cleanly when b8 fills the gap. Never edit backend files.
|
||||
|
||||
### 3.2 C4 — the request form (customer)
|
||||
|
||||
A page under the customer shell (e.g. `(private-routes)/<customer-segment>/booking-requests/new`,
|
||||
reachable from the C3 **درخواست رزرو** CTA with the `nurse_id`). RTL-first, mobile. Fields:
|
||||
|
||||
- **Patient selector** — a dropdown reading `services/patients` (f2). Empty state → a CTA linking to the
|
||||
f2 "add patient" flow (don't inline patient creation here). The selected `patient_id` is sent.
|
||||
- **Service-type selector** — reads the chosen nurse's `nurse_service_variants` (f4). Each option shows
|
||||
the variant name + formatted price + price-unit label (i18n key off the `per_*` code, **not** a
|
||||
hardcoded label). Sends `nurse_service_variant_id`.
|
||||
- **Address block** — **reuse the f3 address picker / map block** (منزل/home), selecting a
|
||||
`customer_address_id` from the address book (with the map preview). Do not rebuild the picker.
|
||||
- **Date + time pickers** — produce a single UTC `scheduled_start_at` on the wire; **display** Shamsi via
|
||||
the f0 date util. (Recurring/multi-session scheduling UI is **(DEFERRED → later)** — one start time here;
|
||||
`session_count` is a server/booking concern.)
|
||||
- **Nurse-gender preference** — a 3-option segmented control: **خانم (female) / آقا (male) / فرقی ندارد
|
||||
(any)** → `required_caregiver_gender`. This is a **first-class field**, never a hidden default; if the
|
||||
nurse's profile already fixes a gender, still send the explicit code the customer chose.
|
||||
- **Request-stage notes** — a free-text field mapped to `customer_notes`. Copy must make clear this is the
|
||||
*only* thing the nurse sees before accepting (it is **not** the clinical care record, which is
|
||||
post-confirmation and **(DEFERRED → [f8](./frontend-phase-8-b9.md))**).
|
||||
- **CTA: ارسال درخواست** — fires `useCreateBookingRequest`; loading state while the server computes/freezes
|
||||
the deadline; on success navigate to C5. Surface domain `400`s (e.g. tenancy: patient/address not owned;
|
||||
same-gender mismatch; variant not bookable) as field/form errors — but do **not** toast `401/403/5xx`
|
||||
(the fetch layer already does).
|
||||
|
||||
Validate client-side at the boundary (all required fields chosen, future date) before enabling the CTA;
|
||||
the server re-validates and is authoritative.
|
||||
|
||||
### 3.3 C5 — awaiting nurse acceptance + status tracker (customer)
|
||||
|
||||
A page keyed by the request id (e.g. `.../booking-requests/[id]`). Uses `useBookingRequest` (polling).
|
||||
|
||||
- **Header:** ⏳ "درخواست برای پرستار ارسال شد".
|
||||
- **Summary card** — nurse (avatar + name), patient, service variant + price, address label, requested
|
||||
time (Shamsi). Compose this as a **shared composite** (`src/components/...`) so the booking-detail screen
|
||||
in f8 can reuse it (a "BookingRequestSummaryCard"); co-locate a `*.test.tsx`.
|
||||
- **3-step status tracker** — reuse the **f0 stepper/progress header** composite:
|
||||
1. **درخواست ثبت شد** (done as soon as the request exists),
|
||||
2. **در انتظار تایید پرستار** (active while `pending_nurse_response`),
|
||||
3. **پرداخت و تایید نهایی** (future; becomes active on `accepted_awaiting_payment`).
|
||||
- **Countdown** — a `CountdownTimer` shared composite (`src/components/...`, co-located test) ticking down
|
||||
to `nurse_response_deadline_at` (computed from the **server-supplied UTC instant** vs `Date.now()` — the
|
||||
client never computes the deadline, only renders it). It is a pure presentational countdown; when it hits
|
||||
zero, the UI shows "in expectation of server confirmation" and the poll resolves the real terminal
|
||||
status. Use a single interval, cleaned up on unmount; do **not** re-render the whole page each tick
|
||||
(isolate the ticking state in the timer component).
|
||||
- **State transitions (driven by polled `status`):**
|
||||
- `accepted_awaiting_payment` → swap step 2 to done, step 3 active; show **"✓ پرستار تایید کرد"**, a
|
||||
prominent **30-minute payment countdown** to `payment_deadline_at`, and a CTA **"ادامه پرداخت ←"** that
|
||||
navigates to checkout (**the checkout screen itself is [f9](./frontend-phase-9-b10.md) — wire the
|
||||
route, stub the destination if f9 isn't merged**).
|
||||
- `rejected_by_nurse` → terminal state card with the `nurse_rejection_reason` and a "request another
|
||||
nurse" CTA back into discovery (f6).
|
||||
- `expired_no_response` → terminal "no response in time" card + re-request CTA.
|
||||
- `payment_deadline_expired` → terminal "payment window lapsed" card + re-request CTA.
|
||||
- `converted` → the request became a booking → route to booking detail (**[f8](./frontend-phase-8-b9.md)**;
|
||||
stub if not merged).
|
||||
|
||||
### 3.4 Nurse request inbox + detail (nurse)
|
||||
|
||||
Under the **nurse shell** (the wireframe's "نمای پرستار"), e.g. `(private-routes)/<nurse-segment>/requests`.
|
||||
|
||||
- **Inbox list** (`useNurseRequestInbox`) — pending requests, each row a card: patient first name/age,
|
||||
service variant, requested time (Shamsi), the **required-caregiver-gender** chip, and a **per-request
|
||||
countdown** to that request's `nurse_response_deadline_at`. Empty state: "درخواست جدیدی ندارید". Paginated
|
||||
(page/page_size per api-conventions). Light polling so new requests appear.
|
||||
- **Request detail** — shows the request summary **and only `customer_notes`** as the clinical context.
|
||||
**It must never render `booking_care_instructions` or any encrypted clinical field** — those don't exist
|
||||
pre-accept and are out of this contract; rendering them would break two-stage disclosure. Actions:
|
||||
- **Accept** (`useAcceptBookingRequest`) — on success the request moves to `accepted_awaiting_payment`,
|
||||
a `payment_deadline_at` is set server-side, and the customer's C5 (via its poll) starts the 30-min
|
||||
payment countdown. Invalidate the inbox list + this detail.
|
||||
- **Reject** (`useRejectBookingRequest`) — a small reason dialog capturing `nurse_rejection_reason`;
|
||||
on success the request leaves the inbox. Invalidate the inbox list + detail.
|
||||
- Both actions are disabled / show a terminal banner if the request already expired (the server returns
|
||||
`409`/`400` for a stale accept/reject — surface it gracefully, then refetch).
|
||||
|
||||
### 3.5 i18n + tokens
|
||||
|
||||
Add a **`booking`** (and/or `bookingRequests`) namespace to **both** `messages/en.json` and
|
||||
`messages/fa.json`, in sync, RTL-first. Every visible string is a key — the gender labels (خانم/آقا/فرقی
|
||||
ندارد), the three tracker steps, the price-unit labels (off the `per_*` codes), all terminal-state copy,
|
||||
countdown labels, and the empty states. Colours from `tokens.css` only; financial/terracotta accent (e.g.
|
||||
the payment-deadline countdown) uses the brand terracotta token, not a literal.
|
||||
|
||||
## 4. Mocks & seams in this phase
|
||||
|
||||
This phase **introduces no new external seam** — booking requests carry **no money** and call no third
|
||||
party. It only consumes the b8 HTTP contract.
|
||||
|
||||
- **Backend-not-ready / contract-gap fallback:** if `after-backend-phase-8.md` shows b8 isn't merged, or
|
||||
booking-requests.md is missing a shape, build a **mock `clientApi`** behind the `services/bookingRequests`
|
||||
seam (same function signatures the real one will have), driving a small in-memory state machine so the
|
||||
whole flow is demoable: create → (timer or manual) accept/reject/expire. Record it in
|
||||
[`mocks-registry.md`](../../shared-working-context/reports/mocks-registry.md) and your report; swapping
|
||||
to the real `clientApi` must be a one-file change. File any contract gap in
|
||||
`dev/shared-working-context/frontend/requests/for-backend.md` (never edit backend files).
|
||||
- **Reused seams:** the patient list (`services/patients`, f2), the address picker (`services/addresses`,
|
||||
f3), and the nurse variants (`services/catalog` or f4's name) — **reuse**, do not redefine.
|
||||
|
||||
## 5. Critical rules you must not get wrong
|
||||
|
||||
- **Two-stage clinical disclosure.** The nurse sees **only** `customer_notes` before accepting — never any
|
||||
encrypted `booking_care_instructions` or other clinical detail. That data isn't in this contract and must
|
||||
not appear anywhere in the inbox/detail UI. Full care instructions are post-confirmation and belong to
|
||||
[f8](./frontend-phase-8-b9.md).
|
||||
- **`required_caregiver_gender` is a first-class field.** Always sent explicitly (`male`/`female`/`any`),
|
||||
never defaulted or dropped — it drives same-gender bodily-care matching. The server re-validates; surface
|
||||
a mismatch `400` clearly.
|
||||
- **No money, no booking row here.** This is the request phase. Do not render a price-breakdown/escrow/pay
|
||||
step (that's C6 / [f9](./frontend-phase-9-b10.md)) and do not assume a booking exists until `converted`.
|
||||
- **Deadlines come from the server, frozen.** Render countdowns from the server-supplied UTC instants
|
||||
(`nurse_response_deadline_at`, `payment_deadline_at`) against `Date.now()`; the client **never computes
|
||||
or recomputes** a deadline. Show the **response** countdown pre-accept and the **30-minute payment**
|
||||
countdown post-accept; show the correct **terminal** state (`rejected_by_nurse` / `expired_no_response`
|
||||
/ `payment_deadline_expired`) when the poll resolves it.
|
||||
- **Invalidate on accept/reject.** A nurse action must invalidate the inbox list + the request detail so
|
||||
the request leaves the pending list immediately and the customer's polled C5 reflects it — never leave
|
||||
stale cache. Equally, don't over-poll: stop polling once a terminal/`converted` status is reached.
|
||||
- **Minimal re-renders.** The ticking countdown state is isolated in the timer component (not lifted to a
|
||||
page that would re-render the form/summary every second). Stable query keys, `select` for slices.
|
||||
- **RTL + both locales + tokens + MUI primitives.** `fa` default & RTL; every string in `en.json` and
|
||||
`fa.json` in sync; colours from `tokens.css`; MUI v9 primitives/`App*` reused, shared composites
|
||||
(summary card, countdown) at the shared level with co-located tests — never re-implement a root primitive
|
||||
and never bury a reusable composite in a page.
|
||||
|
||||
## 6. Definition of Done
|
||||
|
||||
The shared [definition-of-done.md](../_shared/definition-of-done.md), plus:
|
||||
|
||||
- [ ] `services/bookingRequests` exists (types/keys/apis/hooks/index), typed **from** booking-requests.md
|
||||
(gaps filed + mocked behind the seam, not guessed).
|
||||
- [ ] **C4 request form** submits a valid request (patient + variant + address + date/time + gender +
|
||||
notes) and navigates to C5; client-side validation gates the CTA; domain `400`s surface as
|
||||
form/field errors.
|
||||
- [ ] **C5 awaiting screen** shows the summary card, the 3-step tracker, and a live countdown to
|
||||
`nurse_response_deadline_at`; it transitions (via poll) through accept (→ 30-min payment countdown +
|
||||
checkout CTA), reject, and both expiry terminal states.
|
||||
- [ ] **Nurse inbox** lists pending requests (with per-request countdown + gender chip), the detail shows
|
||||
**only `customer_notes`**, and accept/reject work and **invalidate the inbox + detail**.
|
||||
- [ ] Polling stops on terminal/`converted` status; no needless refetch; the ticking countdown doesn't
|
||||
re-render the whole page.
|
||||
- [ ] `booking`/`bookingRequests` i18n keys added to **both** locales in sync; colours from tokens; RTL
|
||||
verified.
|
||||
- [ ] `npm run check` green; `npm run test:ci` green for the new shared composites (summary card,
|
||||
countdown timer) and any touched shared component.
|
||||
- [ ] `client/CLAUDE.md` *Project Structure* updated for the new route segments + the `services/bookingRequests`
|
||||
domain and any new shared components.
|
||||
|
||||
## 7. How to test (what a human can verify after this phase)
|
||||
|
||||
Run `npm run dev` (point `NEXT_PUBLIC_API_URL` at a b8 server, or use the mock `clientApi` seam if b8
|
||||
isn't merged). Then:
|
||||
|
||||
1. **Submit a request.** From a nurse profile (C3) tap **درخواست رزرو** → on C4 pick a patient, a service
|
||||
variant, an address (map block), a future date/time, and a gender preference (خانم/آقا/فرقی ندارد),
|
||||
add a note → **ارسال درخواست**. *Expected:* land on **C5** showing the summary card, the 3-step tracker
|
||||
(step 2 active), and a countdown ticking down to the nurse's response deadline.
|
||||
2. **Nurse sees it.** In the nurse shell open **requests** (inbox). *Expected:* the new request appears
|
||||
with the patient/variant/time, the **required-gender chip**, and a per-request countdown; opening the
|
||||
detail shows **only the `customer_notes`** — no clinical/care fields anywhere.
|
||||
3. **Nurse accepts.** Tap accept. *Expected:* the request leaves the pending inbox immediately
|
||||
(cache invalidated); on the customer's **C5** (without a manual refresh, via the poll) step 2 flips to
|
||||
done, step 3 activates, a **✓ پرستار تایید کرد** badge appears, the **30-minute payment countdown**
|
||||
starts, and the **ادامه پرداخت ←** CTA appears (routing toward checkout/f9).
|
||||
4. **Reject path.** On a different request, nurse rejects with a reason. *Expected:* customer's C5 shows the
|
||||
terminal **rejected** card with the reason + a re-request CTA back into discovery.
|
||||
5. **Expiry paths.** Let (or simulate via the mock) the response deadline lapse → C5 shows
|
||||
**expired_no_response**; let the payment window lapse after accept → C5 shows **payment_deadline_expired**.
|
||||
Both are terminal with a re-request CTA.
|
||||
6. **Quality:** locale switch flips `dir` + strings on every screen; dark mode holds; `npm run check` and
|
||||
`npm run test:ci` pass; React Query Devtools shows the inbox/detail invalidating on accept/reject and the
|
||||
poll stopping at a terminal status.
|
||||
|
||||
## 8. Hand off & document (close the phase)
|
||||
|
||||
- **Docs:** update `client/CLAUDE.md` *Project Structure* (the new customer + nurse route segments, the
|
||||
`services/bookingRequests` domain, the new shared composites — `BookingRequestSummaryCard`,
|
||||
`CountdownTimer`). Fix any doc drift you touch. If you discover/decide a request-flow rule the
|
||||
`product/` docs don't capture (e.g. the exact tracker wording, the re-request UX), note it in
|
||||
[`product/business/05-booking-and-scheduling.md`](../../../product/business/05-booking-and-scheduling.md)
|
||||
(don't invent rules — record decisions) and regenerate the HTML view per `product/CLAUDE.md` if you
|
||||
edited Markdown.
|
||||
- **Contracts:** this phase **consumes** [`../../contracts/domains/booking-requests.md`](../../contracts/domains/booking-requests.md)
|
||||
— derive `services/bookingRequests/types.ts` from it; produce no contract. Append any missing/ambiguous
|
||||
shape to `dev/shared-working-context/frontend/requests/for-backend.md`.
|
||||
- **Handoff & report:** append your phase summary to
|
||||
`dev/shared-working-context/frontend/STATUS.md`; write
|
||||
`dev/shared-working-context/reports/frontend-phase-7-report.md` covering *what was built* (C4/C5/nurse
|
||||
inbox + the domain service), *what is now testable and exactly how* (the steps in §7), *what is mocked*
|
||||
(any contract-gap field behind the `services/bookingRequests` seam + how it swaps to real), *contracts
|
||||
consumed* (booking-requests.md) and any gaps filed, and *follow-ups* for f8/f9 (the `converted` → booking
|
||||
detail handoff, the payment CTA → checkout handoff). Update
|
||||
[`mocks-registry.md`](../../shared-working-context/reports/mocks-registry.md) only if you added a
|
||||
client-side mock seam.
|
||||
- **Memory:** save a `project` memory note for the non-obvious decisions here — the dual-countdown design
|
||||
(server-frozen deadlines, client renders only), the polling-until-terminal pattern for request status,
|
||||
and the two-stage-disclosure boundary in the nurse inbox — with a one-line pointer in `MEMORY.md`.
|
||||
Reference in New Issue
Block a user