/* global React, Wordmark, Money, Sparkline, Donut, BarChart, AreaChart, Progress, Avatar, Icon */
const { useState: useStateApp, useMemo: useMemoApp } = React;
/* ============================================================
PRODUCT APP — shared shell + screens
============================================================ */
function AppShell({ goTo, view, theme, wordmark, children }) {
const nav = [
["dashboard", "Overview", "home"],
["budget", "Budget", "pie"],
["goals", "Goals", "target"],
["invest", "Invest", "seed"],
["transactions", "Activity", "list"],
["settings", "Settings", "settings"],
];
return (
{new Date().toLocaleDateString("en-US",{ weekday: "long", month: "long", day: "numeric" })}
Good morning, Alex.
{children}
);
}
/* ===== DASHBOARD ===== */
function Dashboard() {
return (
Net worth
↑ $1,284.42 today · +12.3% YTD
{["1W","1M","3M","1Y","All"].map((t, i) => (
))}
$3,006 / $3,500
$1,200/wk
NEXT · TUE, MAY 13 · INDEX 80/20
$64,321.55
+$241.18 INTEREST IN APRIL
{[
["Trader Joe's", "Groceries", -42.18, "Today", "cart"],
["Auto-invest deposit", "Invest", -300, "Today", "seed"],
["Stripe: Salary", "Income", 5840, "Yesterday", "arrowDown"],
["Verve Coffee", "Dining", -7.25, "Yesterday", "coffee"],
["Vacation goal", "Tokyo", -120, "Mon", "plane"],
].map((t, i) => (
{t[3]}
0 ? "var(--positive)" : "var(--ink)", fontSize: 13 }}>
{t[2] > 0 ? "+" : "−"}${Math.abs(t[2]).toFixed(2)}
))}
Kaizen insight
"You'd hit your Tokyo goal six weeks earlier by raising auto-invest from $240 to $300/wk."
{[
["Tokyo trip", 4280, 6000, "var(--accent)", "plane"],
["Down payment", 18420, 60000, "var(--accent-2)", "house"],
["New bike", 1240, 2200, "#c47a08", "car"],
].map((g, i) => (
{g[0]}
${g[1].toLocaleString()} / ${g[2].toLocaleString()}
))}
);
function Legend({ swatch, label, v }) {
return (
{label}
{v}
);
}
}
/* ===== BUDGET ===== */
function BudgetView() {
const cats = [
{ name: "Rent", v: 1850, max: 1850, color: "var(--ink)", icon: "house" },
{ name: "Groceries", v: 412, max: 600, color: "var(--accent)", icon: "cart" },
{ name: "Transit", v: 88, max: 150, color: "var(--accent-2)", icon: "car" },
{ name: "Dining", v: 246, max: 300, color: "#c47a08", icon: "coffee" },
{ name: "Subscriptions", v: 92, max: 100, color: "var(--ink-2)", icon: "card" },
{ name: "Discretionary", v: 318, max: 500, color: "var(--negative)", icon: "sparkle" },
];
return (
May · week 2 of 4
of $3,500
({ value: c.v, color: c.color }))} label="86%" sublabel="of plan"/>
Categories
{cats.map((c, i) => {
const pct = (c.v / c.max) * 100;
return (
{c.name}
{Math.round(pct)}% used
${c.v} / ${c.max}
);
})}
Pattern detected
Dining out spikes on Fridays.
Six of your last seven Fridays exceeded $40. Want to set a Friday cap of $25?
);
}
/* ===== GOALS ===== */
function GoalsView() {
const goals = [
{ name: "Tokyo trip", icon: "plane", saved: 4280, target: 6000, eta: "Oct 2026", weekly: 240, color: "var(--accent)", note: "October peak season. Book flights early." },
{ name: "Down payment", icon: "house", saved: 18420, target: 60000, eta: "Mar 2028", weekly: 380, color: "var(--accent-2)", note: "Auto-invested in conservative portfolio" },
{ name: "New bike", icon: "car", saved: 1240, target: 2200, eta: "Aug 2026", weekly: 60, color: "#c47a08", note: "Almost there, 56% complete." },
{ name: "Emergency fund", icon: "shield", saved: 9600, target: 12000, eta: "On schedule", weekly: 80, color: "var(--ink)", note: "6 months of expenses" },
{ name: "Wedding", icon: "sparkle", saved: 3100, target: 25000, eta: "Sep 2027", weekly: 280, color: "var(--negative)", note: "Just started saving" },
{ name: "Tuition", icon: "grad", saved: 8400, target: 18000, eta: "Aug 2026", weekly: 200, color: "var(--warn)", note: "On track for fall semester" },
];
return (
Saving for
across 6 goals
$1,240/WK AUTO-ALLOCATED · ALL ON TRACK
{goals.map((g, i) => {
const pct = (g.saved / g.target) * 100;
return (
{g.name}
of ${g.target.toLocaleString()}
);
})}
);
}
/* ===== INVEST ===== */
function InvestView() {
const holdings = [
{ sym: "VTI", name: "Total US Stock", v: 110023.7, alloc: 56, day: 1.2, color: "var(--accent)" },
{ sym: "VXUS", name: "Total Intl Stock", v: 47140.9, alloc: 24, day: 0.4, color: "var(--accent-2)" },
{ sym: "BND", name: "Total Bond", v: 31427.3, alloc: 16, day: -0.1, color: "#c47a08" },
{ sym: "CASH", name: "Cash buffer", v: 7829.0, alloc: 4, day: 0.0, color: "var(--ink-3)" },
];
return (
Index portfolio
↑ +$24,103 (+14.0%) all-time · +$1,284 today
{[["1D","+0.66%"],["1W","+1.4%"],["1M","+2.1%"],["1Y","+12.4%"],["3Y","+38.2%"],["All","+14.0%"]].map((p, i) => (
))}
Allocation · 80/20
({ value: h.alloc, color: h.color }))} label="80/20" sublabel="stocks/bonds"/>
{holdings.map((h, i) => (
{h.sym}
{h.alloc}%
))}
Tax-loss harvesting
$1,284
Saved this year. Automatic, daily, in the background.
Holdings
| Symbol | Name | Allocation | Value | Day | Trend |
{holdings.map((h, i) => (
| {h.sym} |
{h.name} |
{h.alloc}% |
${h.v.toLocaleString(undefined, {minimumFractionDigits: 2})} |
0 ? "var(--positive)" : h.day < 0 ? "var(--negative)" : "var(--ink-3)" }}>
{h.day > 0 ? "+" : ""}{h.day.toFixed(1)}%
|
v + (h.day < 0 ? -2 : 0))} stroke={h.color} w={80} h={24}/>
|
))}
);
}
/* ===== TRANSACTIONS ===== */
function TransactionsView() {
const tx = [
{ d: "Today", items: [
["Trader Joe's", "Groceries", -42.18, "Chase ••4421", "cart"],
["Auto-invest deposit", "Invest · weekly", -300, "Kaizen Brokerage", "seed"],
["Spotify", "Subscriptions", -10.99, "Chase ••4421", "card"],
]},
{ d: "Yesterday", items: [
["Stripe: Payroll", "Income", 5840.00, "Chase ••4421", "arrowDown"],
["Verve Coffee", "Dining", -7.25, "Chase ••4421", "coffee"],
["Lyft", "Transit", -14.20, "Chase ••4421", "car"],
["Vacation goal: Tokyo", "Goal transfer", -120.00, "Goals reserve", "plane"],
]},
{ d: "Mon, May 5", items: [
["Whole Foods", "Groceries", -88.40, "Chase ••4421", "cart"],
["Dividend: VTI", "Income", 142.31, "Kaizen Brokerage", "arrowDown"],
["Comcast", "Subscriptions", -65.00, "Chase ••4421", "card"],
]},
{ d: "Sat, May 3", items: [
["Olive Garden", "Dining", -64.22, "Chase ••4421", "coffee"],
["Uniqlo", "Shopping", -118.50, "Chase ••4421", "cart"],
]},
];
return (
{tx.map((day, i) => (
{day.d}
{day.items.length} items · ${day.items.reduce((s, it) => s + it[2], 0).toFixed(2)}
{day.items.map((t, j) => (
{t[3]}
0 ? "var(--positive)" : "var(--ink)" }}>
{t[2] > 0 ? "+" : "−"}${Math.abs(t[2]).toFixed(2)}
))}
))}
);
function SumCell({ label, value, positive, large, prefix = "$", decimals = 2 }) {
return (
{label}
{value < 0 ? "−" : ""}{prefix}{Math.abs(value).toLocaleString(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals })}
);
}
}
/* ===== ONBOARDING ===== */
function OnboardingView({ goTo, wordmark = "slash" }) {
const [step, setStep] = useStateApp(0);
const steps = ["Welcome","Connect bank","Pick goals","Set auto-invest","Review"];
return (
{steps.map((s, i) => (
{String(i + 1).padStart(2, "0")}
{s}
))}
{step === 0 && setStep(1)} />}
{step === 1 && setStep(2)} />}
{step === 2 && setStep(3)} />}
{step === 3 && setStep(4)} />}
{step === 4 && goTo("dashboard")} />}
);
}
function OnbWelcome({ onNext }) {
return (
Welcome
Money is a practice.
Setup takes 4 minutes. We'll connect your bank, set up two or three goals,
and turn on auto-invest. You can change anything, any time.
);
}
function OnbBank({ onNext }) {
const banks = [
{ name: "Chase", logo: "C", color: "#117ACA" },
{ name: "Bank of America", logo: "B", color: "#E31837" },
{ name: "Wells Fargo", logo: "W", color: "#D71E28" },
{ name: "Citi", logo: "Ci", color: "#003D7A" },
{ name: "Capital One", logo: "C1", color: "#D03027" },
{ name: "Ally", logo: "A", color: "#7F2487" },
{ name: "SoFi", logo: "S", color: "#00A6E2" },
{ name: "Schwab", logo: "Sc", color: "#0C84B6" },
];
return (
Step 02 of 05
Connect your bank.
Read-only access via Plaid. We never see your password.
{banks.map((b, i) => (
))}
Read-only by default
We can see balances and transactions, never move money without permission.
);
}
function OnbGoals({ onNext }) {
const presets = [
{ name: "Vacation", icon: "plane", color: "var(--accent)" },
{ name: "Down payment", icon: "house", color: "var(--accent-2)" },
{ name: "New car", icon: "car", color: "#c47a08" },
{ name: "Emergency fund", icon: "shield", color: "var(--ink)" },
{ name: "Wedding", icon: "sparkle", color: "var(--negative)" },
{ name: "Tuition", icon: "grad", color: "var(--warn)" },
];
const [picked, setPicked] = useStateApp(["Vacation", "Emergency fund"]);
const toggle = (n) => setPicked(p => p.includes(n) ? p.filter(x => x !== n) : [...p, n]);
return (
Step 03 of 05
What are you saving for?
Pick one or many. We'll figure out the math.
{presets.map((p, i) => {
const on = picked.includes(p.name);
return (
);
})}
);
}
function OnbInvest({ onNext }) {
const [risk, setRisk] = useStateApp(2);
const profiles = [
{ name: "Conservative", split: "30/70", desc: "Bond-heavy. Lower swings, lower upside.", color: "var(--ink-3)" },
{ name: "Balanced", split: "60/40", desc: "Classic mix. Smooth ride.", color: "#c47a08" },
{ name: "Growth", split: "80/20", desc: "Stock-heavy. Recommended for your horizon.", color: "var(--accent)" },
{ name: "Aggressive", split: "100/0", desc: "All stocks. For long timelines and steady stomachs.", color: "var(--negative)" },
];
return (
Step 04 of 05
Pick your portfolio.
Index funds. We'll rebalance automatically.
{profiles.map((p, i) => (
))}
Auto-invest
/ week
);
}
function OnbReview({ onDone }) {
return (
Step 05 of 05
You're ready.
Here's what we'll do. Change anything any time.
);
function ReviewRow({ label, v, tone }) {
return (
{label}
{v}
);
}
}
/* ===== SETTINGS ===== */
function SettingsView() {
return (
Profile
Alex Morgan
alex@morgan.io · Member since Mar 2024
Security
Linked accounts
Danger zone
Pause auto-invest, withdraw funds, or close your account. No exit fees.
);
}
function SetRow({ label, v, pos, download, toggle }) {
return (
{label}
{v}
{download && }
{toggle &&
}
);
}
/* Export */
Object.assign(window, {
AppShell, Dashboard, BudgetView, GoalsView, InvestView, TransactionsView, OnboardingView, SettingsView,
});