85 lines
6.3 KiB
Markdown
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.
|