17a82832ab
Replace the username/password stub with Balinyaar's real credential (phone-OTP) and add the role router every authenticated screen sits behind. - services/auth rewritten for OTP over the b2 contract: types/keys/constants, clientApi+mockApi behind a config'd seam, hooks (useRequestOtp/useVerifyOtp/ useMe/useRefresh/useLogout/useSelectRole/useSessionRoleSync). Stub removed. - A1/A2 customer login + B1/B2 nurse switch as one OTP flow at /login (PhoneStep/OtpStep: auto-verify, resend countdown, wrong/expired/lockout states). - Role router: pure resolveRoleDestination + RoleRouter -> family / nurse / admin / select-role, with a splash while /me loads (no wrong-shell flash). - SelectRole first-use screen at /select-role. - Widened AuthState (roles via SessionUser), hydrated from /me by useSessionRoleSync. - Fetch-layer silent token refresh (single-flight + one retry) + shared persistAuthTokens/clearAuthTokens; useRefresh as the on-demand path. - auth i18n namespace in both locales; tests for routing branches, countdown, OtpStep state machine, RoleRouter branches; fixed jest @/ -> src alias. - Docs: client/CLAUDE.md, frontend STATUS/report, for-backend REQ-002..004. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4.8 KiB
4.8 KiB
Frontend → Backend requests (append-only)
The frontend lane appends here when it needs a contract that doesn't exist yet, finds a shape mismatch, or needs a new field/filter/endpoint. The backend agent reads this at the start of each phase and delivers fixes in its own change. Frontend never edits backend code to "fix" a gap — it requests it.
REQ-001 — Confirm response envelope, wire casing & pagination shape — filed by frontend-phase-0 — 2026-07-02
- Need: Authoritative confirmation of three things the frontend types depend on:
- Envelope unwrapping. The b0 swagger shows every response wrapped in
ApiResult({ isSuccess, statusCode, message, requestId, data }). The frontend'sclientFetchcurrently returns the raw body, so domainclientApis read the payload viaunwrap()(data). Confirm this is the intended shape for all endpoints (i.e. payload always underdata), so the pattern is correct before f1+ copy it. - Wire casing. Observed swagger properties are camelCase (
isSuccess,serverTimeUtc) — not the snake_caseapi-conventions.mdimplies for URL segments. Please confirm JSON body casing is camelCase (and, if so, we can note it in the convention doc), or state where it differs. - Pagination payload.
api-conventions.mdsays lists returnitems+total(+page/page_size). Confirm the exact field names/casing on the wire (we've typedPaginated<T>as{ items, total, page, pageSize }inclient/src/lib/api/types.ts).
- Envelope unwrapping. The b0 swagger shows every response wrapped in
- Why: These fix the shared
ApiEnvelope<T>/Paginated<T>types and theservices/{domain}reference pattern every later frontend phase inherits. - Proposed shape:
{ isSuccess: boolean, statusCode: number, message?: string, requestId?: string, data?: T }anddata: { items: T[], total: number, page: number, pageSize: number }for lists. - Status: open
REQ-002 — OTP length + expiry in RequestOtpResult — filed by frontend-phase-1-b2 — 2026-07-02
- Need: Add
codeLength(int) andexpiresInSeconds(int) toRequestOtpResult. - Why: The A2/B2 OTP screen renders one box per digit and (later) a code-expiry hint.
RequestOtpResultcurrently exposes onlyotpSent+resendAvailableInSeconds, so the frontend hardcodes the box count (OTP_CODE_LENGTH = 6, inferred from the live 6-digit verify example, not the 4-box wireframe). Surfacing the length makes the box count contract-driven; the expiry lets us show "code expires in …". - Proposed shape:
{ otpSent: boolean, resendAvailableInSeconds: number, codeLength: number, expiresInSeconds: number } - Status: open
REQ-003 — Machine-readable error codes for verify_otp failures — filed by frontend-phase-1-b2 — 2026-07-02
- Need: A stable
codeon the 400 envelope for verify_otp that distinguishes wrong code vs expired code vs max-attempts lockout (e.g.otp_invalid|otp_expired|otp_locked), and — for lockout — aretryAfterSeconds(orlockedUntil) field. - Why: The OTP screen has explicit wrong-code, expired-code, and max-attempts-lockout states
(per the phase spec), but the contract returns the same safe 400 message for all of them, so the frontend
can't reliably tell them apart. Today it maps a lockout only when it sees the mock's
otp_lockedcode and otherwise degrades to a generic "incorrect or expired" message. A stable machine code (kept generic enough to avoid account enumeration) would let the UI render the precise state + the unlock countdown. - Proposed shape:
{ isSuccess: false, statusCode: 400, message: "…", code: "otp_locked", data: { retryAfterSeconds: 60 } } - Status: open
REQ-004 — Confirm multi-role disambiguation (activeRole?) — filed by frontend-phase-1-b2 — 2026-07-02
- Need: Confirm whether
MeResultwill gain anactiveRole(the user's currently-selected actor) for a user who holds bothcustomerandnurse, or whether the client should keep owning that choice. - Why: The role router must pick one app for a dual-role user. Absent an
activeRolein the contract, it currently uses the intended role carried from the login switch (A1 vs B1), defaulting to the family app. If the backend intends to persist a "current role", the router should prefer it. Also note: verify_otp returnsrolesbut no userid(only/mehas it) — fine for now (context id is hydrated from/me), flagging in case that changes. - Status: open