2026-05-28 22:06:15 -05:00
import React , { useState , useEffect } from 'react' ;
2026-05-30 21:20:51 -05:00
import { AlertTriangle , Info } from 'lucide-react' ;
2026-05-28 22:06:15 -05:00
import { toast } from 'sonner' ;
import { api } from '@/api' ;
import { Button } from '@/components/ui/button' ;
feat: configurable sync interval, auto-match, encryption note, admin link, SimpleFIN hyperlink
#1 Sync interval in admin UI:
- bankSyncConfigService: reads simplefin_sync_interval_hours from settings
(DB-first, env fallback, default 4h), setSyncIntervalHours() with validation
- bankSyncWorker: live-updates interval from getBankSyncConfig() each tick
- routes/admin: PUT accepts enabled and sync_interval_hours independently
- BankSyncAdminCard: number input (0.5 step, 0.5-168 range), dirty-checks both
#3 Auto-match after background sync:
- matchSuggestionService: autoMatchForUser() auto-applies suggestions ≥80
score (exact amount + date ±1d + name signal), lazy-requires matchTransactionToBill
- bankSyncWorker: calls autoMatchForUser after each successful sync, own try/catch
#4 Encryption note in BankSyncAdminCard below worker status panel
Also: error handling, admin link in tracker sidebar, SimpleFIN bridge hyperlink
2026-05-29 00:28:50 -05:00
import { Input } from '@/components/ui/input' ;
2026-05-28 22:06:15 -05:00
import { Card , CardHeader , CardTitle , CardContent } from '@/components/ui/card' ;
import { Toggle } from './adminShared' ;
2026-06-07 19:41:17 -05:00
function parseUtc ( str ) {
if ( ! str ) return null ;
const normalized = str . includes ( 'T' ) ? str : str . replace ( ' ' , 'T' ) + 'Z' ;
return new Date ( normalized ) ;
}
2026-05-28 22:32:33 -05:00
function timeAgo ( iso ) {
if ( ! iso ) return null ;
2026-06-07 19:41:17 -05:00
const secs = Math . floor ( ( Date . now ( ) - parseUtc ( iso ) . getTime ( ) ) / 1000 ) ;
2026-05-28 22:32:33 -05:00
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 ;
2026-06-07 19:41:17 -05:00
const secs = Math . floor ( ( parseUtc ( iso ) . getTime ( ) - Date . now ( ) ) / 1000 ) ;
2026-05-28 22:32:33 -05:00
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 ` ;
}
2026-05-28 22:06:15 -05:00
export default function BankSyncAdminCard ( ) {
2026-05-29 01:33:54 -05:00
const [ config , setConfig ] = useState ( null ) ;
const [ loading , setLoading ] = useState ( true ) ;
const [ loadError , setLoadError ] = useState ( '' ) ;
const [ saving , setSaving ] = useState ( false ) ;
const [ enabled , setEnabled ] = useState ( false ) ;
feat: configurable sync interval, auto-match, encryption note, admin link, SimpleFIN hyperlink
#1 Sync interval in admin UI:
- bankSyncConfigService: reads simplefin_sync_interval_hours from settings
(DB-first, env fallback, default 4h), setSyncIntervalHours() with validation
- bankSyncWorker: live-updates interval from getBankSyncConfig() each tick
- routes/admin: PUT accepts enabled and sync_interval_hours independently
- BankSyncAdminCard: number input (0.5 step, 0.5-168 range), dirty-checks both
#3 Auto-match after background sync:
- matchSuggestionService: autoMatchForUser() auto-applies suggestions ≥80
score (exact amount + date ±1d + name signal), lazy-requires matchTransactionToBill
- bankSyncWorker: calls autoMatchForUser after each successful sync, own try/catch
#4 Encryption note in BankSyncAdminCard below worker status panel
Also: error handling, admin link in tracker sidebar, SimpleFIN bridge hyperlink
2026-05-29 00:28:50 -05:00
const [ syncInterval , setSyncInterval ] = useState ( 4 ) ;
2026-06-06 15:51:56 -05:00
const [ syncDays , setSyncDays ] = useState ( 30 ) ;
2026-06-07 19:41:17 -05:00
const [ debugLogging , setDebugLogging ] = useState ( false ) ;
2026-05-28 22:06:15 -05:00
useEffect ( ( ) => {
api . bankSyncConfig ( )
. then ( d => {
setConfig ( d ) ;
setEnabled ( ! ! d . enabled ) ;
feat: configurable sync interval, auto-match, encryption note, admin link, SimpleFIN hyperlink
#1 Sync interval in admin UI:
- bankSyncConfigService: reads simplefin_sync_interval_hours from settings
(DB-first, env fallback, default 4h), setSyncIntervalHours() with validation
- bankSyncWorker: live-updates interval from getBankSyncConfig() each tick
- routes/admin: PUT accepts enabled and sync_interval_hours independently
- BankSyncAdminCard: number input (0.5 step, 0.5-168 range), dirty-checks both
#3 Auto-match after background sync:
- matchSuggestionService: autoMatchForUser() auto-applies suggestions ≥80
score (exact amount + date ±1d + name signal), lazy-requires matchTransactionToBill
- bankSyncWorker: calls autoMatchForUser after each successful sync, own try/catch
#4 Encryption note in BankSyncAdminCard below worker status panel
Also: error handling, admin link in tracker sidebar, SimpleFIN bridge hyperlink
2026-05-29 00:28:50 -05:00
setSyncInterval ( d . sync _interval _hours ? ? 4 ) ;
2026-06-06 15:51:56 -05:00
setSyncDays ( d . sync _days ? ? 30 ) ;
2026-06-07 19:41:17 -05:00
setDebugLogging ( ! ! d . debug _logging ) ;
2026-05-28 22:06:15 -05:00
} )
feat: configurable sync interval, auto-match, encryption note, admin link, SimpleFIN hyperlink
#1 Sync interval in admin UI:
- bankSyncConfigService: reads simplefin_sync_interval_hours from settings
(DB-first, env fallback, default 4h), setSyncIntervalHours() with validation
- bankSyncWorker: live-updates interval from getBankSyncConfig() each tick
- routes/admin: PUT accepts enabled and sync_interval_hours independently
- BankSyncAdminCard: number input (0.5 step, 0.5-168 range), dirty-checks both
#3 Auto-match after background sync:
- matchSuggestionService: autoMatchForUser() auto-applies suggestions ≥80
score (exact amount + date ±1d + name signal), lazy-requires matchTransactionToBill
- bankSyncWorker: calls autoMatchForUser after each successful sync, own try/catch
#4 Encryption note in BankSyncAdminCard below worker status panel
Also: error handling, admin link in tracker sidebar, SimpleFIN bridge hyperlink
2026-05-29 00:28:50 -05:00
. catch ( err => setLoadError ( err . message || 'Failed to load bank sync config' ) )
2026-05-28 22:06:15 -05:00
. finally ( ( ) => setLoading ( false ) ) ;
} , [ ] ) ;
const handleSave = async ( ) => {
feat: configurable sync interval, auto-match, encryption note, admin link, SimpleFIN hyperlink
#1 Sync interval in admin UI:
- bankSyncConfigService: reads simplefin_sync_interval_hours from settings
(DB-first, env fallback, default 4h), setSyncIntervalHours() with validation
- bankSyncWorker: live-updates interval from getBankSyncConfig() each tick
- routes/admin: PUT accepts enabled and sync_interval_hours independently
- BankSyncAdminCard: number input (0.5 step, 0.5-168 range), dirty-checks both
#3 Auto-match after background sync:
- matchSuggestionService: autoMatchForUser() auto-applies suggestions ≥80
score (exact amount + date ±1d + name signal), lazy-requires matchTransactionToBill
- bankSyncWorker: calls autoMatchForUser after each successful sync, own try/catch
#4 Encryption note in BankSyncAdminCard below worker status panel
Also: error handling, admin link in tracker sidebar, SimpleFIN bridge hyperlink
2026-05-29 00:28:50 -05:00
const hours = parseFloat ( syncInterval ) ;
2026-05-29 01:33:54 -05:00
const days = parseInt ( syncDays , 10 ) ;
2026-06-06 15:51:56 -05:00
const maxDays = config ? . sync _days _max ? ? 45 ;
feat: configurable sync interval, auto-match, encryption note, admin link, SimpleFIN hyperlink
#1 Sync interval in admin UI:
- bankSyncConfigService: reads simplefin_sync_interval_hours from settings
(DB-first, env fallback, default 4h), setSyncIntervalHours() with validation
- bankSyncWorker: live-updates interval from getBankSyncConfig() each tick
- routes/admin: PUT accepts enabled and sync_interval_hours independently
- BankSyncAdminCard: number input (0.5 step, 0.5-168 range), dirty-checks both
#3 Auto-match after background sync:
- matchSuggestionService: autoMatchForUser() auto-applies suggestions ≥80
score (exact amount + date ±1d + name signal), lazy-requires matchTransactionToBill
- bankSyncWorker: calls autoMatchForUser after each successful sync, own try/catch
#4 Encryption note in BankSyncAdminCard below worker status panel
Also: error handling, admin link in tracker sidebar, SimpleFIN bridge hyperlink
2026-05-29 00:28:50 -05:00
if ( ! Number . isFinite ( hours ) || hours < 0.5 || hours > 168 ) {
toast . error ( 'Sync interval must be between 0.5 and 168 hours.' ) ;
return ;
}
2026-06-06 15:51:56 -05:00
if ( ! Number . isFinite ( days ) || days < 1 || days > maxDays ) {
toast . error ( ` Routine sync lookback must be 1– ${ maxDays } days. SimpleFIN Bridge enforces a ${ maxDays } -day hard limit — values above ${ maxDays } return errors. ` ) ;
2026-05-29 01:33:54 -05:00
return ;
}
2026-05-28 22:06:15 -05:00
setSaving ( true ) ;
try {
2026-06-07 19:41:17 -05:00
const result = await api . setBankSyncConfig ( { enabled , sync _interval _hours : hours , sync _days : days , debug _logging : debugLogging } ) ;
2026-05-28 22:06:15 -05:00
setConfig ( result ) ;
setEnabled ( ! ! result . enabled ) ;
feat: configurable sync interval, auto-match, encryption note, admin link, SimpleFIN hyperlink
#1 Sync interval in admin UI:
- bankSyncConfigService: reads simplefin_sync_interval_hours from settings
(DB-first, env fallback, default 4h), setSyncIntervalHours() with validation
- bankSyncWorker: live-updates interval from getBankSyncConfig() each tick
- routes/admin: PUT accepts enabled and sync_interval_hours independently
- BankSyncAdminCard: number input (0.5 step, 0.5-168 range), dirty-checks both
#3 Auto-match after background sync:
- matchSuggestionService: autoMatchForUser() auto-applies suggestions ≥80
score (exact amount + date ±1d + name signal), lazy-requires matchTransactionToBill
- bankSyncWorker: calls autoMatchForUser after each successful sync, own try/catch
#4 Encryption note in BankSyncAdminCard below worker status panel
Also: error handling, admin link in tracker sidebar, SimpleFIN bridge hyperlink
2026-05-29 00:28:50 -05:00
setSyncInterval ( result . sync _interval _hours ? ? 4 ) ;
2026-06-06 15:51:56 -05:00
setSyncDays ( result . sync _days ? ? 30 ) ;
2026-06-07 19:41:17 -05:00
setDebugLogging ( ! ! result . debug _logging ) ;
feat: configurable sync interval, auto-match, encryption note, admin link, SimpleFIN hyperlink
#1 Sync interval in admin UI:
- bankSyncConfigService: reads simplefin_sync_interval_hours from settings
(DB-first, env fallback, default 4h), setSyncIntervalHours() with validation
- bankSyncWorker: live-updates interval from getBankSyncConfig() each tick
- routes/admin: PUT accepts enabled and sync_interval_hours independently
- BankSyncAdminCard: number input (0.5 step, 0.5-168 range), dirty-checks both
#3 Auto-match after background sync:
- matchSuggestionService: autoMatchForUser() auto-applies suggestions ≥80
score (exact amount + date ±1d + name signal), lazy-requires matchTransactionToBill
- bankSyncWorker: calls autoMatchForUser after each successful sync, own try/catch
#4 Encryption note in BankSyncAdminCard below worker status panel
Also: error handling, admin link in tracker sidebar, SimpleFIN bridge hyperlink
2026-05-29 00:28:50 -05:00
toast . success ( 'Bank sync settings saved.' ) ;
2026-05-28 22:06:15 -05:00
} catch ( err ) {
toast . error ( err . message || 'Failed to update bank sync setting.' ) ;
} finally {
setSaving ( false ) ;
}
} ;
if ( loading ) {
return (
< Card >
< CardContent className = "py-8 text-center text-muted-foreground text-sm" >
Loading …
< / CardContent >
< / Card >
) ;
}
feat: configurable sync interval, auto-match, encryption note, admin link, SimpleFIN hyperlink
#1 Sync interval in admin UI:
- bankSyncConfigService: reads simplefin_sync_interval_hours from settings
(DB-first, env fallback, default 4h), setSyncIntervalHours() with validation
- bankSyncWorker: live-updates interval from getBankSyncConfig() each tick
- routes/admin: PUT accepts enabled and sync_interval_hours independently
- BankSyncAdminCard: number input (0.5 step, 0.5-168 range), dirty-checks both
#3 Auto-match after background sync:
- matchSuggestionService: autoMatchForUser() auto-applies suggestions ≥80
score (exact amount + date ±1d + name signal), lazy-requires matchTransactionToBill
- bankSyncWorker: calls autoMatchForUser after each successful sync, own try/catch
#4 Encryption note in BankSyncAdminCard below worker status panel
Also: error handling, admin link in tracker sidebar, SimpleFIN bridge hyperlink
2026-05-29 00:28:50 -05:00
if ( loadError ) {
return (
< Card >
< CardContent className = "py-8 text-center text-sm text-destructive" >
{ loadError }
< / CardContent >
< / Card >
) ;
}
2026-05-29 01:33:54 -05:00
const changed = enabled !== ! ! config ? . enabled
|| parseFloat ( syncInterval ) !== config ? . sync _interval _hours
2026-06-07 19:41:17 -05:00
|| parseInt ( syncDays , 10 ) !== config ? . sync _days
|| debugLogging !== ! ! config ? . debug _logging ;
2026-05-28 22:32:33 -05:00
const worker = config ? . worker ;
2026-06-06 15:51:56 -05:00
const seedDays = config ? . seed _days ? ? 44 ;
const maxDays = config ? . sync _days _max ? ? 45 ;
2026-05-28 22:06:15 -05:00
return (
< Card >
< CardHeader className = "pb-4" >
< CardTitle > Bank Sync ( SimpleFIN ) < / CardTitle >
< p className = "text-sm text-muted-foreground mt-1" >
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 .
< / p >
< / CardHeader >
< CardContent className = "space-y-5" >
{ /* Enable toggle */ }
< div className = "flex items-center justify-between" >
< div >
< p className = "text-sm font-medium" > Allow users to connect SimpleFIN < / p >
< p className = "text-xs text-muted-foreground mt-0.5" >
When enabled , users see a Bank Sync section on their Data page .
< / p >
< / div >
< Toggle
checked = { enabled }
onChange = { v => setEnabled ( v ) }
label = "Enable bank sync"
/ >
< / div >
feat: configurable sync interval, auto-match, encryption note, admin link, SimpleFIN hyperlink
#1 Sync interval in admin UI:
- bankSyncConfigService: reads simplefin_sync_interval_hours from settings
(DB-first, env fallback, default 4h), setSyncIntervalHours() with validation
- bankSyncWorker: live-updates interval from getBankSyncConfig() each tick
- routes/admin: PUT accepts enabled and sync_interval_hours independently
- BankSyncAdminCard: number input (0.5 step, 0.5-168 range), dirty-checks both
#3 Auto-match after background sync:
- matchSuggestionService: autoMatchForUser() auto-applies suggestions ≥80
score (exact amount + date ±1d + name signal), lazy-requires matchTransactionToBill
- bankSyncWorker: calls autoMatchForUser after each successful sync, own try/catch
#4 Encryption note in BankSyncAdminCard below worker status panel
Also: error handling, admin link in tracker sidebar, SimpleFIN bridge hyperlink
2026-05-29 00:28:50 -05:00
{ /* Sync interval */ }
< div className = "flex items-center justify-between gap-4" >
< div >
< p className = "text-sm font-medium" > Auto - sync interval < / p >
< p className = "text-xs text-muted-foreground mt-0.5" >
How often the background worker checks for new transactions .
< / p >
< / div >
< div className = "flex items-center gap-2 shrink-0" >
< Input
type = "number"
min = "0.5"
max = "168"
step = "0.5"
value = { syncInterval }
onChange = { e => setSyncInterval ( e . target . value ) }
className = "w-20 text-sm text-right"
/ >
< span className = "text-sm text-muted-foreground" > hrs < / span >
< / div >
< / div >
2026-05-30 21:20:51 -05:00
{ /* Sync window — two-mode explainer */ }
< div className = "space-y-3" >
2026-05-29 01:33:54 -05:00
< div >
2026-05-30 21:20:51 -05:00
< p className = "text-sm font-medium" > Sync lookback windows < / p >
2026-05-29 01:33:54 -05:00
< p className = "text-xs text-muted-foreground mt-0.5" >
2026-05-30 21:20:51 -05:00
SimpleFIN uses two different windows depending on sync type .
2026-05-29 01:33:54 -05:00
< / p >
< / div >
2026-05-30 21:20:51 -05:00
{ /* Initial / backfill — read-only */ }
< div className = "rounded-lg border border-border/50 bg-muted/20 px-4 py-3 space-y-1" >
< div className = "flex items-center justify-between" >
< p className = "text-xs font-semibold text-muted-foreground uppercase tracking-wide" >
Initial connect & amp ; backfill
< / p >
2026-06-06 15:51:56 -05:00
< span className = "font-mono text-sm font-bold" > { seedDays } days < / span >
2026-05-30 21:20:51 -05:00
< / div >
< p className = "text-xs text-muted-foreground" >
2026-06-06 15:51:56 -05:00
The first sync ( and any manual backfill ) fetches up to < strong > { seedDays } days < / strong > of
history to build a complete transaction picture . This is fixed — SimpleFIN Bridge enforces a
strict < strong > { maxDays } - day hard limit < / strong > , so this stays one day under it to avoid
latency - related errors .
2026-05-30 21:20:51 -05:00
< / p >
< / div >
{ /* Routine sync — editable */ }
< div className = "flex items-start justify-between gap-4" >
< div className = "flex-1 min-w-0" >
< p className = "text-sm font-medium" > Routine sync lookback < / p >
< p className = "text-xs text-muted-foreground mt-0.5" >
How far back each auto - sync and manual "Sync Now" looks after the initial connect .
Recommended : < strong > 7 – 30 days < / strong > . Setting this near 45 increases request size
and duplicate - skip work with no benefit once history is established .
< / p >
< / div >
< div className = "flex items-center gap-2 shrink-0" >
< Input
type = "number"
min = "1"
2026-06-06 15:51:56 -05:00
max = { maxDays }
2026-05-30 21:20:51 -05:00
step = "1"
value = { syncDays }
2026-06-06 15:51:56 -05:00
onChange = { e => setSyncDays ( Math . min ( maxDays , Math . max ( 1 , parseInt ( e . target . value , 10 ) || 30 ) ) ) }
2026-05-30 21:20:51 -05:00
className = "w-20 text-sm text-right"
/ >
< span className = "text-sm text-muted-foreground" > days < / span >
< / div >
< / div >
{ /* Amber warning at the SimpleFIN limit */ }
2026-06-06 15:51:56 -05:00
{ parseInt ( syncDays , 10 ) >= maxDays && (
2026-05-30 21:20:51 -05:00
< div className = "flex items-start gap-2 rounded-lg border border-amber-500/30 bg-amber-500/8 px-3 py-2.5" >
< AlertTriangle className = "h-3.5 w-3.5 text-amber-500 shrink-0 mt-0.5" / >
< p className = "text-xs text-amber-600 dark:text-amber-400" >
2026-06-06 15:51:56 -05:00
{ maxDays } days is SimpleFIN Bridge & apos ; s maximum . Requests at this limit may occasionally
2026-05-30 21:20:51 -05:00
fail due to request latency — 30 days or less is recommended for reliable routine syncs .
< / p >
< / div >
) }
{ /* Always-visible hard-limit note */ }
< div className = "flex items-start gap-2 text-xs text-muted-foreground" >
< Info className = "h-3.5 w-3.5 shrink-0 mt-0.5" / >
< span >
2026-06-06 15:51:56 -05:00
SimpleFIN Bridge enforces a < strong > { maxDays } - day maximum < / strong > on all requests .
Any value above { maxDays } will cause sync errors for all users .
2026-05-30 21:20:51 -05:00
< / span >
2026-05-29 01:33:54 -05:00
< / div >
< / div >
2026-05-28 22:32:33 -05:00
{ /* Auto-sync worker status */ }
{ config ? . enabled && worker && (
< div className = "rounded-lg border border-border/60 bg-muted/20 px-4 py-3 space-y-2" >
< p className = "text-xs font-semibold uppercase tracking-widest text-muted-foreground" > Auto - sync worker < / p >
< div className = "grid grid-cols-2 gap-x-6 gap-y-1 text-sm sm:grid-cols-3" >
< div >
< p className = "text-xs text-muted-foreground" > Status < / p >
< p className = "font-medium flex items-center gap-1.5" >
< span className = { ` h-1.5 w-1.5 rounded-full ${ worker . running ? 'bg-amber-500 animate-pulse' : 'bg-emerald-500' } ` } / >
{ worker . running ? 'Running' : 'Idle' }
< / p >
< / div >
< div >
< p className = "text-xs text-muted-foreground" > Last run < / p >
< p className = "font-medium" > { worker . last _run _at ? timeAgo ( worker . last _run _at ) : '—' } < / p >
< / div >
< div >
< p className = "text-xs text-muted-foreground" > Next run < / p >
< p className = "font-medium" > { worker . next _run _at ? ` in ${ timeUntil ( worker . next _run _at ) } ` : '—' } < / p >
< / div >
< / div >
< / div >
) }
2026-06-07 19:41:17 -05:00
{ /* Debug logging toggle */ }
< div className = "flex items-center justify-between border-t border-border/50 pt-4" >
< div >
< p className = "text-sm font-medium" > Debug logging < / p >
< p className = "text-xs text-muted-foreground mt-0.5" >
Logs each account , transaction , and auto - match step to the server console .
Turn on to diagnose sync issues , then off when done .
< / p >
< / div >
< Toggle
checked = { debugLogging }
onChange = { v => setDebugLogging ( v ) }
label = "Enable debug logging"
/ >
< / div >
feat: configurable sync interval, auto-match, encryption note, admin link, SimpleFIN hyperlink
#1 Sync interval in admin UI:
- bankSyncConfigService: reads simplefin_sync_interval_hours from settings
(DB-first, env fallback, default 4h), setSyncIntervalHours() with validation
- bankSyncWorker: live-updates interval from getBankSyncConfig() each tick
- routes/admin: PUT accepts enabled and sync_interval_hours independently
- BankSyncAdminCard: number input (0.5 step, 0.5-168 range), dirty-checks both
#3 Auto-match after background sync:
- matchSuggestionService: autoMatchForUser() auto-applies suggestions ≥80
score (exact amount + date ±1d + name signal), lazy-requires matchTransactionToBill
- bankSyncWorker: calls autoMatchForUser after each successful sync, own try/catch
#4 Encryption note in BankSyncAdminCard below worker status panel
Also: error handling, admin link in tracker sidebar, SimpleFIN bridge hyperlink
2026-05-29 00:28:50 -05:00
{ /* Encryption note */ }
< p className = "text-xs text-muted-foreground border-t border-border/50 pt-3" >
2026-06-06 15:27:45 -05:00
{ config ? . encryption _key _source === 'env'
? < > SimpleFIN credentials are encrypted at rest . Encryption key is loaded from < code className = "font-mono" > TOKEN _ENCRYPTION _KEY < / code > — stored separately from the database , so a database backup alone cannot decrypt credentials . < / >
: < > SimpleFIN credentials are encrypted , but the key is stored in the same database as the data . A database backup or file - level read includes everything needed to decrypt credentials . Set < code className = "font-mono" > TOKEN _ENCRYPTION _KEY < / code > in your environment to keep the key separate . < / >
} { ' ' }
feat: configurable sync interval, auto-match, encryption note, admin link, SimpleFIN hyperlink
#1 Sync interval in admin UI:
- bankSyncConfigService: reads simplefin_sync_interval_hours from settings
(DB-first, env fallback, default 4h), setSyncIntervalHours() with validation
- bankSyncWorker: live-updates interval from getBankSyncConfig() each tick
- routes/admin: PUT accepts enabled and sync_interval_hours independently
- BankSyncAdminCard: number input (0.5 step, 0.5-168 range), dirty-checks both
#3 Auto-match after background sync:
- matchSuggestionService: autoMatchForUser() auto-applies suggestions ≥80
score (exact amount + date ±1d + name signal), lazy-requires matchTransactionToBill
- bankSyncWorker: calls autoMatchForUser after each successful sync, own try/catch
#4 Encryption note in BankSyncAdminCard below worker status panel
Also: error handling, admin link in tracker sidebar, SimpleFIN bridge hyperlink
2026-05-29 00:28:50 -05:00
Regular database backups preserve all user connections .
< / p >
< div className = "flex justify-end" >
2026-05-28 22:18:20 -05:00
< Button onClick = { handleSave } disabled = { saving || ! changed } >
2026-05-28 22:06:15 -05:00
{ saving ? 'Saving…' : 'Save' }
< / Button >
< / div >
< / CardContent >
< / Card >
) ;
}