diff --git a/frontend/src/api/onboarding.ts b/frontend/src/api/onboarding.ts new file mode 100644 index 00000000..4f54e687 --- /dev/null +++ b/frontend/src/api/onboarding.ts @@ -0,0 +1,21 @@ +import { apiClient } from './client' + +export interface OnboardingStatus { + created_flow: boolean + ran_session: boolean + exported_session: boolean + tried_ai_assistant: boolean + invited_teammate: boolean + connected_psa: boolean + is_team_user: boolean + dismissed: boolean +} + +export async function getOnboardingStatus(): Promise { + const response = await apiClient.get('/users/onboarding-status') + return response.data +} + +export async function dismissOnboarding(): Promise { + await apiClient.post('/users/onboarding-status/dismiss') +} diff --git a/frontend/src/components/dashboard/OnboardingChecklist.tsx b/frontend/src/components/dashboard/OnboardingChecklist.tsx new file mode 100644 index 00000000..f4552204 --- /dev/null +++ b/frontend/src/components/dashboard/OnboardingChecklist.tsx @@ -0,0 +1,160 @@ +import { useState, useEffect } from 'react' +import { useNavigate } from 'react-router-dom' +import { Check, X, ChevronRight } from 'lucide-react' +import { cn } from '@/lib/utils' +import { getOnboardingStatus, dismissOnboarding } from '@/api/onboarding' +import type { OnboardingStatus } from '@/api/onboarding' + +interface ChecklistItem { + key: keyof OnboardingStatus + label: string + path: string +} + +const SOLO_ITEMS: ChecklistItem[] = [ + { key: 'created_flow', label: 'Create your first flow', path: '/trees' }, + { key: 'ran_session', label: 'Run your first session', path: '/trees' }, + { key: 'exported_session', label: 'Export a session', path: '/sessions' }, + { key: 'tried_ai_assistant', label: 'Try the AI assistant', path: '/assistant' }, +] + +const TEAM_ITEMS: ChecklistItem[] = [ + { key: 'created_flow', label: 'Create your first flow', path: '/trees' }, + { key: 'invited_teammate', label: 'Invite a team member', path: '/account' }, + { key: 'ran_session', label: 'Run your first session', path: '/trees' }, + { key: 'connected_psa', label: 'Connect a PSA integration', path: '/account/integrations' }, + { key: 'exported_session', label: 'Export a session', path: '/sessions' }, +] + +export function OnboardingChecklist() { + const navigate = useNavigate() + const [status, setStatus] = useState(null) + const [dismissed, setDismissed] = useState(false) + const [allComplete, setAllComplete] = useState(false) + + useEffect(() => { + getOnboardingStatus() + .then(setStatus) + .catch(() => { + // Silently fail — don't show checklist if endpoint unavailable + }) + }, []) + + const items = status?.is_team_user ? TEAM_ITEMS : SOLO_ITEMS + const completedCount = status + ? items.filter((item) => status[item.key]).length + : 0 + const totalCount = items.length + const isAllDone = completedCount === totalCount && status !== null + + useEffect(() => { + if (isAllDone) { + const timer = setTimeout(() => setAllComplete(true), 2000) + return () => clearTimeout(timer) + } + }, [isAllDone]) + + // Don't render if dismissed, fully complete, or not loaded yet + if (!status || status.dismissed || dismissed || allComplete) return null + + const progressPercent = totalCount > 0 ? (completedCount / totalCount) * 100 : 0 + + const handleDismiss = async () => { + setDismissed(true) + try { + await dismissOnboarding() + } catch { + // Already hidden locally + } + } + + return ( +
+ {/* Progress bar */} +
+
+
+ +
+ {/* Header */} +
+
+

+ Getting Started +

+

+ {isAllDone ? ( + You're all set! + ) : ( + + {completedCount} + {' '}of {totalCount} complete + + )} +

+
+ +
+ + {/* Checklist items */} +
    + {items.map((item) => { + const done = status[item.key] + return ( +
  • + +
  • + ) + })} +
+
+
+ ) +} diff --git a/frontend/src/pages/QuickStartPage.tsx b/frontend/src/pages/QuickStartPage.tsx index ecb71bc8..26278446 100644 --- a/frontend/src/pages/QuickStartPage.tsx +++ b/frontend/src/pages/QuickStartPage.tsx @@ -26,6 +26,7 @@ import { QuickActions } from '@/components/dashboard/QuickActions' import { OpenSessions } from '@/components/dashboard/OpenSessions' import { RecentActivity } from '@/components/dashboard/RecentActivity' import { PreparedSessions } from '@/components/dashboard/PreparedSessions' +import { OnboardingChecklist } from '@/components/dashboard/OnboardingChecklist' function timeAgo(dateStr: string): string { const now = Date.now() @@ -278,6 +279,9 @@ export function QuickStartPage() {

+ {/* Onboarding Checklist */} + + {/* Row 1: Calendar + Quick Actions */}