UI Architecture
Components, hooks, and data flow in the leads frontend
Page structure
The leads page lives at /agent/leads (route: app/(nav)/(private)/agent/leads/page.tsx). It renders the LeadsPage component which has a split layout: lead table on the left, detail panel on the right.
Tabs
The page has tabs that vary by role:
| Tab | Who sees it | Component |
|---|---|---|
| Leads | Everyone | Lead table with filters |
| Feed | Everyone | Activity feed across all leads |
| Referrals (Old) | Everyone | Legacy referrals view |
| Distribute | Admins/managers | Bulk lead assignment |
| Export | Admins/managers | CSV export with history |
Key components
leads-page.tsx
The main container. Manages:
- Active tab state
- Selected lead ID
- Filter state (stages, types, states, search query)
- Modal visibility
Renders the lead table, detail panel (when a lead is selected), and tab content.
Lead table (in leads-page.tsx)
Uses the design system Table component. Columns: Name, Stage (color-coded badge), Type, State, Contact (phone/email icons), Created, Last Activity (via LiveTimeSince).
lead-details.tsx
The detail panel for a single lead. Tabbed interface:
| Tab | What it shows |
|---|---|
| Actions | Stage-aware action buttons (call, schedule, present, resolve) |
| Demographics | Primary/secondary insured info |
| Outcome | Sale details table (ALP/AHP summary + product breakdown) |
| Appointments | Appointment history with status badges |
| Activity | Full timeline of every action |
| Admin | Field editing (admin only) |
| Debug | Raw JSON dump |
The Actions tab adapts based on the lead's current stage — different buttons appear for new vs scheduled vs no_show.
Action modals
| Modal | File | When used |
|---|---|---|
outgoing-contact-modal.tsx | Agent initiates contact | Shows method (call/text/email), outcome options, optional appointment picker |
received-message-modal.tsx | Lead contacts agent | Similar to outgoing but for inbound |
appointment-modal.tsx | Direct appointment management | Schedule, cancel, no-show, reschedule |
resolve-modal.tsx | Terminal outcomes | Refused, Wrong Number, Unresponsive, DNC |
note-modal.tsx | Add a note | Simple text input |
presentation-start-modal.tsx | Confirm presentation start | Counts toward metrics |
create-lead-modal.tsx | Create a new lead | Name, phone, state, type, etc. |
Supporting detail components
| File | Purpose |
|---|---|
lead-details-overview.tsx | Contact info, status, type cards |
lead-details-activity.tsx | Activity timeline table |
lead-details-demographics.tsx | Primary/secondary insured details |
lead-details-outcome.tsx | Sale summary and product breakdown |
lead-details-appointments.tsx | Appointment history |
lead-details-admin.tsx | Admin field editing |
lead-details-debug.tsx | Raw JSON |
Hooks
useLead(user, leadId)
File: src/components/leads/use-lead.ts
The core hook for a single lead. Fetches the lead, its referral parent, activity history, appointments, and sale details in parallel.
Data returned:
lead— the lead with source imagereferralLead— parent lead if this is a referralleadActivity[]— all activities with agent namesappointments[]— calendar events of typelead_appointmentsaleDetails[]— individual policy records
Methods returned:
addActivity(type, note)— log a simple activityaddActivityWithAppointment(outcome, note, date)— callsmanage_appointmentRPClogContactWithOutcome(method, result, metadata)— callslog_contact_with_outcomeRPClogIncomingContact(method, result, metadata)— callslog_incoming_contactRPCstartPresentation()— opens the presentation pop-out windowfetchLead()— manual refresh
Auto-refreshes on window focus (so data updates after returning from the presentation window).
useLeads(user, options)
File: src/components/leads/use-leads.ts
Lists and filters leads with pagination (30 per page).
Filter options: agentIds, agencyId, stages, types, states, searchQuery, unassigned
Search is case-insensitive across first_name, last_name, phone, and email.
Methods: fetchLeads(), loadMore(), createLead(data)
useLeadExports(agencyId, filterTypes, filterStates)
File: src/components/leads/use-lead-exports.ts
Manages CSV exports. Tracks which leads have been exported to avoid duplicates.
Methods: exportFiltered(), redownloadExport(record), createExport(leadIds, filters, filename)
useLeadTeamUsers(user, isAdmin)
File: src/components/leads/use-lead-team-users.ts
Fetches team members for the distribution view. Admins get all agency profiles; non-admins use get_users_under_profile RPC.
Utility files
| File | Export | Purpose |
|---|---|---|
get-lead-source-img.ts | getLeadSourceImage() | Maps source to icon ('onsidian' → /images/xp.svg) |
is-terminal-status.ts | isTerminalStatus() | Returns true if status === 'resolved' |
is-contacted-status.ts | isContactedStage() | Returns true if stage !== 'new' |
lead-filters-dropdown.tsx | LeadFiltersDropdown | Multi-select filter UI for type and state |
Presentation flow
When an agent starts a presentation from a lead:
startPresentation()inuseLeadchecks thatlead.appt_atexistsopenPresentationFromLead(lead)builds aBasePresentationobject with lead data- Checks local storage for an existing presentation (resume vs. new)
- Opens a pop-out browser window with the presentation controls
- The presentation window calls the
/api/leads/log-presentation-activityendpoint to logpresentation_startedorpresentation_resumed - On completion, the presentation window calls
submit_lead_presentationRPC directly - When the agent returns to the main window,
useLeadauto-refreshes via the window focus listener
Stage colors
Used consistently across the table and detail panel for visual status:
| Stage | Color variable |
|---|---|
new | --primary-blue |
attempted | --yellow |
callback | --yellow |
scheduled | --green |
no_show | --red |
sale | --green |
no_sale | --red |
refused | --red |
wrong_number | --red |
bad_number | --red |
unresponsive | --red |
duplicate | --secondary-text-color |
dnc | --red |