import { useId } from 'react'; import { motion } from 'framer-motion'; import { Wallet, CalendarClock, Sparkles, ArrowRight } from 'lucide-react'; import { cn, fmt, fmtDate } from '@/lib/utils'; import { buildTimelineGeometry, daysUntilLabel, shortDate, splitUpcoming } from '@/lib/cashflowUtils'; const CHART_W = 400; const CHART_H = 96; function TimelineChart({ timeline, positive }) { const gradId = useId(); const geo = buildTimelineGeometry(timeline, CHART_W, CHART_H); if (!geo) return null; const tone = positive ? '#10b981' : '#f43f5e'; // emerald-500 / rose-500 return ( {/* zero line — only meaningful when the projection dips negative */} {geo.points.some(p => p.balance < 0) && ( )} {/* bill-day markers */} {geo.points.filter(p => p.isDrop).map(p => ( {`${fmtDate(p.date)} — ${p.bills.map(b => `${b.name} ${fmt(b.amount)}`).join(', ')} → ${fmt(p.balance)} left`} ))} {/* payday marker */} {geo.points.filter(p => p.isPayday).map(p => ( {`Payday ${fmtDate(p.date)} — ${fmt(p.balance)} left`} ))} ); } function UpcomingList({ upcoming }) { const { visible, overflow } = splitUpcoming(upcoming, 4); if (visible.length === 0) { return (
All bills covered

Nothing else is due before payday.

); } return ( ); } /** * Safe-to-Spend hero card: what's left for the current 1st/15th period after * everything still due before the next payday is covered. Sits directly under * the summary cards and reads from the tracker payload's `cashflow` block. */ export default function CashFlowCard({ cashflow, onSetStartingAmounts }) { if (!cashflow) return null; // No starting amounts and no bank sync — invite setup instead of guessing. if (!cashflow.has_data) { return (

See your safe-to-spend

Add what's in your account for the 1st and 15th, and BillTracker projects what's left after bills.

); } const safe = Number(cashflow.safe_to_spend ?? 0); const positive = safe >= 0; return (
{/* ── Number ── */}

Safe to spend

{positive ? '' : '−'}{fmt(Math.abs(safe))}

until {shortDate(cashflow.next_payday)} · {daysUntilLabel(cashflow.days_until_payday)}

{/* ── Projection ── */}

{fmt(cashflow.available)} on hand → {cashflow.still_due_count === 0 ? 'no bills left before payday' : `${cashflow.still_due_count} bill${cashflow.still_due_count === 1 ? '' : 's'} (${fmt(cashflow.still_due_total)}) before payday`}

{/* ── Upcoming ── */}

Due before payday

); }