Skip to content

Admin UI Guide

Multi-tenant clinic management with RBAC authentication

Version: 4.0.0 Updated: 2026-02-15


Overview

The VitaraVox Admin UI is a React 18 single-page application providing self-service clinic management, EMR integration, onboarding, clinical control, support ticketing, and system administration. It supports two distinct user roles with separate dashboards and navigation.

Stack: React 18.2 + TypeScript + Vite 5 + Tailwind CSS 3.4 + React Router 6 + Lucide Icons + Radix UI


User Roles

Role Prefix Access Pages
Clinic Manager /clinic/* Own clinic only 12 pages
Vitara Admin /admin/* All clinics, system settings 8 pages

Authentication

JWT-Based Authentication

  • Access tokens: 1 hour expiry (HS256)
  • Refresh tokens: 7 days expiry
  • Password hashing: bcrypt with cost factor 12
  • Password max-length: 128 characters (bcrypt DoS prevention)
  • Account lockout: 5 failed attempts = 15 minute lock
  • Token storage: In-memory via Zustand auth store
  • Auto-refresh: Transparent token renewal on 401 responses

Login Flow

  1. User submits email/password to POST /api/auth/login
  2. Server validates against users table (bcrypt compare)
  3. Returns JWT access token + refresh token + user profile
  4. useAuth() context stores tokens and user state
  5. All API requests include Authorization: Bearer {token}
  6. Role-based redirect: vitara_admin -> /admin, clinic_manager -> /clinic

Clinic Manager Features

Dashboard (/clinic)

  • Stats cards: Total calls, appointments booked, patients registered, active waitlist
  • Recent calls: Last 5 call log entries with intent/outcome badges
  • Setup banner: "Complete your clinic setup" CTA for clinics with incomplete onboarding

Onboarding Wizard (/clinic/onboarding)

4-step guided setup for new clinics:

Step Page Fields
1. Clinic Info Name, phone, address, city, province, postal code, timezone Auto-saves on blur
2. EMR Connection EMR type selector (OSCAR / TELUS PS Suite / Accuro / Other with "coming soon" badges), OSCAR credential form (URL, username, password), "Test Connection" button with auto-pull of OSCAR config on success. Non-OSCAR EMR types display "Contact support for integration" message Live connection feedback
3. Business Hours Explanatory text ("Typical clinic hours are Mon–Fri 8 AM – 5 PM"), 7-day grid with open/close time selectors, toggle per day Visual schedule grid
4. Validation 6 parallel integration checks run on entry: EMR Connection, Provider Roster, Schedule Codes, Appointment Types, Sample Availability, Patient Search. EMR Connection and Provider Roster are required (block completion). Others are informational (shown but do not block). Expandable data panels for each check. "Complete Onboarding" button triggers admin notification via POST /api/clinic/onboarding/complete Real-time check status

Pre-launch checklist (10 checks — 7 blocking + 3 informational):

Blocking (must pass before go-live):

  1. Clinic info complete (name, phone, address, timezone)
  2. Business hours configured (at least 1 day open)
  3. At least 1 active provider with OSCAR ID
  4. EMR connection verified
  5. Vapi squad assigned and verified
  6. Privacy officer designated
  7. Credentials encrypted

Informational (do not block go-live):

  1. Test call passed (recommended)
  2. Schedule data flow (adapter returns well-shaped slots)
  3. OSCAR config synced (pulled within 7 days)

Settings Hub (/clinic/settings)

Sub-page Route Features
Schedule Settings /clinic/settings/schedule Business hours grid, holiday management, advance booking limits
Provider Settings /clinic/settings/providers Provider list, EMR sync, edit details, status management
EMR Configuration /clinic/settings/emr OSCAR connection, credentials, test connection, sync status
Clinical Settings /clinic/settings/clinical Custom greeting (EN/ZH), default provider, pharmacy, appointment type mappings
OSCAR Config /clinic/settings/oscar-config Schedule codes, appointment types, config pull, sync status

Call History (/clinic/calls)

  • Paginated call log table with date range filter
  • Intent and outcome badge columns
  • Duration, language, transferred status
  • Click-through to call detail

Waitlist (/clinic/waitlist)

  • Patient waitlist table with status management
  • Bulk operations (contact, register)
  • Add new entries manually
  • Status workflow: pending -> contacted -> registered

Analytics (/clinic/analytics)

  • Call volume over time (chart)
  • Intent distribution
  • Outcome breakdown
  • Language split

Support (/clinic/support)

  • Ticket list: Status and priority filter pills, pagination
  • Create ticket: Modal with title, description, priority dropdown
  • Ticket detail: Threaded message view with reply input
  • Route: /clinic/support (list) and /clinic/support/:id (detail)

Vitara Admin Features

Dashboard (/admin)

  • System statistics: Total clinics, active clinics, total users, total calls
  • Clinics Pending Activation: Amber card listing clinics with status=pending AND onboardingProgress.completedAt set. Shows clinic name, completion date, phone status badge, and "Review" button linking to /admin/clinics/:id
  • Quick actions: Links to clinic management, support tickets
  • System health: Database, OSCAR Bridge, Vapi connectivity status

Clinic Management (/admin/clinics)

Feature Description
List Clinics Paginated table with search and status filter
Create Clinic Modal: clinic name, manager email, manager password. Transaction creates Clinic + User + ClinicConfig + OnboardingProgress + 7 ClinicHours
Clinic Detail Status, EMR config, Vapi mapping, provider list. When status=pending AND onboarding is completed, shows Admin Provisioning Checklist: Phone number assigned (yes/no), Squad linked (yes/no). "Activate Clinic" button enabled when both phone and squad are assigned. Calls PUT /api/admin/clinics/:id/activate

Support Tickets (/admin/support)

Feature Description
All tickets System-wide ticket list with status + priority filters
Ticket detail Full message thread with reply
Status controls Dropdown to change ticket status and priority
Internal notes Toggle checkbox with Lock icon. Internal messages highlighted in amber and labeled "Internal Note". Hidden from clinic managers
Assignment Assign ticket to specific admin user

Phone Number Management (/admin/phones)

Centralized management of Vapi phone numbers and their clinic assignments.

Stat Cards:

Card Description
Total Phone Numbers Count of all Vapi phone numbers
Assigned Phone numbers currently linked to a clinic
Available Phone numbers not yet assigned
Clinics Without Phone Clinics that have no Vapi phone number assigned

Clinic Assignments Table:

Column Description
Clinic Name Name of the clinic
Assigned Phone Vapi phone number linked to this clinic (or "No phone assigned")
Actions Assign (if no phone) or Unassign (if phone linked)

Vapi Phone Numbers Table:

Column Description
Phone Number E.164 formatted number
Name Display name from Vapi
Provider Telephony provider (e.g., Telnyx)
Assigned Clinic Clinic name if assigned, or "Unassigned"
Created Date the number was provisioned

Assign Modal:

  • Triggered by "Assign" button on a clinic row
  • Dropdown lists only unassigned Vapi phone numbers
  • On confirm, links the selected phone number to the clinic
  • Updates both tables immediately on success

Placeholder Pages

The following admin pages exist with placeholder content (future implementation):

  • Users (/admin/users) — User management
  • System Settings (/admin/settings) — Global configuration
  • Audit Logs (/admin/audit) — Audit log viewer
  • API Keys (/admin/api-keys) — API key management

Layout Structure

┌──────────────────────────────────────────────────────────────────┐
│ Header                                                            │
│  [Hamburger]           [NotificationBell] [Role Label] [Logout]  │
├──────────┬───────────────────────────────────────────────────────┤
│ Sidebar  │  Page Content (<Outlet />)                             │
│          │                                                        │
│ Logo     │  Renders the active route's page component             │
│ Nav:     │                                                        │
│ Dashboard│                                                        │
│ Settings▼│                                                        │
│  Schedule│                                                        │
│  Provider│                                                        │
│  EMR     │                                                        │
│  Clinical│                                                        │
│  OSCAR   │                                                        │
│ Calls    │                                                        │
│ Waitlist │                                                        │
│ Analytics│                                                        │
│ Support  │                                                        │
│          │                                                        │
│ [User]   │                                                        │
└──────────┴───────────────────────────────────────────────────────┘
  • Sidebar: Collapsible on mobile (hamburger toggle), always visible on lg: breakpoint
  • Settings submenu: Expandable chevron with 5 children (Schedule, Providers, EMR, Clinical, OSCAR Config)
  • Notification bell: Unread count badge (red), dropdown with notifications, 30-second polling, mark all read
  • Responsive: Mobile-friendly with sm: / md: / lg: breakpoints throughout

Notification System

The notification bell in the header provides real-time awareness of support ticket activity.

Feature Implementation
Bell icon Lucide Bell, always visible in header
Unread badge Red circle, shows count (capped at "9+")
Dropdown Click to open, shows recent notifications with relative timestamps
Click-through Each notification links to relevant page (e.g., ticket detail)
Mark all read Button at top of dropdown
Polling Fetches GET /api/notifications every 30 seconds

Auto-creation triggers:

Event Who Gets Notified
Clinic creates ticket All admin users
Admin replies to ticket Ticket creator
Clinic replies to ticket Assigned admin (or all admins)
Status change Ticket creator

API Integration

Client API Service

The api.ts service provides a typed ApiClient class with 50+ methods:

// Authentication
api.login(email, password)
api.logout()
api.refreshToken()
api.getMe()

// Clinic Manager
api.getClinicMe()
api.getClinicStats()
api.updateClinicalSettings(data)
api.getProviders()
api.syncProvidersFromEmr()
api.getWaitlist(page, pageSize)
api.getCallHistory(page, pageSize, filters)
api.getOnboardingProgress()
api.updateOnboardingStep(step, data)
api.validatePreLaunch()
api.goLive()

// Support
api.createTicket(data)
api.getTickets(params)
api.getTicket(id)
api.addTicketMessage(ticketId, data)

// Admin
api.getAdminTickets(params)
api.getAdminTicket(id)
api.updateAdminTicket(id, data)
api.addAdminTicketMessage(ticketId, data)

// Vapi Phone Management
api.getVapiPhoneNumbers()
api.assignPhoneToClinic(clinicId, phoneData)
api.unassignPhoneFromClinic(clinicId)

// Onboarding validation
api.testScheduleSlots()
api.testPatientSearch()
api.completeOnboarding()
api.getOscarConfig()
api.pullOscarConfig()

// Admin provisioning
api.getProvisioningStatus(clinicId)
api.activateClinic(clinicId)
api.getPendingActivationClinics()

// Notifications
api.getNotifications()
api.markNotificationRead(id)
api.markAllNotificationsRead()

All methods automatically:

  • Include Authorization: Bearer {token} header
  • Parse JSON responses
  • Throw on non-2xx status codes

Auth Store

Zustand-based store (stores/auth.tsx) providing:

  • user — Current user profile
  • isAuthenticated — Login state
  • isLoading — Initial auth check
  • login(email, password) — Authenticate and store tokens
  • logout() — Clear tokens and redirect
  • Auto-refresh on app mount

API Endpoints

Authentication (/api/auth/*)

Method Endpoint Description
POST /api/auth/login Login, returns JWT + refresh token
POST /api/auth/refresh Renew access token
GET /api/auth/me Current user profile
POST /api/auth/logout Invalidate session

Clinic Manager (/api/clinic/*)

Method Endpoint Description
GET /api/clinic/me Own clinic details
GET /api/clinic/stats Dashboard statistics
GET/PUT /api/clinic/settings Clinic settings
GET/POST /api/clinic/providers Provider management
POST /api/clinic/providers/emr-sync Sync providers from OSCAR
PUT/DELETE /api/clinic/providers/:id Edit/delete provider
GET/POST /api/clinic/schedule Business hours
GET /api/clinic/emr/config EMR configuration
PUT /api/clinic/emr/config Update EMR config
POST /api/clinic/emr/test-connection Test OSCAR connection
GET/POST/PUT /api/clinic/waitlist Waitlist CRUD
GET /api/clinic/call-history Paginated call logs
GET /api/clinic/onboarding Onboarding progress
PUT /api/clinic/onboarding/step/:step Update step
GET /api/clinic/onboarding/validate Pre-launch checklist
POST /api/clinic/onboarding/go-live Activate clinic
POST /api/clinic/onboarding/complete Complete onboarding, notify admins
POST /api/clinic/onboarding/test-slots Test schedule slot retrieval
POST /api/clinic/onboarding/test-patient-search Test patient search
GET /api/clinic/oscar-config Get OSCAR config (codes, types)
POST /api/clinic/oscar-config/pull Pull config from OSCAR
PUT /api/clinic/clinical-settings Greeting, pharmacy, appt types
PUT /api/clinic/privacy-officer Privacy officer designation
GET /api/clinic/baa-status BAA/DPA status
PUT /api/clinic/data-retention Retention settings
POST /api/clinic/support/tickets Create support ticket
GET /api/clinic/support/tickets List own tickets
GET /api/clinic/support/tickets/:id Ticket detail
POST /api/clinic/support/tickets/:id/messages Reply to ticket

Vitara Admin (/api/admin/*)

Method Endpoint Description
GET /api/admin/clinics List all clinics
POST /api/admin/clinics Create clinic + user + config
GET /api/admin/clinics/:id Clinic detail
GET /api/admin/stats System-wide statistics
GET /api/admin/health System health
GET /api/admin/health/detailed Detailed health with latency
GET /api/admin/vapi/mappings Vapi-clinic mappings
GET /api/admin/vapi/phone-numbers List all Vapi phone numbers (admin only)
GET /api/admin/audit Audit logs (paginated, filtered)
POST /api/admin/data-retention/run Manual retention trigger
GET /api/admin/support/tickets All tickets (system-wide)
GET /api/admin/support/tickets/:id Ticket detail (with internal notes)
PUT /api/admin/support/tickets/:id Update status/priority/assignee
POST /api/admin/support/tickets/:id/messages Reply (supports isInternal)
GET /api/admin/clinics/pending-activation List clinics pending activation
GET /api/admin/clinics/:id/provisioning Get provisioning status
PUT /api/admin/clinics/:id/activate Activate a clinic

Notifications (/api/notifications/*)

Method Endpoint Description
GET /api/notifications Current user's notifications
PUT /api/notifications/read-all Mark all as read
PUT /api/notifications/:id/read Mark single as read

Environment Variables

Variable Default Description
JWT_SECRET (required) Access token signing key
JWT_REFRESH_SECRET (required) Refresh token signing key
ENCRYPTION_KEY (required in prod) AES-256-GCM for credential encryption
DATABASE_URL (required) PostgreSQL connection string
PORT 3002 Server port

Security Features

Feature Implementation
Password hashing bcrypt with cost factor 12
JWT tokens HS256 algorithm, short-lived (1h access, 7d refresh)
Account lockout 5 failed attempts = 15 min lock
Audit logging All POST/PUT/DELETE auto-logged via middleware
RBAC requireRole() and requireClinicAccess middleware
Input validation Zod schemas with .strict() on all endpoints
Credential encryption AES-256-GCM for OSCAR secrets
Rate limiting 5/min auth, 100/min API, 300/min webhooks
Security headers Helmet (CSP, HSTS, X-Frame-Options)
Internal notes isInternal enforced server-side (not client trust)
Data retention Automated transcript/log purge (configurable per clinic)