# Phase 15: UX Polish + Super Admin Ops Tools **Status:** Not started **Depends on:** All previous phases ## Goal Production-ready polish: audit log viewer, global search, bulk operations, mobile-responsive portal, keyboard shortcuts, error boundaries, and production security hardening. ## Planned Scope ### Backend **Audit log viewer endpoint** - `GET /api/audit-logs` — paginated, filterable by school, action, actor, date range - Already have `audit_logs` table — just need the endpoint + frontend **Global search endpoint** - `GET /api/search?q={query}` — searches across schools, invoices, tickets - Returns grouped results: `{ schools: [...], invoices: [...], tickets: [...] }` - Limit 5 results per category **Bulk school actions** - `POST /api/schools/export-csv` — export school list as CSV (name, status, tier, credits, license) - `POST /api/schools/bulk-status` — change status for multiple school IDs (super admin) **Rate limiting on auth endpoints** - Add `slowapi` middleware: `POST /auth/login` — max 10 req/min per IP - Return `429 Too Many Requests` with Retry-After header **HTTPS redirect** - Add redirect middleware in `main.py` (X-Forwarded-Proto check) - Update `nginx.conf` to pass X-Forwarded-Proto header ### Frontend **Audit log page** (`AuditLogPage.vue` — new or sub-section in admin) - Table: timestamp, actor, action, entity type, entity name, IP - Filters: date range, actor, action type **Global search** (keyboard shortcut `/`) - `SearchModal.vue` — command palette style - Press `/` anywhere → modal opens with search input - Results grouped by type, click navigates to entity **Keyboard shortcuts** - `/` — open global search - `N` — new school (when on /schools) - `Esc` — close any open modal **Mobile-responsive portal** - PortalSidebar: collapsible on mobile (hamburger menu) - PortalLayout: responsive header - All portal pages: responsive table → card layout on mobile **Error boundary component** (`ErrorBoundary.vue`) - Wraps page content in AppLayout/PortalLayout - On uncaught error: shows friendly "Something went wrong" card with retry button **Empty state illustrations** - SchoolsPage: "No schools yet" with Add School CTA - TicketsPage: "No tickets" with Submit Ticket CTA - BillingPage: "No invoices yet" with Generate Invoices CTA **Dashboard refresh UX** - Auto-refresh every 5 minutes (setInterval) - Show "Data may be stale" banner after 10 minutes without refresh **CSV export button** in SchoolsPage toolbar ## Key Files to Create/Modify - `backend/app/routers/search.py` (new) - `backend/app/routers/audit.py` (new — expose existing audit_logs table) - `backend/app/routers/schools.py` — bulk-status + export-csv endpoints - `backend/app/main.py` — include search + audit routers, add rate limit + HTTPS middleware - `backend/requirements.txt` — add `slowapi` - `frontend/src/pages/AuditLogPage.vue` (new) - `frontend/src/components/ui/SearchModal.vue` (new) - `frontend/src/components/ui/ErrorBoundary.vue` (new) - `frontend/src/layouts/AppLayout.vue` — global keyboard shortcut handler - `frontend/src/layouts/PortalLayout.vue` — mobile responsive - `frontend/src/components/sidebar/PortalSidebar.vue` — mobile collapsible - `frontend/src/pages/SchoolsPage.vue` — bulk actions, CSV export, empty state - `frontend/src/router/index.ts` — /audit-logs route - `frontend/src/components/sidebar/AppSidebar.vue` — audit logs nav item - `frontend/src/lib/api.ts` — globalSearch, getAuditLogs, exportSchoolsCsv, bulkSchoolStatus