'use client' import { useState, useEffect, useCallback } from 'react' import { Sliders, RotateCcw, Save, CheckCircle2, Users, AlertTriangle } from 'lucide-react' import { cn } from '@/lib/utils' import { wallets } from '@/lib/mock-data' import { calculateChurnScore, DEFAULT_THRESHOLDS, ScoringThresholds } from '@/lib/agent-engine' import type { ChurnRisk } from '@/lib/types' const STORAGE_KEY = 'flowstate_thresholds' function loadThresholds(): ScoringThresholds { if (typeof window === 'undefined') return DEFAULT_THRESHOLDS try { const raw = localStorage.getItem(STORAGE_KEY) return raw ? { ...DEFAULT_THRESHOLDS, ...JSON.parse(raw) } : DEFAULT_THRESHOLDS } catch { return DEFAULT_THRESHOLDS } } function walletToSignals(w: typeof wallets[0]) { const daysMatch = w.lastActive.match(/(\d+)d/) const daysInactive = daysMatch ? parseInt(daysMatch[1]) : 0 return { daysInactive, volumeDropPct: w.streak === 0 ? 80 : Math.max(0, (10 - Math.min(w.streak, 10)) * 8), uniqueProtocols: w.protocols.length, currentStreak: w.streak, hasLiquidation: false, } } const RISK_COLORS: Record = { critical: 'bg-trading-down/20 text-trading-down', high: 'bg-[#ff9500]/20 text-[#ff9500]', medium: 'bg-brand-yellow/20 text-brand-yellow', low: 'bg-trading-up/20 text-trading-up', safe: 'bg-muted/20 text-muted', } interface SliderRowProps { label: string; desc: string; value: number; min: number; max: number; unit: string onChange: (v: number) => void } function SliderRow({ label, desc, value, min, max, unit, onChange }: SliderRowProps) { return (

{label}

{desc}

{value}{unit}
onChange(parseInt(e.target.value))} className="w-full h-1.5 rounded-full appearance-none bg-surface-elevated cursor-pointer accent-brand-yellow" />
{min}{unit}{max}{unit}
) } export default function SettingsPage() { const [thresholds, setThresholds] = useState(DEFAULT_THRESHOLDS) const [saved, setSaved] = useState(false) useEffect(() => { setThresholds(loadThresholds()) }, []) const set = useCallback((key: K, value: number) => { setThresholds(t => ({ ...t, [key]: value })) }, []) const preview = wallets.map(w => { const signals = walletToSignals(w) const { score, risk } = calculateChurnScore(signals, thresholds) return { ...w, score, risk } }) const riskCounts = preview.reduce>((acc, w) => { acc[w.risk] = (acc[w.risk] || 0) + 1 return acc }, {}) const handleSave = () => { localStorage.setItem(STORAGE_KEY, JSON.stringify(thresholds)) setSaved(true) setTimeout(() => setSaved(false), 2000) } const handleReset = () => { setThresholds(DEFAULT_THRESHOLDS) localStorage.removeItem(STORAGE_KEY) } return (

Threshold Editor

Tune the 5-signal AI scoring model — live preview updates wallet risk tiers

{/* Live preview */}

Live Preview — Wallet Distribution

updates as you drag
{(['critical','high','medium','low','safe'] as ChurnRisk[]).map(r => (

{r}

{riskCounts[r] || 0}

))}
{preview.map(w => (
{w.address.slice(0,8)}...{w.address.slice(-4)} {w.risk}
{w.score}
))}
{/* Inactivity thresholds */}

Inactivity Signals

set('inactivityCritical', v)} /> set('inactivityHigh', v)} /> set('inactivityMedium', v)} />
{/* Volume drop thresholds */}

Volume Drop Signals

set('volumeDropCritical', v)} /> set('volumeDropHigh', v)} /> set('volumeDropMedium', v)} />
{/* Pre-churn early warning */}

Pre-Churn Early Warning

3× more effective than win-back

Fire inactivity_detected before wallets reach churn threshold. Different sensitivity per wallet type.

set('preChurnDaysTrader', v)} /> set('preChurnDaysLP', v)} /> set('preChurnDaysStaker', v)} />
) }