import React, { useState, useEffect } from 'react'; import { toast } from 'sonner'; import { api } from '@/api'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'; import { Toggle } from './adminShared'; function timeAgo(iso) { if (!iso) return null; const secs = Math.floor((Date.now() - new Date(iso).getTime()) / 1000); if (secs < 60) return 'just now'; if (secs < 3600) return `${Math.floor(secs / 60)}m ago`; if (secs < 86400) return `${Math.floor(secs / 3600)}h ago`; return `${Math.floor(secs / 86400)}d ago`; } function timeUntil(iso) { if (!iso) return null; const secs = Math.floor((new Date(iso).getTime() - Date.now()) / 1000); if (secs <= 0) return 'soon'; if (secs < 60) return `${secs}s`; if (secs < 3600) return `${Math.floor(secs / 60)}m`; return `${Math.floor(secs / 3600)}h`; } export default function BankSyncAdminCard() { const [config, setConfig] = useState(null); const [loading, setLoading] = useState(true); const [loadError, setLoadError] = useState(''); const [saving, setSaving] = useState(false); const [enabled, setEnabled] = useState(false); const [syncInterval, setSyncInterval] = useState(4); useEffect(() => { api.bankSyncConfig() .then(d => { setConfig(d); setEnabled(!!d.enabled); setSyncInterval(d.sync_interval_hours ?? 4); }) .catch(err => setLoadError(err.message || 'Failed to load bank sync config')) .finally(() => setLoading(false)); }, []); const handleSave = async () => { const hours = parseFloat(syncInterval); if (!Number.isFinite(hours) || hours < 0.5 || hours > 168) { toast.error('Sync interval must be between 0.5 and 168 hours.'); return; } setSaving(true); try { const result = await api.setBankSyncConfig({ enabled, sync_interval_hours: hours }); setConfig(result); setEnabled(!!result.enabled); setSyncInterval(result.sync_interval_hours ?? 4); toast.success('Bank sync settings saved.'); } catch (err) { toast.error(err.message || 'Failed to update bank sync setting.'); } finally { setSaving(false); } }; if (loading) { return ( Loading… ); } if (loadError) { return ( {loadError} ); } const changed = enabled !== !!config?.enabled || parseFloat(syncInterval) !== config?.sync_interval_hours; const worker = config?.worker; return ( Bank Sync (SimpleFIN)

Allow users to connect their own SimpleFIN Bridge account to sync read-only bank transactions. Each user manages their own connection from the Data page — no bank credentials are stored.

{/* Enable toggle */}

Allow users to connect SimpleFIN

When enabled, users see a Bank Sync section on their Data page.

setEnabled(v)} label="Enable bank sync" />
{/* Sync interval */}

Auto-sync interval

How often the background worker checks for new transactions.

setSyncInterval(e.target.value)} className="w-20 text-sm text-right" /> hrs
{/* Auto-sync worker status */} {config?.enabled && worker && (

Auto-sync worker

Status

{worker.running ? 'Running' : 'Idle'}

Last run

{worker.last_run_at ? timeAgo(worker.last_run_at) : '—'}

Next run

{worker.next_run_at ? `in ${timeUntil(worker.next_run_at)}` : '—'}

)} {/* Encryption note */}

SimpleFIN credentials are encrypted with a key stored in your database. Regular database backups preserve all user connections.

); }