"use client"; import { FormEvent, useEffect, useMemo, useState } from "react"; import { AppShell } from "../../../components/app-shell"; import { apiFetch } from "@/lib/api"; type Household = { id: string; name: string; members?: HouseholdMember[]; }; type HouseholdMember = { id: string; userId: string; role: string; status?: string; joinedAt: string; user?: { email: string; fullName?: string | null; }; }; type HouseholdInvite = { id: string; email: string; role: string; status: string; expiresAt: string; acceptedAt?: string | null; createdAt: string; }; type HouseholdAccount = { displayId: string; institutionName: string; accountType: string; mask?: string | null; currentBalance: number; availableBalance: number; isoCurrencyCode: string; ownerUserId?: string | null; ownershipType: "mine" | "theirs" | "joint" | string; lastBalanceSync?: string | null; syncStatus: string; }; type CashflowMonth = { month: string; income: number; expenses: number; net: number; transactionCount: number; }; type HealthScore = { score: number; rating: "excellent" | "strong" | "building" | "needs_attention" | string; components: { cashflow: number; balanceBuffer: number; goalProgress: number; collaboration: number; syncHealth: number; }; metrics: { savingsRate: number; bufferMonths: number; activeGoalCount: number; jointAccountCount: number; collaborativeTransactionCount: number; }; recommendations: string[]; }; type RecentTransaction = { date: string; description: string; amount: number; source: string; category: string; account?: { institutionName?: string; mask?: string | null; ownerUserId?: string | null; ownershipType?: string; }; }; type HouseholdGoal = { id: string; name: string; description?: string | null; targetAmount: number; currentAmount: number; remainingAmount: number; progressPercent: number; isoCurrencyCode: string; targetDate?: string | null; priority: "low" | "medium" | "high" | string; status: "active" | "paused" | "completed" | "archived" | string; createdBy?: { email: string; fullName?: string | null; }; }; type DashboardData = { household: Household; members: HouseholdMember[]; privacyMode: { enabled: boolean; hideIndividualBalances: boolean; hideIndividualTransactions: boolean; }; summary: { memberCount: number; accountCount: number; totalBalance: number; availableBalance: number; monthlyIncome: number; monthlyExpenses: number; monthlyNet: number; }; ownershipBreakdown: Record; healthScore: HealthScore; accounts: HouseholdAccount[]; cashflow: CashflowMonth[]; recentTransactions: RecentTransaction[]; }; type FairSplitResult = { expenseAmount: number; splitMode: "equal" | "income_weighted" | "custom"; mineMonthlyIncome: number; yoursMonthlyIncome: number; totalIncome: number; incomeShares: { mine: number; yours: number; }; split: { minePercent: number; yoursPercent: number; mineAmount: number; yoursAmount: number; }; transactionDefaults: { attribution: "ours"; splitMode: "equal" | "custom"; splitMinePercent: number; splitYoursPercent: number; }; rationale: string; }; type MoneyDatePrompt = { id: string; topic: string; question: string; why: string; actionLabel: string; priority: "high" | "medium" | "low"; }; type MoneyDatePromptResponse = { householdId: string; generatedAt: string; cadenceSuggestion: "weekly" | "monthly" | string; prompts: MoneyDatePrompt[]; }; type DebtPayoffResult = { strategy: "avalanche" | "snowball" | "custom"; monthlyExtraPayment: number; monthlyPayment: number; totalStartingBalance: number; totalInterestPaid: number; payoffMonths: number; payoffYears: number; contributionSplit: { splitMode: "equal" | "income_weighted" | "custom"; minePercent: number; yoursPercent: number; mineAmount: number; yoursAmount: number; rationale: string; }; orderedDebts: Array<{ name: string; startingBalance: number; annualPercentageRate: number; minimumPayment: number; payoffOrder: number; }>; timeline: Array<{ month: number; targetDebt: string; totalRemainingBalance: number; interestPaid: number; principalPaid: number; paidOffDebts: string[]; }>; recommendations: string[]; }; type FutureScenarioResult = { name: string; type: "goal" | "net_worth" | "income_change" | "expense_change"; horizonMonths: number; startingBalance: number; finalBalance: number; targetAmount: number | null; targetReachedMonth: number | null; monthlyNetChange: number; totalContributions: number; eventTotal: number; projectedGrowth: number; milestones: Array<{ month: number; label: string; balance: number }>; recommendation: string; }; type FutureScenarioResponse = { generatedAt: string; scenarios: FutureScenarioResult[]; comparison: { bestFinalBalance: FutureScenarioResult; earliestTarget: FutureScenarioResult | null; }; }; const money = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", }); const inputClass = "w-full rounded-lg border border-border bg-background px-3 py-2 text-sm text-foreground focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20"; const cardClass = "rounded-xl border border-border bg-secondary/10 p-5"; const collaboratorRoles = ["admin", "member", "viewer", "accountant", "advisor"] as const; function formatMoney(value: number) { return money.format(value || 0); } function monthLabel(value: string) { const [year, month] = value.split("-").map(Number); return new Date(year, month - 1, 1).toLocaleDateString("en-US", { month: "short" }); } function accountLabel(account: HouseholdAccount | RecentTransaction["account"]) { if (!account) return "Household account"; const mask = account.mask ? ` ending ${account.mask}` : ""; return `${account.institutionName ?? "Account"}${mask}`; } function healthRatingLabel(value: string) { return value.replace(/_/g, " "); } export default function HouseholdSettingsPage() { const [households, setHouseholds] = useState([]); const [selectedId, setSelectedId] = useState(""); const [dashboard, setDashboard] = useState(null); const [newName, setNewName] = useState(""); const [goals, setGoals] = useState([]); const [invites, setInvites] = useState([]); const [inviteDraft, setInviteDraft] = useState({ email: "", role: "accountant" }); const [collaborationStatus, setCollaborationStatus] = useState(""); const [goalStatus, setGoalStatus] = useState(""); const [goalSaving, setGoalSaving] = useState(false); const [goalDraft, setGoalDraft] = useState({ name: "", description: "", targetAmount: "", currentAmount: "", targetDate: "", priority: "medium" as "low" | "medium" | "high", }); const [goalContributions, setGoalContributions] = useState>({}); const [fairSplitDraft, setFairSplitDraft] = useState({ expenseAmount: "", mineMonthlyIncome: "", yoursMonthlyIncome: "", splitMode: "income_weighted" as "equal" | "income_weighted" | "custom", customMinePercent: "50", }); const [fairSplitResult, setFairSplitResult] = useState(null); const [fairSplitStatus, setFairSplitStatus] = useState(""); const [debtDraft, setDebtDraft] = useState({ strategy: "avalanche" as "avalanche" | "snowball" | "custom", monthlyExtraPayment: "300", splitMode: "income_weighted" as "equal" | "income_weighted" | "custom", mineMonthlyIncome: "", yoursMonthlyIncome: "", customMinePercent: "50", debts: [ { name: "Credit card", balance: "", annualPercentageRate: "", minimumPayment: "", priority: "1" }, { name: "Personal loan", balance: "", annualPercentageRate: "", minimumPayment: "", priority: "2" }, ], }); const [debtPayoffResult, setDebtPayoffResult] = useState(null); const [debtPayoffStatus, setDebtPayoffStatus] = useState(""); const [futureDraft, setFutureDraft] = useState({ scenarios: [ { name: "Shared milestone", type: "goal", startingBalance: "", monthlyContribution: "", monthlyIncome: "", monthlyExpenses: "", targetAmount: "", horizonMonths: "36", annualGrowthRate: "0" }, { name: "Stretch plan", type: "net_worth", startingBalance: "", monthlyContribution: "", monthlyIncome: "", monthlyExpenses: "", targetAmount: "", horizonMonths: "36", annualGrowthRate: "3" }, ], }); const [futureResult, setFutureResult] = useState(null); const [futureStatus, setFutureStatus] = useState(""); const [privacyStatus, setPrivacyStatus] = useState(""); const [moneyDatePrompts, setMoneyDatePrompts] = useState(null); const [moneyDateStatus, setMoneyDateStatus] = useState(""); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [status, setStatus] = useState(""); const selectedHousehold = useMemo( () => households.find((household) => household.id === selectedId) ?? null, [households, selectedId], ); useEffect(() => { loadHouseholds(); }, []); useEffect(() => { if (!selectedId) { setDashboard(null); setGoals([]); setInvites([]); setMoneyDatePrompts(null); return; } loadDashboard(selectedId); loadGoals(selectedId); loadInvites(selectedId); loadMoneyDatePrompts(selectedId); }, [selectedId]); const loadHouseholds = async () => { setLoading(true); const res = await apiFetch("/api/households"); setLoading(false); if (res.error) { setStatus(res.error.message ?? "Unable to load households."); return; } const list = res.data ?? []; setHouseholds(list); setSelectedId((current) => current || list[0]?.id || ""); }; const loadDashboard = async (id: string) => { setStatus("Loading shared dashboard..."); const res = await apiFetch(`/api/households/${id}/dashboard`); if (res.error) { setDashboard(null); setStatus(res.error.message ?? "Unable to load shared dashboard."); return; } setDashboard(res.data); setStatus(""); }; const loadGoals = async (id: string) => { const res = await apiFetch(`/api/households/${id}/goals`); if (res.error) { setGoalStatus(res.error.message ?? "Unable to load shared goals."); return; } setGoals(res.data ?? []); setGoalStatus(""); }; const loadInvites = async (id: string) => { const res = await apiFetch(`/api/households/${id}/invites`); if (res.error) { setInvites([]); return; } setInvites(res.data ?? []); }; const loadMoneyDatePrompts = async (id: string) => { setMoneyDateStatus("Loading prompts..."); const res = await apiFetch(`/api/households/${id}/money-date-prompts`); if (res.error) { setMoneyDatePrompts(null); setMoneyDateStatus(res.error.message ?? "Unable to load money date prompts."); return; } setMoneyDatePrompts(res.data ?? null); setMoneyDateStatus(""); }; const createHousehold = async (event: FormEvent) => { event.preventDefault(); const name = newName.trim(); if (!name) return; setSaving(true); const res = await apiFetch("/api/households", { method: "POST", body: JSON.stringify({ name }), }); setSaving(false); if (res.error) { setStatus(res.error.message ?? "Unable to create household."); return; } setNewName(""); await loadHouseholds(); if (res.data?.id) setSelectedId(res.data.id); }; const createGoal = async (event: FormEvent) => { event.preventDefault(); if (!selectedId || !goalDraft.name.trim()) return; const targetAmount = Number.parseFloat(goalDraft.targetAmount); const currentAmount = goalDraft.currentAmount ? Number.parseFloat(goalDraft.currentAmount) : 0; if (!Number.isFinite(targetAmount) || targetAmount <= 0) { setGoalStatus("Goal target must be greater than zero."); return; } if (!Number.isFinite(currentAmount) || currentAmount < 0 || currentAmount > targetAmount) { setGoalStatus("Current amount must be between zero and the target."); return; } setGoalSaving(true); const res = await apiFetch(`/api/households/${selectedId}/goals`, { method: "POST", body: JSON.stringify({ name: goalDraft.name, description: goalDraft.description || undefined, targetAmount, currentAmount, targetDate: goalDraft.targetDate || undefined, priority: goalDraft.priority, }), }); setGoalSaving(false); if (res.error) { setGoalStatus(res.error.message ?? "Unable to create shared goal."); return; } setGoalDraft({ name: "", description: "", targetAmount: "", currentAmount: "", targetDate: "", priority: "medium" }); await loadGoals(selectedId); }; const contributeToGoal = async (goal: HouseholdGoal) => { if (!selectedId) return; const contribution = Number.parseFloat(goalContributions[goal.id] ?? ""); if (!Number.isFinite(contribution) || contribution <= 0) { setGoalStatus("Contribution must be greater than zero."); return; } const nextAmount = Math.min(goal.targetAmount, goal.currentAmount + contribution); const nextStatus = nextAmount >= goal.targetAmount ? "completed" : goal.status; const res = await apiFetch(`/api/households/${selectedId}/goals/${goal.id}`, { method: "PATCH", body: JSON.stringify({ currentAmount: nextAmount, status: nextStatus }), }); if (res.error) { setGoalStatus(res.error.message ?? "Unable to update shared goal."); return; } setGoalContributions((prev) => ({ ...prev, [goal.id]: "" })); await loadGoals(selectedId); }; const setGoalStatusValue = async (goal: HouseholdGoal, status: "active" | "paused" | "completed" | "archived") => { if (!selectedId) return; const res = await apiFetch(`/api/households/${selectedId}/goals/${goal.id}`, { method: "PATCH", body: JSON.stringify({ status }), }); if (res.error) { setGoalStatus(res.error.message ?? "Unable to update shared goal."); return; } await loadGoals(selectedId); }; const inviteCollaborator = async (event: FormEvent) => { event.preventDefault(); if (!selectedId || !inviteDraft.email.trim()) return; const res = await apiFetch(`/api/households/${selectedId}/invites`, { method: "POST", body: JSON.stringify({ email: inviteDraft.email, role: inviteDraft.role, }), }); if (res.error) { setCollaborationStatus(res.error.message ?? "Unable to invite collaborator."); return; } setInviteDraft({ email: "", role: "accountant" }); setCollaborationStatus("Invite sent."); await loadInvites(selectedId); }; const calculateFairSplit = async (event: FormEvent) => { event.preventDefault(); if (!selectedId) return; const expenseAmount = Number.parseFloat(fairSplitDraft.expenseAmount); if (!Number.isFinite(expenseAmount) || expenseAmount <= 0) { setFairSplitStatus("Enter an expense amount greater than zero."); return; } setFairSplitStatus("Calculating fair split..."); const res = await apiFetch(`/api/households/${selectedId}/fair-split`, { method: "POST", body: JSON.stringify({ expenseAmount, mineMonthlyIncome: Number.parseFloat(fairSplitDraft.mineMonthlyIncome || "0"), yoursMonthlyIncome: Number.parseFloat(fairSplitDraft.yoursMonthlyIncome || "0"), splitMode: fairSplitDraft.splitMode, customMinePercent: fairSplitDraft.splitMode === "custom" ? Number.parseFloat(fairSplitDraft.customMinePercent) : undefined, }), }); if (res.error) { setFairSplitResult(null); setFairSplitStatus(res.error.message ?? "Unable to calculate split."); return; } setFairSplitResult(res.data); setFairSplitStatus(""); }; const updateDebtDraft = (index: number, field: "name" | "balance" | "annualPercentageRate" | "minimumPayment" | "priority", value: string) => { setDebtDraft((prev) => ({ ...prev, debts: prev.debts.map((debt, i) => i === index ? { ...debt, [field]: value } : debt), })); }; const addDebtDraftRow = () => { setDebtDraft((prev) => ({ ...prev, debts: [ ...prev.debts, { name: `Debt ${prev.debts.length + 1}`, balance: "", annualPercentageRate: "", minimumPayment: "", priority: String(prev.debts.length + 1) }, ], })); }; const calculateDebtPayoff = async (event: FormEvent) => { event.preventDefault(); if (!selectedId) return; const debts = debtDraft.debts .filter((debt) => debt.name.trim() && debt.balance && debt.minimumPayment) .map((debt) => ({ name: debt.name, balance: Number.parseFloat(debt.balance), annualPercentageRate: Number.parseFloat(debt.annualPercentageRate || "0"), minimumPayment: Number.parseFloat(debt.minimumPayment), priority: Number.parseInt(debt.priority || "0", 10), })); if (!debts.length) { setDebtPayoffStatus("Add at least one debt with balance and minimum payment."); return; } setDebtPayoffStatus("Calculating debt payoff plan..."); const res = await apiFetch(`/api/households/${selectedId}/debt-payoff`, { method: "POST", body: JSON.stringify({ strategy: debtDraft.strategy, monthlyExtraPayment: Number.parseFloat(debtDraft.monthlyExtraPayment || "0"), splitMode: debtDraft.splitMode, mineMonthlyIncome: Number.parseFloat(debtDraft.mineMonthlyIncome || "0"), yoursMonthlyIncome: Number.parseFloat(debtDraft.yoursMonthlyIncome || "0"), customMinePercent: debtDraft.splitMode === "custom" ? Number.parseFloat(debtDraft.customMinePercent) : undefined, debts, }), }); if (res.error) { setDebtPayoffResult(null); setDebtPayoffStatus(res.error.message ?? "Unable to calculate debt payoff plan."); return; } setDebtPayoffResult(res.data ?? null); setDebtPayoffStatus(""); }; const updateFutureScenario = ( index: number, field: "name" | "type" | "startingBalance" | "monthlyContribution" | "monthlyIncome" | "monthlyExpenses" | "targetAmount" | "horizonMonths" | "annualGrowthRate", value: string, ) => { setFutureDraft((prev) => ({ ...prev, scenarios: prev.scenarios.map((scenario, i) => i === index ? { ...scenario, [field]: value } : scenario), })); }; const addFutureScenario = () => { setFutureDraft((prev) => ({ ...prev, scenarios: [ ...prev.scenarios, { name: `Scenario ${prev.scenarios.length + 1}`, type: "goal", startingBalance: "", monthlyContribution: "", monthlyIncome: "", monthlyExpenses: "", targetAmount: "", horizonMonths: "36", annualGrowthRate: "0" }, ], })); }; const calculateFutureScenarios = async (event: FormEvent) => { event.preventDefault(); if (!selectedId) return; const scenarios = futureDraft.scenarios .filter((scenario) => scenario.name.trim()) .map((scenario) => ({ name: scenario.name, type: scenario.type, startingBalance: Number.parseFloat(scenario.startingBalance || "0"), monthlyContribution: Number.parseFloat(scenario.monthlyContribution || "0"), monthlyIncome: Number.parseFloat(scenario.monthlyIncome || "0"), monthlyExpenses: Number.parseFloat(scenario.monthlyExpenses || "0"), targetAmount: scenario.targetAmount ? Number.parseFloat(scenario.targetAmount) : undefined, horizonMonths: Number.parseInt(scenario.horizonMonths || "36", 10), annualGrowthRate: Number.parseFloat(scenario.annualGrowthRate || "0"), })); if (!scenarios.length) { setFutureStatus("Add at least one scenario."); return; } setFutureStatus("Calculating future scenarios..."); const res = await apiFetch(`/api/households/${selectedId}/future-scenarios`, { method: "POST", body: JSON.stringify({ scenarios }), }); if (res.error) { setFutureResult(null); setFutureStatus(res.error.message ?? "Unable to calculate future scenarios."); return; } setFutureResult(res.data ?? null); setFutureStatus(""); }; const updateMemberAccess = async (member: HouseholdMember, payload: { role?: string; status?: string }) => { if (!selectedId) return; const res = await apiFetch(`/api/households/${selectedId}/members/${member.id}`, { method: "PATCH", body: JSON.stringify(payload), }); if (res.error) { setCollaborationStatus(res.error.message ?? "Unable to update collaborator access."); return; } setCollaborationStatus("Collaborator access updated."); await loadDashboard(selectedId); await loadInvites(selectedId); }; const updatePrivacyMode = async (enabled: boolean) => { if (!selectedId) return; setPrivacyStatus("Updating privacy mode..."); const res = await apiFetch<{ privacyMode: DashboardData["privacyMode"] }>(`/api/households/${selectedId}/privacy`, { method: "PATCH", body: JSON.stringify({ enabled, hideIndividualBalances: true, hideIndividualTransactions: true, }), }); if (res.error) { setPrivacyStatus(res.error.message ?? "Unable to update privacy mode."); return; } setPrivacyStatus(enabled ? "Privacy mode enabled." : "Privacy mode disabled."); await loadDashboard(selectedId); await loadMoneyDatePrompts(selectedId); }; const maxCashflow = Math.max( 1, ...(dashboard?.cashflow ?? []).map((month) => Math.max(month.income, month.expenses)), ); const activeGoalTotal = goals .filter((goal) => goal.status !== "archived") .reduce((sum, goal) => sum + goal.currentAmount, 0); return (

