217 lines
9.5 KiB
Markdown
217 lines
9.5 KiB
Markdown
# Frontend Technical Specs and Requirements (MVP)
|
|
|
|
## Purpose
|
|
Define the implementation contract for the React frontend against the current backend REST API so the customer flows (auth, search, booking, payment, profile) can ship reliably for KSA.
|
|
|
|
## Scope
|
|
In scope:
|
|
- Customer web app (React + Vite) for Phase 1 flows.
|
|
- API integration contracts for current backend endpoints.
|
|
- UX/error/loading behavior and test requirements.
|
|
- i18n/RTL and KSA timezone handling.
|
|
|
|
Out of scope (Phase 2+):
|
|
- Manager/staff admin dashboards.
|
|
- Advanced reporting/reviews moderation tools.
|
|
- Full observability product dashboards.
|
|
|
|
## Product and Platform Constraints
|
|
- Market default: KSA first.
|
|
- Default locale: `ar-sa`; fallback: `en`.
|
|
- Timezone baseline: `Asia/Riyadh` (`+03:00`).
|
|
- API error contract: HTTP status + `detail` where applicable.
|
|
- Booking/payment operations must avoid duplicate side effects.
|
|
|
|
## UX Priorities (Primary Contract)
|
|
- First screen for authenticated and guest users MUST be a feed of nearby/available salons.
|
|
- The feed is the default home experience and primary entry to booking.
|
|
- Main customer navigation MUST be bottom tabs (mobile-first), not header-first navigation.
|
|
- Bottom tabs MUST include at minimum:
|
|
- Home/Feed
|
|
- Bookings
|
|
- Profile
|
|
- Optional tabs (when implemented) may include Search/Explore and Payments, but must not displace Home/Feed as default.
|
|
|
|
## Frontend Architecture Requirements
|
|
|
|
## Runtime and Stack
|
|
- React 18 + Vite.
|
|
- React Router (`BrowserRouter`) for route navigation.
|
|
- `i18next` + `react-i18next` for translations and direction switching.
|
|
- `fetch` wrapper in `src/api/client.js` as single API boundary.
|
|
- Auth/session state in `AuthContext`.
|
|
|
|
## Route Map (Customer)
|
|
- `/` home feed (nearby/available salons) + search/filter.
|
|
- `/salon/:id` salon detail.
|
|
- `/login` phone OTP login.
|
|
- `/book?salon=<id>` booking creation.
|
|
- `/pay?booking=<id>` payment initiation.
|
|
- `/pay/return` payment callback/return surface.
|
|
- `/bookings` customer booking history.
|
|
- `/profile` customer profile summary.
|
|
|
|
## Module Boundaries
|
|
- `src/api/`: all HTTP logic, standardized errors.
|
|
- `src/contexts/`: auth/session lifecycle only.
|
|
- `src/hooks/`: domain-side UI logic (`useSalonSearch`, `usePaymentForm`).
|
|
- `src/pages/`: route-level composition.
|
|
- `src/components/`: reusable presentation and guarded wrappers.
|
|
- `src/i18n/`: locale dictionaries and locale/direction state.
|
|
|
|
## Functional Requirements
|
|
|
|
### FR-1 Phone-First Authentication
|
|
- Login MUST use:
|
|
- `POST /api/auth/phone/request/`
|
|
- `POST /api/auth/phone/verify/`
|
|
- Password auth endpoint (`/api/auth/token/`) MUST NOT be used (returns 410).
|
|
- Login request form MUST collect:
|
|
- `phone_number` (accept KSA local or E.164 input)
|
|
- `channel` (`sms` or `whatsapp`)
|
|
- optional: `device_id` (recommended for abuse controls)
|
|
- Verify step MUST submit `request_id` + 6-digit `code`.
|
|
- On success, frontend MUST persist `access` and `refresh` tokens and user payload.
|
|
|
|
### FR-2 Session Restore and Token Refresh
|
|
- On app boot, if `access` exists:
|
|
- call `GET /api/auth/me/`.
|
|
- If `401`/token invalid:
|
|
- call `POST /api/auth/token/refresh/` once.
|
|
- retry `GET /api/auth/me/` with new access token.
|
|
- If refresh fails, frontend MUST clear tokens and require re-login.
|
|
|
|
### FR-3 Salon Discovery
|
|
- Home MUST render a salon feed by default on first load.
|
|
- Feed data MUST come from `GET /api/salons/` and support query params for discovery.
|
|
- Home search MUST call `GET /api/salons/?q=<query>`.
|
|
- Search SHOULD support additional filters when UI is added:
|
|
- `city`
|
|
- `service`
|
|
- Nearby/available ranking can be client-side initially, but server response MUST remain source of truth.
|
|
- Result cards MUST link to `/salon/:id`.
|
|
|
|
### FR-4 Salon Detail
|
|
- Detail page MUST call `GET /api/salons/:id/`.
|
|
- UI MUST render:
|
|
- salon base info
|
|
- services (duration, amount, currency)
|
|
- staff list
|
|
- optional reviews/photos if present
|
|
- CTA MUST deep-link to booking flow with salon id.
|
|
|
|
### FR-5 Booking Creation
|
|
- Booking page MUST require authenticated user.
|
|
- Create booking with `POST /api/bookings/` using:
|
|
- `service`
|
|
- `staff` (required)
|
|
- `start_time`
|
|
- `end_time`
|
|
- optional `notes`
|
|
- `end_time` MUST match service duration exactly.
|
|
- Datetime submitted to backend MUST include explicit offset (`+03:00` for KSA baseline).
|
|
- On success (`201`), frontend MUST navigate to payment flow with booking id.
|
|
|
|
### FR-6 Booking History
|
|
- `/bookings` MUST call authenticated `GET /api/bookings/`.
|
|
- List MUST show booking id, status, salon/service labels, datetime, and price.
|
|
- Datetime rendering MUST use active locale formatting.
|
|
|
|
### FR-7 Payment Initiation (Idempotent)
|
|
- Payment submission MUST call `POST /api/payments/`.
|
|
- Payload requirements:
|
|
- `booking_id` (number)
|
|
- `provider` = `moyasar`
|
|
- `idempotency_key` (UUID)
|
|
- `source` object with supported type (`stcpay`, `token`, `applepay`, `samsungpay`)
|
|
- `callback_url` required for `source.type=token`
|
|
- Frontend MUST disable duplicate submits while request is in-flight.
|
|
- Same payment attempt retry MUST reuse the same `idempotency_key`.
|
|
- New attempt MUST generate a new key.
|
|
- If response includes `redirect_url`, frontend MUST redirect.
|
|
|
|
### FR-8 Payment Return Handling
|
|
- `/pay/return` MUST parse query params:
|
|
- `status`
|
|
- `id`
|
|
- Success statuses shown as success UX: `paid`, `captured`, `authorized`.
|
|
- Non-success statuses MUST show neutral/pending/failure guidance and link to profile/bookings.
|
|
|
|
### FR-9 Locale and Direction
|
|
- App MUST allow switching between `ar-sa` and `en`.
|
|
- Locale switch MUST:
|
|
- persist preference in local storage
|
|
- set `<html lang>`
|
|
- set `<html dir>` (`rtl` for `ar-sa`, `ltr` for `en`)
|
|
- API calls MUST include `Accept-Language` header with active locale.
|
|
|
|
## API Contract Requirements
|
|
|
|
| Endpoint | Auth | Request | Success | Error handling |
|
|
|---|---|---|---|---|
|
|
| `POST /api/auth/phone/request/` | No | `phone_number`, `channel`, optional profile fields | `201` with `request_id`, `expires_at` | `429` may include `retry_after_seconds`; show wait message |
|
|
| `POST /api/auth/phone/verify/` | No | `request_id`, `code` | `200` with `access`, `refresh`, `user` | `400` invalid/expired code |
|
|
| `POST /api/auth/token/refresh/` | No | `refresh` | `200` with new `access` | logout on failure |
|
|
| `GET /api/auth/me/` | Bearer | - | `200` user payload | `401` triggers refresh flow |
|
|
| `GET /api/salons/` | No | `q`, optional `city`, `service` | `200` list | show localized generic fetch error |
|
|
| `GET /api/salons/:id/` | No | - | `200` detail object | show detail/fallback |
|
|
| `POST /api/bookings/` | Bearer | booking payload | `201` booking | `400` field validation errors |
|
|
| `GET /api/bookings/` | Bearer | - | `200` list | auth + generic errors |
|
|
| `POST /api/payments/` | Bearer | payment payload | `201` created or `200` reused idempotent record | `400/403` with details; never auto-retry with new key |
|
|
|
|
## Error and State Handling Requirements
|
|
- API wrapper MUST throw structured errors with:
|
|
- HTTP status
|
|
- parsed response body
|
|
- best message (`detail` first, fallback to response text)
|
|
- For validation objects (`{field: [msg]}`), UI SHOULD render first field message near form and keep raw object in debug logs.
|
|
- For `429` with `retry_after_seconds`, UI MUST display server-provided cooldown.
|
|
- All mutating forms MUST expose:
|
|
- idle/loading/error/success states
|
|
- submit button disabled while loading
|
|
|
|
## Security and Abuse-Resistance Requirements
|
|
- Use Bearer access token for authenticated endpoints only.
|
|
- Include optional `device_id` during phone auth request to strengthen backend abuse controls.
|
|
- Never send raw card PAN/CVV data to backend; use tokenized sources only.
|
|
- On logout, clear user and both tokens from memory + storage.
|
|
|
|
## Accessibility and UX Requirements
|
|
- All interactive controls MUST have accessible labels.
|
|
- Auth/booking/payment forms MUST be keyboard usable.
|
|
- Error text MUST be visible and associated with active form context.
|
|
- Layout MUST remain usable on mobile widths (`>=320px`) and desktop.
|
|
|
|
## Non-Functional Requirements
|
|
- Reliability: no duplicate payment submission side effects for one attempt.
|
|
- Consistency: API errors surfaced predictably and localized where available.
|
|
- Maintainability: domain behavior in hooks/services, not route components.
|
|
- Extensibility: route/module structure must support manager/staff pages later without rewrite.
|
|
|
|
## Test Requirements (Frontend)
|
|
- Test stack: `vitest` + Testing Library.
|
|
- Required coverage for release:
|
|
- phone login request + verify success/failure + 429 cooldown message
|
|
- auth restore and refresh-token fallback
|
|
- protected route redirect behavior
|
|
- salon search loading/empty/results/error
|
|
- booking form validation + API error mapping + success redirect
|
|
- payment form source validation + idempotency key reuse on retry + redirect behavior
|
|
- locale switching persists and sets `lang`/`dir`
|
|
- bookings list rendering and localized datetime output
|
|
|
|
Run:
|
|
- `cd frontend && npm run test`
|
|
|
|
## Definition of Done (Frontend)
|
|
- All FR requirements implemented for in-scope routes.
|
|
- API integrations match endpoint/payload contract above.
|
|
- No use of deprecated password login API.
|
|
- All listed frontend tests pass.
|
|
- `ar-sa` and `en` UX verified on mobile + desktop.
|
|
|
|
## Known Dependencies and Open Decisions
|
|
- OAuth/social-linking policy is not finalized; keep social login UI hidden for now.
|
|
- Cancellation and refund policies are not finalized; do not ship irreversible customer actions until policy finalization.
|
|
- Detailed business-hours/timezone policy beyond current backend validation remains open; keep KSA-offset submission and avoid client-side assumptions that override server validation.
|