Phase 2 of hospital onboarding & self-service plan
(docs/superpowers/plans/2026-04-06-hospital-onboarding-self-service.md
checked in here for the helix-engage repo).
Frontend foundations for the staff-portal Settings hub and 6-step setup
wizard. Backend was Phase 1 (helix-engage-server commit).
New shared components (src/components/setup/):
- wizard-shell.tsx — fullscreen layout with left step navigator, progress
bar, and Skip-for-now affordance
- wizard-step.tsx — single-step wrapper with Mark Complete + Prev/Next/
Finish navigation, completion badge
- section-card.tsx — Settings hub card with title/description/icon, links
to a section page, optional status badge mirroring setup-state
New pages:
- pages/setup-wizard.tsx — top-level /setup route, fullscreen (no AppShell),
loads setup-state from sidecar, renders the active step. Each step has a
placeholder body for now; Phase 5 swaps placeholders for real form
components from the matching settings pages. Already functional end-to-end:
Mark Complete writes to PUT /api/config/setup-state/steps/<step>, Skip
posts to /dismiss, Finish navigates to /.
- pages/team-settings.tsx — moved the existing workspace member listing out
of the old monolithic settings.tsx into its own /settings/team route. No
functional change; Phase 3 will add the invite form + role editor here.
- pages/settings-placeholder.tsx — generic "Coming in Phase X" stub used by
routes for clinics, doctors, telephony, ai, widget until those pages land.
Modified pages:
- pages/settings.tsx — rewritten as the Settings hub (the new /settings
route). Renders SectionCards in 3 groups (Hospital identity, Care
delivery, Channels & automation) with completion badges sourced from
/api/config/setup-state. The hub links to existing pages (/branding,
/rules) and to placeholder pages for the not-yet-built sections.
- pages/login.tsx — after successful login, calls getSetupState() and
redirects to /setup if wizardRequired. Failures fall through to / so an
older sidecar without the setup-state endpoint still works.
- components/layout/sidebar.tsx — collapsed the Configuration group
(Rules Engine + Branding standalone entries) into the single Settings
entry that opens the hub. Removes the IconSlidersUp import that's no
longer used.
New types and helpers (src/lib/setup-state.ts):
- SetupState / SetupStepName / SetupStepStatus types mirroring the sidecar
shape
- SETUP_STEP_NAMES constant + SETUP_STEP_LABELS map (title + description
per step) — single source of truth used by the wizard, hub, and any
future surface that wants to render step metadata
- getSetupState / markSetupStepComplete / markSetupStepIncomplete /
dismissSetupWizard / resetSetupState helpers wrapping the api-client
Other:
- lib/api-client.ts — added apiClient.put() helper for the setup-state
step update mutations (PUT was the only verb missing from the existing
get/post/graphql helpers)
- main.tsx — registered new routes:
/setup (fullscreen, no AppShell)
/settings (the hub, replaces old settings.tsx)
/settings/team (moved member listing)
/settings/clinics (placeholder, Phase 3)
/settings/doctors (placeholder, Phase 3)
/settings/telephony (placeholder, Phase 4)
/settings/ai (placeholder, Phase 4)
/settings/widget (placeholder, Phase 4)
Tested via npx tsc --noEmit and npm run build (clean, only pre-existing
chunk-size and dynamic-import warnings unrelated to this change).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Helix Engage — Frontend
Call center CRM frontend for healthcare lead management. Built on the FortyTwo platform.
Owner: Mouli
Architecture
┌─────────────────────┐ ┌──────────────────────┐ ┌─────────────────────┐
│ helix-engage │ │ helix-engage-server │ │ FortyTwo Platform │
│ (this repo) │────▶│ (sidecar) │────▶│ (backend) │
│ React frontend │ │ NestJS REST API │ │ GraphQL API │
│ Port 5173 (dev) │ │ Port 4100 │ │ Port 4000 │
└─────────────────────┘ └──────────────────────┘ └─────────────────────┘
│ │
│ SIP/WebRTC │ Ozonetel CloudAgent APIs
▼ ▼
┌───────────┐ ┌──────────────┐
│ Ozonetel │ │ Ozonetel │
│ SIP (444) │ │ REST APIs │
└───────────┘ └──────────────┘
Three repos:
| Repo | Purpose | Owner |
|---|---|---|
helix-engage (this) |
React frontend | Mouli |
helix-engage-server |
NestJS sidecar — Ozonetel + Platform bridge | Karthik |
helix-engage-app |
FortyTwo SDK app — entity schemas (Call, Lead, etc.) | Shared |
Getting Started
npm install
npm run dev # http://localhost:5173
npm run build # TypeScript check + production build
Environment Variables (set at build time or in .env)
| Variable | Purpose | Dev Default | Production |
|---|---|---|---|
VITE_API_URL |
Platform GraphQL | http://localhost:4000 |
https://engage-api.srv1477139.hstgr.cloud |
VITE_SIDECAR_URL |
Sidecar REST API | http://localhost:4100 |
https://engage-api.srv1477139.hstgr.cloud |
VITE_SIP_URI |
Ozonetel SIP URI | — | sip:523590@blr-pub-rtc4.ozonetel.com |
VITE_SIP_PASSWORD |
SIP password | — | 523590 |
VITE_SIP_WS_SERVER |
SIP WebSocket | — | wss://blr-pub-rtc4.ozonetel.com:444 |
Production build command:
VITE_API_URL=https://engage-api.srv1477139.hstgr.cloud \
VITE_SIDECAR_URL=https://engage-api.srv1477139.hstgr.cloud \
VITE_SIP_URI=sip:523590@blr-pub-rtc4.ozonetel.com \
VITE_SIP_PASSWORD=523590 \
VITE_SIP_WS_SERVER=wss://blr-pub-rtc4.ozonetel.com:444 \
npm run build
Tech Stack
- React 19 + TypeScript + Vite
- Tailwind CSS 4 with semantic color tokens (
text-primary,bg-brand-section— never raw colors liketext-gray-900) - React Aria Components for accessibility (imports always prefixed
Aria*) - Jotai for SIP/call state
- React Context for auth, data, theme
- FontAwesome Pro Duotone icons
- Untitled UI component library (
src/components/base/,src/components/application/)
Project Structure
src/
├── pages/ # Route-level pages
│ ├── call-desk.tsx # Main CC agent workspace — THE CORE PAGE
│ ├── login.tsx # Auth page (centered card on blue bg)
│ ├── call-history.tsx # CDR log viewer
│ ├── my-performance.tsx # Agent KPI dashboard
│ ├── team-dashboard.tsx # Supervisor overview
│ ├── all-leads.tsx # Lead master table
│ └── campaigns.tsx # Campaign listing
│
├── components/
│ ├── call-desk/ # ⚡ Call center components — WHERE MOST WORK HAPPENS
│ │ ├── active-call-card.tsx # In-call UI + post-call disposition flow
│ │ ├── worklist-panel.tsx # Agent task queue with tabs + sub-tabs
│ │ ├── context-panel.tsx # AI assistant + Lead 360 sidebar
│ │ ├── disposition-form.tsx # Post-call outcome selector
│ │ ├── appointment-form.tsx # Book appointment during/after call
│ │ ├── agent-status-toggle.tsx # Ready/Break/Training/Offline toggle
│ │ ├── transfer-dialog.tsx # Call transfer
│ │ ├── enquiry-form.tsx # General enquiry capture
│ │ ├── live-transcript.tsx # Real-time transcription (Deepgram)
│ │ └── phone-action-cell.tsx # Click-to-call in table rows
│ ├── base/ # Untitled UI primitives (Button, Input, Select, Badge)
│ ├── application/ # Complex UI (Table, Modal, Tabs, DatePicker, Nav)
│ ├── layout/ # Sidebar — role-based navigation
│ └── dashboard/ # KPI cards, charts, missed queue widget
│
├── providers/
│ ├── sip-provider.tsx # SIP WebRTC — call lifecycle management
│ ├── auth-provider.tsx # User session, roles (executive/admin/cc-agent)
│ ├── data-provider.tsx # Bulk entity loader (leads, campaigns, calls)
│ └── theme-provider.tsx # Light/dark mode
│
├── hooks/
│ ├── use-worklist.ts # Polls sidecar /api/worklist every 30s
│ ├── use-call-assist.ts # Live transcript via Socket.IO
│ └── use-sip-phone.ts # Low-level SIP.js wrapper
│
├── lib/
│ ├── api-client.ts # REST + GraphQL client (auth, queries, sidecar calls)
│ ├── queries.ts # Platform GraphQL query strings
│ └── format.ts # Phone/date formatting
│
├── state/
│ └── sip-state.ts # Jotai atoms (callState, callerNumber, isMuted, etc.)
│
└── types/
└── entities.ts # Lead, Patient, Call, Appointment, etc.
Troubleshooting Guide — Where to Look
"The call desk isn't working"
File: src/pages/call-desk.tsx
This is the orchestrator. It uses useSip() for call state, useWorklist() for the task queue, and renders either ActiveCallCard (in-call) or WorklistPanel (idle). Start here, then drill into whichever child component is misbehaving.
"Calls aren't connecting / SIP errors"
File: src/providers/sip-provider.tsx + src/state/sip-state.ts
Check VITE_SIP_* env vars. Ozonetel SIP WebSocket runs on port 444 — VPNs block it. If WebSocket hangs at "connecting", turn off VPN. Also check browser console for SIP.js registration errors.
"Worklist not loading / empty"
File: src/hooks/use-worklist.ts
This polls GET /api/worklist on the sidecar every 30s. Open browser Network tab → filter for /api/worklist. Common causes: sidecar is down, auth token expired, or agent name doesn't match any assigned leads.
"Missed calls not appearing / sub-tabs empty"
File: src/components/call-desk/worklist-panel.tsx
Missed calls come from the sidecar worklist response. The sub-tabs filter by callbackstatus field. If all sub-tabs are empty, the sidecar ingestion may not be running (check sidecar logs for MissedQueueService).
"Disposition / appointment not saving"
File: src/components/call-desk/active-call-card.tsx → handleDisposition()
Posts to sidecar POST /api/ozonetel/dispose. Errors are caught silently (non-blocking). Check browser Network tab for the dispose request/response, then check sidecar logs.
"Login broken / Failed to fetch"
File: src/pages/login.tsx + src/lib/api-client.ts
Login calls apiClient.login() → sidecar /auth/login → platform GraphQL. Most common cause: wrong VITE_API_URL (built with localhost instead of production URL). Always set env vars at build time.
"UI component looks wrong"
Files: src/components/base/ (primitives), src/components/application/ (complex)
These come from the Untitled UI library. Design tokens are in src/styles/theme.css. Brand colors were rebuilt from logo blue rgb(32, 96, 160).
"Navigation / role-based access"
File: src/components/layout/sidebar.tsx
Navigation groups are defined per role (admin, cc-agent, executive). Routes are registered in src/main.tsx.
Data Flow
User action
│
▼
Component (e.g. ActiveCallCard)
│
├──▶ Sidecar REST API (via apiClient.post/get)
│ e.g. /api/ozonetel/dispose, /api/worklist
│
├──▶ Platform GraphQL (via apiClient.graphql)
│ e.g. leads, appointments, patients queries
│
└──▶ SIP.js (via useSip() hook)
Call control: answer, hangup, mute, hold
Key pattern: The frontend talks to TWO backends:
- Sidecar (REST) — for Ozonetel telephony operations and worklist
- Platform (GraphQL) — for entity CRUD (leads, appointments, patients)
Conventions
- File naming: kebab-case (
worklist-panel.tsx) - Colors: Semantic tokens only (
text-primary,bg-brand-section) - Icons:
@fortawesome/pro-duotone-svg-icons+faIcon()wrapper insrc/lib/icon-wrapper.ts - React Aria: Always prefix imports (
Button as AriaButton) - Transitions:
transition duration-100 ease-linear
Git Workflow
dev— active developmentmaster— stable baseline- Always build with production env vars before deploying