# Track A — Identity & Access **Owner:** Person A **Branch:** `fix/stubs-identity-access` (worktree at `.worktrees/stubs-identity-access/`) **Files in scope:** `client/src/pages/Login.tsx`, `client/src/pages/Profile.tsx`, `client/src/pages/Teachers.tsx`, `client/src/pages/admin/Users.tsx` **No backend changes** unless explicitly noted in the task. ## Conventions (from AGENTS.md) - Work in a worktree branched from `dev`. Do not edit the main checkout. - Use the shared axios instance at `client/src/store/api.ts`. - Route gating stays in `App.tsx` via `` — do not gate inside pages. - PWA service worker is only built by `npm run build` (not `npm run dev`). - `.env` is gitignored. Never commit secrets. - Do not commit or push without an explicit "go" from the reviewer. ## Tasks ### A1. Login — Password recovery button - **File:** `client/src/pages/Login.tsx:208-213` - **Current:** "Recovery" link calls `alert('Password recovery is not yet available. Please contact your administrator.')`. - **Decision (pick one, ask backend if needed):** - **(a) Backend exists** — wire to `POST /auth/forgot-password`. Show success/error message in-place. - **(b) Backend does not exist** — demote the control to a non-button text label, or render it disabled with `title="Contact your administrator"`. Do not pretend the action does something. - **Acceptance:** Clicking Recovery either triggers a real request or visibly does nothing. No `alert()`. ### A2. Profile — Avatar upload - **File:** `client/src/pages/Profile.tsx:147-167` (TODO comment at :156) - **Current:** File picker opens, reads file to a data URL, logs `console.warn('Avatar selected but upload endpoint not yet available. Local preview only.')`. The selected image is never displayed or persisted. - **Desired:** After picking a file, the avatar in the header updates **and** the change persists across reloads. - **Decision (check backend first):** - **(a) `POST /users/:id/avatar` exists** — upload the file (or data URL) to that route and use the returned URL. - **(b) No upload endpoint** — read file to data URL and `PUT /users/:id` with `avatar_url`. If `PUT` does not accept that field, fall back to `localStorage` keyed on `user.id` (clearly document in code that this is the interim store). - **Acceptance:** Picking a JPG changes the avatar in the header and survives a full page reload. ### A3. Profile — Request Leave form - **File:** `client/src/pages/Profile.tsx:358-382` - **Current:** "Request Leave" button fires **three sequential** `window.prompt(...)` calls (start date, end date, leave type), then `POST /hr/leave-requests`. Replace with a single modal. - **Desired:** Modal with three fields: `start_date` (date input), `end_date` (date input, must be ≥ start), `leave_type` (select: Annual / Sick / Maternity / Other). Validate end ≥ start before submit. On 2xx, show an in-modal success and call `loadStaffData()`. On error, show inline error. - **Acceptance:** No `window.prompt`. End-before-start triggers an inline validation error, not a request to the server. ### A4. Profile — Leave record info button - **File:** `client/src/pages/Profile.tsx:422-426` - **Current:** Per-row info icon calls `alert(\`Leave record ${lr.uid}:\nType: ${lr.leave_type}\nDates: ${lr.start_date} → ${lr.end_date}\nStatus: ${lr.status}\`)`. - **Desired:** Replace with either (a) a popover anchored to the row, or (b) a small modal. Show the same four fields, formatted (`new Date(...).toLocaleDateString()`). - **Acceptance:** Clicking the info icon opens a popover/modal. No `alert()`. ### A5. Teachers — Department filter button - **File:** `client/src/pages/Teachers.tsx:222-241` - **Current:** "Departmental Sync" button opens a `window.prompt` listing department IDs and asks the teacher to type one in. Awful UX. - **Desired:** Replace with a `