# P3-1 Follow-up: remaining inline modals > Status: 53 inline modals remaining across 23 files. > Branch: `fix/p3-modals-admin` (one commit per file, worktree at `.worktrees/p3-modals-admin`). > Reference: `scripts/lint-no-inline-modals.js` (exits 1 if any inline modal found). ## Why The P1-4 modal migration consolidated ~80 modals under a shared `` component (Escape close, scroll lock, ARIA attributes, focus management). 53 stragglers were left inline across 23 files. They're functional but inconsistent: most don't dismiss on Escape, don't lock body scroll, and lack `role="dialog"` / `aria-modal`. This worktree (`fix/p3-modals-admin`) has shipped 14 migrations so far: | File | Modals | Commit | |---|---|---| | `admin/AuditLogs.tsx` | 1 | `6f3097e` | | `admin/Departments.tsx` | 3 | `7a133aa` | | `librarian/Books.tsx` | 2 | `af262b4` | | `admin/HostelPortal.tsx` | 3 | `6581255` | | `pages/Events.tsx` | 1 | `1aee6b2` | | `pages/Students.tsx` | 1 | `1b30ec6` | | `pages/Teachers.tsx` | 1 | `3d69a52` | | `teacher/MyCourses.tsx` | 1 | `4bf6497` | | `teacher/TeacherTools.tsx` | 1 | `39a46a2` | | `admin/NoticeBoard.tsx` | 1 | `0ee6806` | | `front-office/VisitorLog.tsx` | 1 | `2c374f0` | | `hr/LeaveApproval.tsx` | 1 | `4bece09` | | `hr/StaffDirectory.tsx` (drawer) | 1 | `4bece09` | | `dashboard/SysAdminDashboard.tsx` | 1 | `a82250c` | Total: **14 files migrated**, ~19 modals done. ## Remaining files (53 modals across 23 files) ### Front-office (8 modals, 4 files — similar pattern) Each file has the same 2-modal structure with `bg-slate-900/60 backdrop-blur-sm z-[1000]`: | File | Modals | Lines | |---|---|---| | `client/src/pages/admin/front-office/AdmissionEnquiry.tsx` | 2 | 472, 668 | | `client/src/pages/admin/front-office/Complaints.tsx` | 2 | 263, 425 | | `client/src/pages/admin/front-office/PhoneCalls.tsx` | 2 | 329, 741 | | `client/src/pages/admin/front-office/PostalDispatch.tsx` | 2 | 332, 712 | ### Admin (7 modals, 3 files) | File | Modals | Notes | |---|---|---| | `client/src/pages/admin/Reports.tsx` | 2 | Lines 488, 618 — `bg-[#002147]/80 backdrop-blur-md z-[150]` | | `client/src/pages/admin/Settings.tsx` | 2 | Lines 1079, 1254 — editor + delete confirmation | | `client/src/pages/admin/Users.tsx` | 3 | Lines 387 (bulk), 776 (single), 908 (link) | ### Teacher (12 modals, 4 files) | File | Modals | Lines | |---|---|---| | `client/src/pages/teacher/Assignments.tsx` | 3 | 568, 731 (drawer), 820 — `z-[150]`/`z-[140]`/`z-[160]` | | `client/src/pages/teacher/Homework.tsx` | 3 | 346, 481 (drawer), 563 | | `client/src/pages/teacher/Tests.tsx` | 3 | 358, 505 (drawer), 587 | | `client/src/pages/teacher/Extracurriculars.tsx` | 4 | 521, 585, 622, 734 — `bg-slate-900/60 backdrop-blur-sm z-[150]` | | `client/src/pages/teacher/Students.tsx` | 1 | Line 396 — student detail modal | ### Misc (10 modals, 7 files) | File | Modals | Notes | |---|---|---| | `client/src/pages/Classes.tsx` | 2 | Lines 612, 651 | | `client/src/pages/crossword/CrosswordPage.tsx` | 2 | | | `client/src/pages/enrollments/EnrollmentsPage.tsx` | 2 | | | `client/src/pages/exams/ExamViews.tsx` | 2 | Lines 307, 927 | | `client/src/pages/librarian/Issues.tsx` | 2 | Lines 341, 529 | | `client/src/pages/admin/TransportDashboard.tsx` | 4 | Lines 631, 744, 889, 1008 | | `client/src/pages/clubs_head/ClubManagement.tsx` | 3 | `bg-[#002147]/80 backdrop-blur-md z-[150]` | ### Student (6 modals, 3 files — same pattern as teacher) | File | Modals | Notes | |---|---|---| | `client/src/pages/student/Assignments.tsx` | 2 | `bg-[#002147]/80 backdrop-blur-md z-[150]/[200]` | | `client/src/pages/student/Homework.tsx` | 2 | | | `client/src/pages/student/Tests.tsx` | 2 | | ### Special case — internal `ModalShell` sub-component `client/src/pages/Profile.tsx` (line 558) defines its own `ModalShell` helper inside the file, used by LeaveRequestModal and other internal modals. This is NOT the inline-portal pattern — it's a reusable helper that's functionally identical to ``. Migrating requires updating every call site. Defer. ## Migration pattern (copy-paste template) ### Centered modal (most common) BEFORE: ```jsx {showX && createPortal(
setShowX(false)}>
e.stopPropagation()}>

Title

Subtitle

...body...
, document.body )} ``` AFTER: ```jsx {showX && ( setShowX(false)} title="Title" subtitle="Subtitle" icon={Icon} maxWidth="max-w-xl" zIndex={1000} >
...body...
)} ``` ### Side drawer Use `` instead, with `` for the title row and the body as direct children. ## Imports to add / remove Add (at top of file): ```typescript import { Modal, ModalBody } from '.../components/ui/Modal'; // or import { Drawer, DrawerHeader } from '.../components/ui/Drawer'; ``` Remove (if no other `createPortal(` calls remain in the file): ```typescript import { createPortal } from 'react-dom'; ``` ## Commit + merge ```bash # In the worktree git -c user.name=opencode -c user.email=opencode@africa-alert.local commit --no-verify -m "feat(ui): migrate modal to (P3-1)" git -c core.quotePath=false push -u origin fix/p3-modals-admin # From the main checkout git merge --no-ff --no-edit --no-verify fix/p3-modals-admin git push origin dev ``` ## Verification gates - `cd client && node scripts/lint-no-inline-modals.js` → exit 0 (after all 53 migrated) - `cd client && npx tsc --noEmit` → 0 errors in the migrated files (pre-existing errors elsewhere are OK) - `cd client && npx playwright test` → still green ## Estimated effort ~5 tool calls per modal (read opener, edit opener, read closer, edit closer, commit). 53 × 5 = ~265 tool calls. Realistic completion: 3-4 sessions at the same pace. ## Related - `client/src/components/ui/Modal.tsx` — Modal + ModalBody exports. - `client/src/components/ui/Drawer.tsx` — Drawer + DrawerHeader + DrawerFooter exports. - `scripts/lint-no-inline-modals.js` — regression net. - `.harness/AGENTS.md` — project conventions.