'use client'; import { useEffect, useState } from 'react'; import Link from 'next/link'; import { useParams } from 'next/navigation'; import { Check, Circle, Loader2, ExternalLink } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Progress } from '@/components/ui/progress'; import { apiFetch } from '@/lib/api/client'; interface OnboardingStep { id: string; href: string; label: string; description: string; /** Setting key whose presence proves the step is done. When set, the * checkmark auto-fills from the settings list. When undefined, the * step relies on the manual checkbox in `onboarding_status`. */ autoCheckSettingKey?: string; /** Override: read this many users / tags / roles from a list endpoint * and consider the step done when count > 0. */ autoCheckListEndpoint?: string; } const STEPS: OnboardingStep[] = [ { id: 'branding', href: 'branding', label: 'Set port name, logo, primary colour', description: 'Branding flows into the navbar, emails, EOI PDFs, and the public auth shell.', autoCheckSettingKey: 'branding_logo_url', }, { id: 'email', href: 'email', label: 'Configure outgoing email', description: 'From-address, signature, footer, plus per-port SMTP overrides if you don’t use the global account.', autoCheckSettingKey: 'sales_email_smtp_host', }, { id: 'documenso', href: 'documenso', label: 'Connect Documenso for EOIs', description: 'API credentials and the EOI template id, plus the in-app vs Documenso pathway choice.', autoCheckSettingKey: 'documenso_api_url', }, { id: 'settings', href: 'settings', label: 'Tune business rules + recommender weights', description: 'Pipeline weights, net-10 discount, berth recommender knobs (heat weights, fall-through policy).', autoCheckSettingKey: 'recommender_top_n_default', }, { id: 'roles', href: 'roles', label: 'Create roles & assign users', description: 'Per-port roles inherit from system roles; override permissions here.', autoCheckListEndpoint: '/api/v1/admin/roles', }, { id: 'users', href: 'users', label: 'Invite the rest of the team', description: 'Invite users, assign roles, optionally grant residential access. Track pending vs accepted.', autoCheckListEndpoint: '/api/v1/admin/users', }, { id: 'tags', href: 'tags', label: 'Define starter tags', description: 'Color-coded labels used across clients, yachts, companies, and interests.', autoCheckListEndpoint: '/api/v1/tags/options', }, { id: 'storage', href: 'storage', label: 'Configure storage backend', description: 'Verify S3/filesystem and run a test connection before going live so PDFs and avatars persist correctly.', autoCheckSettingKey: 'storage_backend', }, { id: 'forms', href: '../', label: 'Wire the website intake forms', description: 'Inquiry forms on the marketing site dual-write into the CRM via /api/public/website-inquiries. Manually mark complete when verified.', }, ]; interface SettingRow { key: string; value: unknown; portId: string | null; } interface SettingsResp { data: { portSettings: SettingRow[]; globalSettings: SettingRow[] }; } export function OnboardingChecklist() { const params = useParams<{ portSlug: string }>(); const portSlug = params?.portSlug ?? ''; const [autoChecks, setAutoChecks] = useState>({}); const [manualChecks, setManualChecks] = useState>({}); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(null); useEffect(() => { async function load() { setLoading(true); try { const settings = await apiFetch('/api/v1/admin/settings'); const all = [...settings.data.portSettings, ...settings.data.globalSettings]; const byKey = new Map(all.map((r) => [r.key, r.value])); const checks: Record = {}; const listChecks = await Promise.all( STEPS.map(async (s) => { if (s.autoCheckSettingKey) { const v = byKey.get(s.autoCheckSettingKey); return [s.id, v !== undefined && v !== null && v !== '' && v !== false] as const; } if (s.autoCheckListEndpoint) { try { const res = await apiFetch<{ data: unknown[] }>(s.autoCheckListEndpoint); return [s.id, Array.isArray(res.data) && res.data.length > 0] as const; } catch { return [s.id, false] as const; } } return [s.id, false] as const; }), ); for (const [id, done] of listChecks) checks[id] = done; setAutoChecks(checks); // Pull the manual-checkbox state from system_settings. const manual = (byKey.get('onboarding_manual_status') ?? {}) as Record; setManualChecks(manual); } finally { setLoading(false); } } void load(); }, []); async function toggleManual(id: string) { const next = { ...manualChecks, [id]: !manualChecks[id] }; setManualChecks(next); setSaving(id); try { await apiFetch('/api/v1/admin/settings', { method: 'PUT', body: { key: 'onboarding_manual_status', value: next }, }); } finally { setSaving(null); } } const stepDone = (id: string) => Boolean(autoChecks[id]) || Boolean(manualChecks[id]); const completed = STEPS.filter((s) => stepDone(s.id)).length; const percent = Math.round((completed / STEPS.length) * 100); return (
Setup checklist {completed} of {STEPS.length} complete. Auto-checked steps update when you save the underlying setting; manual ones (like website-form integration) need the checkbox.
    {STEPS.map((step, idx) => { const auto = Boolean(autoChecks[step.id]); const manual = Boolean(manualChecks[step.id]); const done = auto || manual; return (
  1. {done ? ( ) : loading ? ( ) : ( )}
    {idx + 1}. {step.label}

    {step.description}

    {auto && (

    Auto-detected complete via{' '} {step.autoCheckSettingKey ?? step.autoCheckListEndpoint}

    )}
    {!auto && ( )}
  2. ); })}
); }