Files

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