import { useState } from 'react'; import { toast } from 'sonner'; import { Landmark, RefreshCw, Loader2, AlertTriangle, RotateCcw, Upload, Plus } from 'lucide-react'; import { api } from '@/api'; import { cn } from '@/lib/utils'; import { Button } from '@/components/ui/button'; // Relative "2h ago" / "3d ago"; returns null for empty input. function relativeTime(iso) { if (!iso) return null; const then = new Date(iso).getTime(); if (Number.isNaN(then)) return null; const secs = Math.max(0, Math.round((Date.now() - then) / 1000)); if (secs < 60) return 'just now'; const mins = Math.round(secs / 60); if (mins < 60) return `${mins}m ago`; const hrs = Math.round(mins / 60); if (hrs < 24) return `${hrs}h ago`; const days = Math.round(hrs / 24); return `${days}d ago`; } function HeroShell({ tone = 'default', icon: Icon, children }) { const toneRing = { default: 'border-border/60', good: 'border-emerald-500/30', warn: 'border-amber-500/40', error: 'border-rose-500/30', }[tone]; const toneChip = { default: 'bg-muted/50 text-muted-foreground', good: 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400', warn: 'bg-amber-500/10 text-amber-600 dark:text-amber-400', error: 'bg-rose-500/10 text-rose-600 dark:text-rose-400', }[tone]; return (
{children}
); } /** * The Data page's connection status hero. Five states, so a network blip is never * mistaken for "not connected", and a server without bank sync never gets a dead * Connect button: * loading · disabled · error · not-connected · connected (± needs-attention) */ export default function ConnectionHero({ loading, error, // truthy when the status/summary fetch failed enabled, // status.enabled (server feature flag) hasConnections, // status.has_connections conn, // the simplefin data_source (name, last_sync_at, last_error) or null onRetry, onGoTo, // (sectionId) => void onSynced, // () => void — refresh after a successful sync }) { const [syncing, setSyncing] = useState(false); async function handleSyncNow() { setSyncing(true); try { const r = await api.syncAllSources(); const errs = Array.isArray(r?.errors) ? r.errors : []; if (errs.length) { toast.warning(`Synced, but ${errs.length} connection${errs.length === 1 ? '' : 's'} need attention.`); } else { const n = r?.transactions_new ?? 0; toast.success(`Synced — ${n} new transaction${n === 1 ? '' : 's'}.`); } onSynced?.(); } catch (err) { if (err?.status === 429) toast.error('Please wait a moment before syncing again.'); else toast.error(err?.message || 'Sync failed.'); } finally { setSyncing(false); } } // ── loading ── if (loading) { return (
); } // ── disabled (server has no bank sync) ── if (!enabled) { return (

Automatic bank sync isn’t enabled

This server doesn’t have SimpleFIN configured — you can still import and export your data.

); } // ── error (couldn't check) ── if (error) { return (

Couldn’t check your bank connection

Something went wrong loading your sync status.

); } // ── not connected ── if (!hasConnections || !conn) { return (

Connect your bank to get started

Sync transactions automatically, or import your existing history.

); } // ── connected (± needs attention) ── const needsAttention = Boolean(conn.last_error); const synced = relativeTime(conn.last_sync_at); return (

{needsAttention ? 'Your bank connection needs attention' : 'Your bank is connected'}

{needsAttention ? conn.last_error : <>{conn.name || 'SimpleFIN'}{synced ? <> · synced {synced} : null} · syncs automatically}

{needsAttention && ( )}
); }