v2 Admin Spec — Global Patterns
Global Patterns
Cross-cutting patterns and conventions used throughout the admin backend redesign. Domain-specific spec documents reference these patterns by name rather than re-specifying them.
1. Navigation Shell
1.1 Sidebar Structure
Layout: Fixed left sidebar, always visible. Collapsible to icon-only mode.
Sections (top to bottom):
- Logo/brand area (top)
- Global search bar
- Dashboard (standalone item, not grouped)
- 9 domain sections, each with an icon, label, and expandable sub-items:
- Student Management (icon: users)
- Semester Management (icon: calendar-check)
- Content (icon: play-circle)
- Scheduling (icon: clock)
- Teacher Management (icon: user-check)
- Billing & Payments (icon: credit-card)
- Reporting (icon: bar-chart)
- Communication (icon: mail)
- Admin & System (icon: settings)
Expand/collapse behavior:
- Clicking a section header expands/collapses its sub-items
- Only one section expanded at a time (accordion pattern)
- Active section auto-expands on page load
- Collapsed sidebar shows only icons; hovering expands temporarily
Active state: Current section header highlighted, current sub-item highlighted with left border accent
1.2 Breadcrumb Pattern
Format: Domain > Screen > [Entity Name] Examples:
- Student Management > Students > Sarah Ahmed
- Semester Management > Semesters > Spring 2026 > Setup Checklist
- Billing & Payments > Family Plans
Behavior: Each segment is clickable and navigates to that level. Entity names link to the entity page's default tab (Profile).
1.3 Page Header Pattern
Every page has:
- Title: Screen name (e.g., "Students", "Payment Overview")
- Subtitle (optional): Brief description or context
- Primary action button (if applicable): Top-right, e.g., "Add Student", "Create Semester"
- Breadcrumb: Below title
2. Global Search
From Candidate C enhancement.
2.1 Placement and Access
- Search bar at top of sidebar, always visible
- Keyboard shortcut:
Cmd+K(Mac) /Ctrl+K(Windows) - Focus: opens expanded search overlay with recent searches
2.2 Searchable Entities
| Entity | Searchable Fields | Result Format |
|---|---|---|
| Student | Name, email | Name · Level · Semester · Status badge |
| TA | Name, email | Name · "Teaching Assistant" · Student count |
| Semester | Name | Name · Date range · Status badge |
2.3 Result Behavior
- Results appear as you type (debounced, 300ms)
- Maximum 5 results per entity type, grouped by type
- Clicking a result navigates to the entity's detail page (Student Detail, Semester Hub, or TA Detail), default tab
- No-result state: "No results found for '[query]'" with suggestion: "Try searching by name or email"
2.4 Recent Searches
- Last 5 searches persisted per session
- Shown when search bar is focused with no input
3. Role-Based Access
3.1 Role Definitions
| Role | Code | Sidebar View | Action Permissions |
|---|---|---|---|
| Admin | user_type=1 | Full 9-domain sidebar | Full CRUD + bulk operations |
| Teaching Assistant (TA) | user_type=2 | 4 items: My Students, My Schedule, My Groups, Recordings | View assigned students, review submissions, manage own schedule |
| Admin (View Only) | user_type=5, role_flag=view_only | Full 9-domain sidebar | View all, no create/edit/delete/bulk actions |
| Support Staff | user_type=5, role_flag=support | Full 9-domain sidebar | View all including billing data, no billing actions |
Implementation note: View-Only and Support Staff share user_type=5. A new role_flag column (or equivalent permission attribute) on the users table is required to differentiate them. The only behavioral difference is that Support Staff sees the Student Detail > Payments tab data (read-only) and billing domain screens, while View-Only also sees these but without the billing-specific context that Support Staff needs for student inquiries. In practice, both roles have identical view-only access; the distinction exists primarily for audit/logging purposes and potential future permission divergence.
3.2 Master Access Matrix
| Domain | Admin | TA | View-Only | Support Staff |
|---|---|---|---|---|
| Dashboard | Full | Reduced (own alerts) | View only | View only |
| Student Management | Full | Own students only | View only | View only |
| Semester Management | Full | Hidden | View only | View only |
| Content | Full | Recordings only | View only | View only |
| Scheduling | Full | Own schedule only | View only | View only |
| Teacher Management | Full | Hidden | View only | View only |
| Billing & Payments | Full | Hidden | View only | View only (no actions) |
| Reporting | Full | Hidden | View only | View only |
| Communication | Full | Own messages only | View only | View only |
| Admin & System | Full | Hidden | Hidden | Hidden |
3.3 TA Sidebar
TAs see a simplified sidebar:
├── My Students (→ Students list filtered to assigned students)
├── My Schedule (→ TA Schedules filtered to own schedule)
├── My Groups (→ Student Groups filtered to own groups)
└── Recordings (→ Content > Recordings)
3.4 Action Visibility Rules
- View-only users: See all data, see all filter/sort controls, but action buttons (Create, Edit, Delete, Promote, Deactivate, etc.) are hidden — not grayed out, hidden entirely
- Support staff on billing: See subscription status, payment history, special flags on Student Detail Payments tab. NO action buttons (Cancel, Pause, Apply Discount, etc.)
- Per-action overrides: Some actions are admin-only even within accessible domains. These are noted in domain specs with
[Admin only]
4. Component Patterns
4.1 Data Table
Standard layout:
- Filter bar above table
- Column headers with sort indicators (▲/▼)
- Row hover highlight
- Row click navigates to detail (for entity tables) or opens modal (for action tables)
- Bulk selection: checkbox column (leftmost), "Select All" in header, bulk action bar appears above table
- Pagination: bottom-right, showing "1-25 of 342" with page navigation
Column types:
- Text: left-aligned
- Number: right-aligned
- Status: badge (see 4.3)
- Date: formatted as "Mar 10, 2026"
- Actions: icon buttons or "..." menu (rightmost column)
4.2 Filter Bar
Layout: Horizontal bar above data table with:
- Search input (leftmost): filters by name/email
- Dropdown filters: semester, level, status, TA, etc. (screen-specific)
- Active filter chips: appear below filter bar when filters are applied, each with "×" to remove
- "Clear all" link: appears when any filter is active
Behavior:
- Filters apply immediately (no "Apply" button)
- URL updates with filter state (shareable filtered views)
- Filter state persists within session
4.3 Status Badges
Color scheme by domain:
| Status | Color | Used For |
|---|---|---|
| Active | Green | Student status, subscription status, semester status |
| Inactive / Blocked | Gray | Deactivated student |
| Enrolled | Blue | Student semester status |
| Pass | Green | Student promotion status |
| Fail | Red | Student promotion status |
| Under Review | Yellow/Amber | Submission status |
| Reviewed | Green | Submission status |
| Past Due | Orange | Subscription payment status |
| Cancelled | Gray | Subscription status |
| Paused / Deferred | Blue | Subscription pause or deferment status |
| Incomplete | Yellow/Amber | Stripe subscription incomplete (initial payment failed) |
| Unpaid | Red | Stripe subscription unpaid (renewal payment failed, exhausted retries) |
| None | Gray | No Stripe subscription found for student |
| Pending | Yellow/Amber | Awaiting action (End Checklist steps, Setup Checklist steps) |
| Complete | Green | Workflow step completed |
| Draft | Blue | Semester not yet active (distinguishes from Inactive gray) |
Communication statuses (used in Communication domain screens):
| Status | Color | Used For |
|---|---|---|
| Draft | Blue | Unsent email or notification being composed |
| Scheduled | Yellow/Amber | Message queued for future send |
| Sent | Green | Message dispatched to recipients |
| Delivered | Green | Push notification confirmed delivered |
| Failed | Red | Message delivery failed |
| Read | Green | Private message read by recipient |
| Unread | Yellow/Amber | Private message not yet read |
Issue Queue statuses (used in Admin & System > Issue Queue, per ADR-006):
| Status | Color | Used For |
|---|---|---|
| Open | Blue | New, unread, unassigned issue |
| In Progress | Yellow/Amber | Admin is handling. May carry "Delegated to CS/IT/Teaching" sub-badge (gray text modifier, same amber pill). |
| Resolved | Green | Handled, kept in history |
| Rejected | Red | Not a real issue (spam, misfire, duplicate); kept in history. No hard delete. |
Enrollment Type badges (used on Students list, Student Detail, Active Enrollment Report, per STAKEHOLDER-ANSWERS-2026-04-22 §C):
| Status | Color | Used For |
|---|---|---|
| Continuing | Blue | Student was with us last semester, enrolling in something new (L1–4 graduate, or Year 2 progression) |
| New | Green | First-time enrollee |
| Repeat | Yellow/Amber | Repeating a level previously failed, or opted to redo |
| Year 2 | Purple | Currently in Year 2 program |
Invoice-level statuses (used in Student Detail > Payments tab payment history, sourced from Stripe invoice.status):
| Status | Color | Context |
|---|---|---|
| Paid | Green | Invoice fully paid |
| Open | Yellow/Amber | Invoice issued, awaiting payment |
| Void | Gray | Invoice voided (no longer collectible) |
| Uncollectible | Red | Invoice marked uncollectible (all retry attempts exhausted) |
Payment transaction statuses (used in Billing & Payments > Payments screen, sourced from Stripe charge/payment intent status):
| Status | Color | Context |
|---|---|---|
| Succeeded | Green | Payment charge succeeded |
| Failed | Red | Payment charge failed |
| Refunded | Gray | Payment refunded |
| Pending | Yellow/Amber | Payment processing |
Note: Invoice statuses and payment transaction statuses are related but distinct. An invoice can be "Open" while its latest payment attempt "Failed." The Student Detail Payments tab shows invoice-level status (Stripe's canonical invoice lifecycle). The Billing > Payments screen shows transaction-level status (individual charge outcomes). Both are Stripe-sourced.
Badge format: Rounded pill with background color, white/dark text. Consistent across all screens.
4.4 Alert Tiles (Dashboard)
Layout: Standard Card (default shadcn styling, no custom borders or backgrounds) with:
- Label (CardDescription, with small icon inline — icon in
text-muted-foreground) - Count (CardTitle, large number)
- Trend indicator (optional: Badge with ↑/↓/— and comparison text, in CardAction)
- Click: navigates to resolution context (specific screen with pre-applied filters)
Conditional display: Tiles only appear when count > 0 (except semester phase prompt, which is always visible)
Trend badge styling: Trend badges use a subtle background tint to signal direction:
- Worsening (↑ on alert counts): light red background (
bg-red-50) - Improving (↓ on alert counts): light green background (
bg-green-50) - Flat (—): default outline badge styling
Card and icon styling remains default throughout — no colored borders, tinted card backgrounds, or icon coloring. The trend badge background is the sole visual signal, keeping the layout clean and consistent with the component library.
4.5 Metric Cards
Layout: Small card with:
- Label (top, small text)
- Value (large number or formatted amount)
- Optional trend (vs. previous period)
Used on: Payment Overview metrics bar, Dashboard quick stats
4.6 Confirmation Dialogs
Standard (non-destructive):
- Title: action description
- Body: what will happen
- Buttons: Cancel (left, secondary) | Confirm (right, primary)
Destructive:
- Title: "Are you sure?" or action-specific
- Body: warning text explaining consequences, in red
- Buttons: Cancel (left, secondary) | [Action Name] (right, red/destructive)
- Extra: typed confirmation for high-risk actions (e.g., "Type DELETE to confirm")
Used for: Delete, Deactivate, Reset Progress, Cancel Subscription, Linked Deactivation
4.7 Toast Notifications
Types:
- Success (green): "Student deactivated successfully"
- Error (red): "Failed to cancel subscription. Please try again."
- Info (blue): "Semester cloned. Review Setup Checklist."
Behavior: Auto-dismiss after 5 seconds. Appears top-right. Stacks if multiple.
4.8 Empty States
Layout:
- Icon or illustration (centered)
- Message: what the empty state means (e.g., "No students match your filters")
- Suggested action: button or link (e.g., "Clear filters" or "Add your first student")
Every screen must define its empty state message and suggested action in its spec.
4.9 Loading States
- Initial page load: Skeleton screen (gray blocks matching layout)
- Data refresh: Inline spinner next to the data section being refreshed
- Action processing: Button shows loading spinner, disabled until complete
- Stripe data: Shows "Loading billing data..." with spinner; if slow (>3s), shows "Fetching from Stripe..."
4.10 Modals
Standard layout:
- Overlay with backdrop
- Header: title + close (×) button
- Body: form fields or content
- Footer: action buttons (Cancel | Primary Action)
- Max width: 600px for forms, 800px for previews
- Close on: × button, Cancel button, backdrop click, Escape key
Used for: Create forms, Edit forms, Preview content, Confirmation dialogs
4.11 Tabs (Entity Pages)
Layout:
- Horizontal tab bar below entity header
- Active tab: underline accent + bold text
- Tab labels may include badge count (e.g., "Submissions (12)")
- Default tab: Profile for Students/TAs, Overview for Semesters
Behavior:
- Tabs lazy-load content when first activated
- Tab state preserved when switching between tabs (scroll position, filters)
- URL updates with tab name (shareable deep links, e.g.,
/students/123#payments) - Tab bar is sticky when scrolling within a tab
4.12 Section Headers
Within a tab or screen:
- Section title (bold, larger text)
- Optional action link (right-aligned, e.g., "View all" or "Edit")
- Optional count (e.g., "Payment History (12)")
- Optional collapse/expand toggle
5. Data Patterns
5.1 Stripe Data Fetching
Two strategies depending on context:
| Context | Strategy | Latency | Staleness |
|---|---|---|---|
| Student Detail > Payments tab | Real-time Stripe API call | 1-3 seconds | Fresh |
| Dashboard alert tiles | Webhook-cached in local billing_alerts table |
<100ms | Up to event delay |
| Payment Overview metrics | Webhook-cached + periodic sync | <100ms | Up to 15 min |
5.2 Customer ID Resolution
When looking up a student's Stripe data, follow this chain:
- Check
users.stripe_customer_id(populated for admin-created subscriptions) - If empty, look up
subscription_signups.pp_customer_idin FE database byuser_id - If neither exists, search Stripe by email via
Customer::search() - If found, backfill
users.stripe_customer_idfor future lookups
5.3 Data Staleness Indicators
When displaying Stripe data fetched in real-time:
- Show "Updated just now" on successful fetch
- Show "Last updated: [time ago]" for cached data
- Show warning icon + "Could not reach Stripe — showing cached data" on API failure with fallback
5.4 Error Handling for External APIs
- Stripe timeout: Show cached data (if available) with warning banner. If no cached data, show error state: "Unable to load billing data. Stripe may be temporarily unavailable." with "Retry" button.
- Partial data: If subscription loads but invoices fail, show subscription data with "Payment history unavailable" message in that section.
- Permission denied: Show "You don't have permission to view this data" (should not occur based on role matrix, but handle defensively).
6. Inline Documentation Pattern
Addresses interview finding: "Navigation is unintuitive and undocumented" and admin's request for "clear inline documentation explaining what each feature does."
6.1 Tooltips
- Trigger: hover over info icon (ⓘ) next to field labels
- Content: 1-2 sentence explanation of what the field does and what it affects
- Example: Registration Status ⓘ → "When enabled, students can register for this semester. Does not make the semester active — use 'Active Semester' toggle for that."
6.2 Field-Level Help Text
- Small gray text below input fields in forms
- Explains format, constraints, or downstream effects
- Example: below "Content Availability Date" input → "Students will see this semester's content starting on this date, even if the semester start date is later."
6.3 Section Documentation Blocks
- Collapsible "How does this work?" block at top of complex screens
- Contains 2-3 sentences explaining the screen's purpose and typical workflow
- Collapsed by default after first visit (user preference stored)
- Example on End Checklist tab: "This checklist tracks end-of-semester tasks. Items may complete automatically via backend triggers or require manual confirmation. Check off items as they are verified."
6.4 Transition Hints (First 30 Days)
- For the first 30 days after launch, relocated screens show a subtle hint: "Formerly under [Previous Section]"
- Example: On Tags page → "Formerly under Settings"
- Dismissable per user; auto-expires after 30 days
7. Key Business Rules (Reference)
Preserved from the current system. Domain specs must enforce these:
- Semester protection: Cannot delete a semester if students are enrolled
- Level progression: Level 0 → 1 → 2 → 3 → 4 → Year 2 (no skipping)
- Year 2 enrollment: Level 4 students must explicitly sign up for Year 2
- Passing threshold: 3.5 marks minimum to pass
- Submission flow: Under Review (0) → Reviewed (1) by TA
- Content scoping: All content is scoped to a semester
- Registration vs Active: Registration can be open before semester becomes active
- Active semester:
is_current = 1determines which semester's content is served - TA assignment: Can be automated based on Teacher Assignment Criteria rules
- Password generation: Auto-generated, 4 characters, lowercase only
- Linked deactivation (NEW): Deactivating a student with an active subscription must cancel the Stripe subscription in the same operation