6.3 KiB
6.3 KiB
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.tsxvia<ProtectedRoute allowedRoles>— do not gate inside pages. - PWA service worker is only built by
npm run build(notnpm run dev). .envis 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.
- (a) Backend exists — wire to
- 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/avatarexists — 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/:idwithavatar_url. IfPUTdoes not accept that field, fall back tolocalStoragekeyed onuser.id(clearly document in code that this is the interim store).
- (a)
- 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), thenPOST /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 callloadStaffData(). 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.promptlisting department IDs and asks the teacher to type one in. Awful UX. - Desired: Replace with a
<select>ofdepartments(place it next to the existing search box). AdddepartmentFilter: number | 'all'state, filterfilteredTeachersbyt.department_id === departmentFilterwhen 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 viaPUT /users/:id/reset-password. Success and failure bothalert(). - 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. Noalert()for success or error.
A7. Admin Users — Reset credentials button
- File:
client/src/pages/admin/Users.tsx:123-137 - Current: Same
window.promptpattern as A6, with threealert()calls (:127, :133, :135). - Desired: Share a component with A6 — extract
client/src/components/ResetPasswordModal.tsx, parameterised byuserand the endpoint URL (or just byuserId). Use it from both A6 and A7. - Acceptance: Same modal in both pages. No
window.prompt. Noalert().
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.tsxreturns 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.tsxreturns no hits left.npm run buildis green in your worktree.
Manual test
- Log in as
admin@school.com / admin123. - Profile tab → upload an avatar (A2), request leave (A3), click a leave row's info icon (A4).
- Log out, on the Login page click Recovery (A1).
- Log back in. Teachers page → use the new department dropdown (A5) and reset a teacher's password (A6).
- Admin → Users → reset a user's credentials (A7, same modal as A6).
- 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.tsxalert/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.