Active household

{selectedHousehold && (

{selectedHousehold.name} combines joint, mine, and partner-owned accounts without exposing stable account IDs in the page data.

)}
setNewName(event.target.value)} placeholder="Household name" className={`${inputClass} mt-2`} />
{status &&

{status}

}
{loading ? (
Loading households...
) : dashboard ? ( <>

Total balance

{formatMoney(dashboard.summary.totalBalance)}

{dashboard.summary.accountCount} shared accounts

Available

{formatMoney(dashboard.summary.availableBalance)}

Current liquid view

Monthly net

= 0 ? "text-green-500" : "text-red-500"}`}> {formatMoney(dashboard.summary.monthlyNet)}

{formatMoney(dashboard.summary.monthlyIncome)} in, {formatMoney(dashboard.summary.monthlyExpenses)} out

Members

{dashboard.summary.memberCount}

Active household access

Health score

{dashboard.healthScore.score}

{healthRatingLabel(dashboard.healthScore.rating)}

Couples financial health

{dashboard.healthScore.score}

{healthRatingLabel(dashboard.healthScore.rating)}

{[ ["Cashflow", dashboard.healthScore.components.cashflow, 35], ["Balance buffer", dashboard.healthScore.components.balanceBuffer, 20], ["Goal progress", dashboard.healthScore.components.goalProgress, 20], ["Collaboration", dashboard.healthScore.components.collaboration, 15], ["Sync health", dashboard.healthScore.components.syncHealth, 10], ].map(([label, value, max]) => (
{label} {value}/{max}
))}

Next best moves

{dashboard.healthScore.recommendations.map((recommendation) => (

{recommendation}

))} {!dashboard.healthScore.recommendations.length && (

Shared cashflow, goals, collaboration, and sync health are all in strong shape.

)}

Money date prompts

Next household conversation

{moneyDatePrompts ? `${moneyDatePrompts.cadenceSuggestion} cadence based on cashflow, goals, sync health, and privacy settings.` : "Prompts are generated from the shared dashboard."}

{moneyDateStatus ?

{moneyDateStatus}

: null}
{(moneyDatePrompts?.prompts ?? []).map((prompt) => (

{prompt.topic}

{prompt.priority}

{prompt.question}

{prompt.why}

{prompt.actionLabel}

))} {!moneyDateStatus && !moneyDatePrompts?.prompts.length ? (

No prompts generated yet.

) : null}

Financial independence

Privacy mode

When enabled, the shared dashboard only shows joint household accounts and joint transactions. Individual mine/theirs balances and activity stay out of the shared view.

{privacyStatus ?

{privacyStatus}

: null}
{dashboard.privacyMode.enabled ? (

Individual balances hidden

Mine/theirs account balances are excluded from cards and ownership totals.

Individual transactions hidden

Recent transactions and cashflow use joint account activity only.

) : null}

Fair split calculator

Split shared expenses by income or custom share

Calculate a household split before applying the same percentages to a shared transaction.

{fairSplitResult ? (

Mine

{formatMoney(fairSplitResult.split.mineAmount)}

{fairSplitResult.split.minePercent}% share

Yours

{formatMoney(fairSplitResult.split.yoursAmount)}

{fairSplitResult.split.yoursPercent}% share

{fairSplitResult.rationale} Transaction default: {fairSplitResult.transactionDefaults.splitMode} split with ours attribution.

) : null}
setFairSplitDraft((prev) => ({ ...prev, expenseAmount: event.target.value }))} placeholder="Expense amount" className={inputClass} /> setFairSplitDraft((prev) => ({ ...prev, mineMonthlyIncome: event.target.value }))} placeholder="Mine monthly income" className={inputClass} /> setFairSplitDraft((prev) => ({ ...prev, yoursMonthlyIncome: event.target.value }))} placeholder="Yours monthly income" className={inputClass} /> {fairSplitDraft.splitMode === "custom" ? ( setFairSplitDraft((prev) => ({ ...prev, customMinePercent: event.target.value }))} placeholder="Mine percent" className={inputClass} /> ) : null} {fairSplitStatus ?

{fairSplitStatus}

: null}

Debt payoff teamwork

Plan a shared payoff order and contribution split

Compare avalanche, snowball, or custom priority and agree how much each partner contributes monthly.

{debtPayoffResult ? (

Payoff time

{debtPayoffResult.payoffMonths} months

{debtPayoffResult.payoffYears} years

Interest

{formatMoney(debtPayoffResult.totalInterestPaid)}

{formatMoney(debtPayoffResult.monthlyPayment)} monthly plan

Mine

{formatMoney(debtPayoffResult.contributionSplit.mineAmount)}

{debtPayoffResult.contributionSplit.minePercent}% monthly share

Yours

{formatMoney(debtPayoffResult.contributionSplit.yoursAmount)}

{debtPayoffResult.contributionSplit.yoursPercent}% monthly share

Payoff order

{debtPayoffResult.orderedDebts.map((debt) => (
{debt.payoffOrder}. {debt.name} {debt.annualPercentageRate}% APR · {formatMoney(debt.startingBalance)}
))}
) : null}
setDebtDraft((prev) => ({ ...prev, monthlyExtraPayment: event.target.value }))} placeholder="Extra monthly payment" className={inputClass} /> setDebtDraft((prev) => ({ ...prev, mineMonthlyIncome: event.target.value }))} placeholder="Mine monthly income" className={inputClass} /> setDebtDraft((prev) => ({ ...prev, yoursMonthlyIncome: event.target.value }))} placeholder="Yours monthly income" className={inputClass} /> {debtDraft.splitMode === "custom" ? ( setDebtDraft((prev) => ({ ...prev, customMinePercent: event.target.value }))} placeholder="Mine percent" className={inputClass} /> ) : null}
{debtDraft.debts.map((debt, index) => (
updateDebtDraft(index, "name", event.target.value)} placeholder="Debt name" className={inputClass} /> updateDebtDraft(index, "balance", event.target.value)} placeholder="Balance" className={inputClass} /> updateDebtDraft(index, "annualPercentageRate", event.target.value)} placeholder="APR %" className={inputClass} /> updateDebtDraft(index, "minimumPayment", event.target.value)} placeholder="Minimum" className={inputClass} /> updateDebtDraft(index, "priority", event.target.value)} placeholder="Order" className={inputClass} />
))}
{debtPayoffStatus ?

{debtPayoffStatus}

: null} {debtPayoffResult?.recommendations.length ? (

Team next steps

{debtPayoffResult.recommendations.map((recommendation) => (

{recommendation}

))}
) : null}

Future planning scenarios

Compare what-if household plans

Project shared milestones, income changes, expense changes, or net worth plans over time.

{futureResult ? (

Best final balance

{futureResult.comparison.bestFinalBalance.name}

{formatMoney(futureResult.comparison.bestFinalBalance.finalBalance)} after {futureResult.comparison.bestFinalBalance.horizonMonths} months

{futureResult.comparison.earliestTarget ? (

Earliest target

{futureResult.comparison.earliestTarget.name}

Month {futureResult.comparison.earliestTarget.targetReachedMonth}

) : null}
{futureResult.scenarios.map((scenario) => (

{scenario.name}

{scenario.recommendation}

{formatMoney(scenario.finalBalance)}

Net monthly: {formatMoney(scenario.monthlyNetChange)} Growth: {formatMoney(scenario.projectedGrowth)} Target: {scenario.targetReachedMonth ? `month ${scenario.targetReachedMonth}` : "not reached"}
))}
) : null}
{futureDraft.scenarios.map((scenario, index) => (
updateFutureScenario(index, "name", event.target.value)} placeholder="Scenario name" className={inputClass} /> updateFutureScenario(index, "startingBalance", event.target.value)} placeholder="Starting balance" className={inputClass} /> updateFutureScenario(index, "monthlyContribution", event.target.value)} placeholder="Monthly contribution" className={inputClass} /> updateFutureScenario(index, "monthlyIncome", event.target.value)} placeholder="Monthly income change" className={inputClass} /> updateFutureScenario(index, "monthlyExpenses", event.target.value)} placeholder="Monthly expense change" className={inputClass} /> updateFutureScenario(index, "targetAmount", event.target.value)} placeholder="Target amount" className={inputClass} />
updateFutureScenario(index, "horizonMonths", event.target.value)} placeholder="Months" className={inputClass} /> updateFutureScenario(index, "annualGrowthRate", event.target.value)} placeholder="Annual growth %" className={inputClass} />
))}
{futureStatus ?

{futureStatus}

: null}

Shared goals

{formatMoney(activeGoalTotal)} saved across {goals.filter((goal) => goal.status !== "archived").length} visible goals.

setGoalDraft((prev) => ({ ...prev, name: event.target.value }))} placeholder="Goal name" className={inputClass} /> setGoalDraft((prev) => ({ ...prev, targetAmount: event.target.value }))} placeholder="Target amount" type="number" min="0" step="0.01" className={inputClass} /> setGoalDraft((prev) => ({ ...prev, currentAmount: event.target.value }))} placeholder="Current amount" type="number" min="0" step="0.01" className={inputClass} /> setGoalDraft((prev) => ({ ...prev, targetDate: event.target.value }))} type="date" className={inputClass} />