geocrop-platform./apps/nextgen/.harness/plans/stubs-sprint-1b/A.md

85 lines
6.3 KiB
Markdown

# 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 `<ProtectedRoute allowedRoles>` — 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 `<select>` of `departments` (place it next to the existing search box). Add `departmentFilter: number | 'all'` state, filter `filteredTeachers` by `t.department_id === departmentFilter` when set, and include "All" option.
- **Acceptance:** No `window.prompt`. Selecting a department narrows the table live.
### A6. Teachers — Reset password button
- **File:** `client/src/pages/Teachers.tsx:331-352`
- **Current:** Per-row Key icon calls `window.prompt('Reset password for …')` and sends it via `PUT /users/:id/reset-password`. Success and failure both `alert()`.
- **Desired:** Small "Reset password" modal containing one password input (min length 6) with a generated "auto-fill temporary password" helper button. Drop both `alert()`s — use inline modal status (loading / success / error).
- **Acceptance:** No `window.prompt`. No `alert()` for success or error.
### A7. Admin Users — Reset credentials button
- **File:** `client/src/pages/admin/Users.tsx:123-137`
- **Current:** Same `window.prompt` pattern as A6, with three `alert()` calls (:127, :133, :135).
- **Desired:** Share a component with A6 — extract `client/src/components/ResetPasswordModal.tsx`, parameterised by `user` and the endpoint URL (or just by `userId`). Use it from both A6 and A7.
- **Acceptance:** Same modal in both pages. No `window.prompt`. No `alert()`.
## Cross-cutting checks
- `grep -rn "alert(" client/src/pages/Login.tsx client/src/pages/Profile.tsx client/src/pages/Teachers.tsx client/src/pages/admin/Users.tsx` returns no hits left in Track A scope.
- `grep -rn "window.prompt" client/src/pages/Login.tsx client/src/pages/Profile.tsx client/src/pages/Teachers.tsx client/src/pages/admin/Users.tsx` returns no hits left.
- `npm run build` is green in your worktree.
## Manual test
1. Log in as `admin@school.com / admin123`.
2. Profile tab → upload an avatar (A2), request leave (A3), click a leave row's info icon (A4).
3. Log out, on the Login page click Recovery (A1).
4. Log back in. Teachers page → use the new department dropdown (A5) and reset a teacher's password (A6).
5. Admin → Users → reset a user's credentials (A7, same modal as A6).
6. No browser alert, confirm, or prompt dialog should appear anywhere.
## Out of scope here (other tracks)
- `Fees.tsx`, `MyCourses.tsx`, `AdminDashboard.tsx`, `ExamEditor.tsx`, `Settings.tsx` alert/soundness work — see Track B and C.
- General "swap all `alert()` to toast" pass across remaining controllers (HRManagement, Inventory, Library, etc.) — Track C's C3 lays the toast foundation; the sweep is a follow-up.