From 58d4aa78e66d97c0d4a4a850ba4b230ca9ff2968 Mon Sep 17 00:00:00 2001 From: DevCalebR Date: Tue, 21 Apr 2026 18:09:25 -0400 Subject: [PATCH] admin: tighten onboarding confidence and daily ops --- app/admin/[businessId]/page.tsx | 203 ++++++- app/admin/actions.ts | 247 +++++++- app/admin/page.tsx | 129 +++- components/admin-business-setup-step-card.tsx | 26 + lib/admin-dashboard.ts | 26 +- lib/admin-operator-proof.ts | 551 ++++++++++++++++++ lib/admin-setup-remediation.ts | 124 +++- lib/twilio-setup.ts | 43 +- lib/validators.ts | 11 + tests/admin-dashboard.test.ts | 46 ++ tests/admin-operator-proof.test.ts | 114 ++++ tests/admin-operator-routes.test.ts | 9 +- tests/admin-setup-remediation.test.ts | 235 +++++++- 13 files changed, 1676 insertions(+), 88 deletions(-) create mode 100644 lib/admin-operator-proof.ts create mode 100644 tests/admin-operator-proof.test.ts diff --git a/app/admin/[businessId]/page.tsx b/app/admin/[businessId]/page.tsx index 0f524c6..7dfc805 100644 --- a/app/admin/[businessId]/page.tsx +++ b/app/admin/[businessId]/page.tsx @@ -3,11 +3,13 @@ import { notFound } from 'next/navigation'; import { archiveBusinessAction, + confirmMissedCallValidationAction, connectExistingBusinessOwnerAction, createBusinessMessagingServiceAction, createBusinessTwilioSubaccountAction, deleteTestBusinessAction, inviteBusinessOwnerAction, + markBusinessLiveAction, provisionBusinessAction, resyncBusinessWebhooksAction, restoreBusinessAction, @@ -24,9 +26,11 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select } from '@/components/ui/select'; +import { Textarea } from '@/components/ui/textarea'; import { buildAdminCustomerOpenHref } from '@/lib/admin-customer-paths'; +import { buildAdminOnboardingConfidence, isBusinessArchived } from '@/lib/admin-dashboard'; +import { buildAdminMissedCallValidationTruth, buildAdminOperationalProofs } from '@/lib/admin-operator-proof'; import { buildAdminNextStepGuide, buildAdminSetupPanels } from '@/lib/admin-setup-remediation'; -import { isBusinessArchived } from '@/lib/admin-dashboard'; import { buildAdminBusinessIssue, buildAdminTestSmsTruth, @@ -182,6 +186,11 @@ export default async function AdminBusinessDetailPage({ ? 'pending_delivery' : testSmsTruth.state; const managedTextingNumber = getManagedTextingNumber(business); + const ownerContact = business.notificationSettings?.ownerPhone || business.notifyPhone || null; + const missedCallValidation = buildAdminMissedCallValidationTruth({ + events: operatorEvents, + successfulLeadCount, + }); const setupFlow = buildTwilioSetupFlow({ business, notificationSettings: business.notificationSettings, @@ -189,6 +198,42 @@ export default async function AdminBusinessDetailPage({ successfulLeadCount, testSmsState, webhookSnapshot, + missedCallValidation: { + complete: missedCallValidation.countsAsLaunchProof, + stateLabel: missedCallValidation.label, + detail: missedCallValidation.detail, + tone: + missedCallValidation.tone === 'success' + ? 'success' + : missedCallValidation.tone === 'attention' + ? 'attention' + : missedCallValidation.tone === 'pending' + ? 'pending' + : 'neutral', + }, + }); + const onboardingConfidence = buildAdminOnboardingConfidence({ + business, + notificationSettings: business.notificationSettings, + ownerConnected: ownerState.connected, + successfulLeadCount, + operatorEvents, + webhookSnapshot, + missedCallValidation, + }); + const { goLiveDecision, proofs } = buildAdminOperationalProofs({ + ownerConnected: ownerState.connected, + ownerEmail: business.notificationSettings?.ownerEmail || null, + ownerPhone: ownerContact, + messagingServiceReady: Boolean(business.twilioMessagingServiceSid), + numberAssigned: Boolean(managedTextingNumber && (business.twilioPrimaryNumberSid || business.twilioPhoneNumberSid)), + testSmsTruth, + missedCallValidation, + webhookSnapshot, + provisioningStatus: business.provisioningStatus, + canSafelyMarkLive: onboardingConfidence.canSafelyMarkLive, + blockers: onboardingConfidence.blockers.map((blocker) => blocker.message), + events: operatorEvents, }); const lastIssue = buildAdminBusinessIssue({ events: operatorEvents, @@ -232,7 +277,10 @@ export default async function AdminBusinessDetailPage({ ownerState, webhookSnapshot, testSmsTruth, - successfulLeadCount, + onboardingConfidence, + missedCallValidation, + goLiveDecision, + proofs, }); const nextStepGuide = buildAdminNextStepGuide({ setupFlow, @@ -252,6 +300,8 @@ export default async function AdminBusinessDetailPage({ const archived = getQueryValue(searchParams, 'archived') === '1'; const restored = getQueryValue(searchParams, 'restored') === '1'; const statusSaved = getQueryValue(searchParams, 'statusSaved'); + const validationSaved = getQueryValue(searchParams, 'validationSaved') === '1'; + const liveAcknowledged = getQueryValue(searchParams, 'liveAcknowledged'); const error = getQueryValue(searchParams, 'error'); function renderAutomaticActions(step: TwilioSetupStep) { @@ -546,31 +596,82 @@ export default async function AdminBusinessDetailPage({ if (step.key === 'missed_call_validated') { return ( -
- - Open customer call flow - - - Open customer workspace - - - Open recent activity - +
+
+ + Open customer call flow + + + Open customer workspace + + + Open recent activity + +
+
+ + +
+ +