import { IconChartBar, IconCheck, IconCopy, IconCreditCard, IconKey, IconMail, IconPalette, IconRocket, IconSparkles, } from '@tabler/icons-react'; import { type ComponentType, useEffect, useState } from 'react'; import { Link } from 'react-router'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; import { Skeleton } from '@/components/ui/skeleton'; import { useDocsVisibility } from '@/hooks/api'; import { useSetupStatus } from '@/hooks/api/useSetupStatus'; import { isDocsHrefVisible } from '@/lib/docs-links'; import { commandForStep, type SetupIcon, type SetupIntegration, type SetupStep, } from '@/lib/setup-progress'; import { cn } from '@/lib/utils'; const STEP_ICON: Record> = { sparkles: IconSparkles, paint: IconPalette, mail: IconMail, key: IconKey, chart: IconChartBar, card: IconCreditCard, rocket: IconRocket, }; const isExternal = (url: string) => /^https?:\/\//.test(url); function CopyCommand({ command, className }: { command: string; className?: string }) { const [copied, setCopied] = useState(false); return ( ); } function StepNode({ step }: { step: SetupStep }) { return (
{step.done ? ( ) : step.launch ? ( ) : step.current ? ( ) : ( )}
); } function TimelineStep({ step, isLast }: { step: SetupStep; isLast: boolean }) { const partner = step.integrations?.find((i) => i.isPartner); return (
{!isLast && (
); } function Spotlight({ step, onSkipBilling, busy, }: { step: SetupStep; onSkipBilling: () => void; busy: boolean; }) { const Icon = STEP_ICON[step.icon]; const integrations = step.integrations; const [provider, setProvider] = useState(integrations?.[0]?.id ?? ''); // Reset the selection when the spotlighted step changes. useEffect(() => { setProvider(integrations?.[0]?.id ?? ''); }, [integrations]); const selected: SetupIntegration | undefined = integrations?.find((i) => i.id === provider); const command = commandForStep(step, integrations ? provider : undefined); const docsVisibility = useDocsVisibility(); // Integrations without a hosted signup page (e.g. "Any SMTP") point their CTA // at an internal guide instead. With that documentation turned off the button // would lead nowhere, so it is dropped — the option itself stays, because the // configure command above is what actually does the work. const showCta = selected ? isDocsHrefVisible(selected.url, docsVisibility) : false; return ( ); } export function SetupProgress() { const { progress, isLoading, isError, setBillingOptOut } = useSetupStatus(); if (isLoading) { return ( ); } if (isError || !progress) return null; return (
Setup progress {progress.complete ? ( <>You’re all set. Ready for production 🚀 ) : ( <> You’re{' '} {progress.doneCount} steps in,{' '} {progress.remaining} to launch 🚀 )}
{progress.percent}%
{progress.doneCount} / {progress.total} complete
{progress.steps.map((step, i) => ( ))}
{progress.current && ( setBillingOptOut.mutate(true)} /> )}
); }