- Onboarding steps guide toward copilot usage, not flow building - Mobile nav updated to match sidebar (Session History, Guided Flows) - Remove Step Library from mobile nav - Remove Maintenance from flow type filter tabs - Remove Maintenance badge from all tree views (grid, list, table) - Remove Maintenance create option from CreateFlowDropdown - Add copilot-first dashboard plan and solutions library spec docs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
161 lines
5.5 KiB
TypeScript
161 lines
5.5 KiB
TypeScript
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: 'ran_session', label: 'Try troubleshooting a ticket', path: '/' },
|
|
{ key: 'exported_session', label: 'Review your session notes', path: '/sessions' },
|
|
{ key: 'created_flow', label: 'Explore guided flows', path: '/trees' },
|
|
{ key: 'tried_ai_assistant', label: 'Check out the Script Builder', path: '/script-builder' },
|
|
]
|
|
|
|
const TEAM_ITEMS: ChecklistItem[] = [
|
|
{ key: 'ran_session', label: 'Try troubleshooting a ticket', path: '/' },
|
|
{ key: 'exported_session', label: 'Review your session notes', path: '/sessions' },
|
|
{ key: 'invited_teammate', label: 'Invite a team member', path: '/account' },
|
|
{ key: 'created_flow', label: 'Explore guided flows', path: '/trees' },
|
|
{ key: 'connected_psa', label: 'Connect your PSA', path: '/account/integrations' },
|
|
]
|
|
|
|
export function OnboardingChecklist() {
|
|
const navigate = useNavigate()
|
|
const [status, setStatus] = useState<OnboardingStatus | null>(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 (
|
|
<div className="card-interactive overflow-hidden fade-in" style={{ animationDelay: '150ms' }}>
|
|
{/* Progress bar */}
|
|
<div className="h-1 w-full bg-[rgba(255,255,255,0.04)]">
|
|
<div
|
|
className="h-full bg-primary transition-all duration-500 ease-out"
|
|
style={{ width: `${progressPercent}%` }}
|
|
/>
|
|
</div>
|
|
|
|
<div className="p-4">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between mb-3">
|
|
<div>
|
|
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
|
Getting Started
|
|
</p>
|
|
<p className="text-sm text-foreground mt-0.5">
|
|
{isAllDone ? (
|
|
<span className="text-accent-text font-semibold">You're all set!</span>
|
|
) : (
|
|
<span>
|
|
<span className="text-accent-text font-semibold">{completedCount}</span>
|
|
{' '}of {totalCount} complete
|
|
</span>
|
|
)}
|
|
</p>
|
|
</div>
|
|
<button
|
|
onClick={handleDismiss}
|
|
className="rounded-md p-1 text-muted-foreground hover:text-foreground hover:bg-[rgba(255,255,255,0.04)] transition-colors"
|
|
aria-label="Dismiss onboarding checklist"
|
|
>
|
|
<X size={16} />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Checklist items */}
|
|
<ul className="space-y-1">
|
|
{items.map((item) => {
|
|
const done = status[item.key]
|
|
return (
|
|
<li key={item.key}>
|
|
<button
|
|
onClick={() => !done && navigate(item.path)}
|
|
disabled={done}
|
|
className={cn(
|
|
'w-full flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors text-left',
|
|
done
|
|
? 'cursor-default'
|
|
: 'hover:bg-[rgba(255,255,255,0.04)]'
|
|
)}
|
|
>
|
|
{/* Checkbox */}
|
|
<span
|
|
className={cn(
|
|
'flex h-5 w-5 shrink-0 items-center justify-center rounded-md border transition-colors',
|
|
done
|
|
? 'bg-primary border-transparent'
|
|
: 'border-border'
|
|
)}
|
|
>
|
|
{done && <Check size={12} className="text-white" />}
|
|
</span>
|
|
|
|
{/* Label */}
|
|
<span
|
|
className={cn(
|
|
'flex-1',
|
|
done
|
|
? 'text-muted-foreground line-through'
|
|
: 'text-foreground'
|
|
)}
|
|
>
|
|
{item.label}
|
|
</span>
|
|
|
|
{/* Arrow for incomplete items */}
|
|
{!done && (
|
|
<ChevronRight size={14} className="text-muted-foreground shrink-0" />
|
|
)}
|
|
</button>
|
|
</li>
|
|
)
|
|
})}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|