// A pure client-side SPA — a bill splitter. // // `renderMode = 'spa'` tells `voltro build` NOT to pre-render this page: // the client renders it on load. That is the right choice here because the // whole page is interactive and its state lives in the browser // (localStorage) — an SSR'd first paint would just be discarded on // hydration, so rendering it on the server buys nothing. // // No backend, no rpc client, no loader. Just React + the browser. import type { ReactNode } from 'react' import { useEffect, useState } from 'react' import type { PageMeta } from '@voltro/web' import { T } from '@voltro/i18n' import { getCatalog } from '../locales' // Locale-aware tab title: @voltro/web resolves meta({ locale }) from the // active locale (the `voltro:locale` cookie in this cookie-i18n app). export const meta = ({ locale }: { readonly locale: string }): PageMeta => ({ title: getCatalog(locale)['meta.home.title'], }) export const renderMode = 'spa' as const // `interactive` defaults to 'full' — every component hydrates. That is // correct for an SPA where the whole tree is interactive (don't reach for // islands here; islands only help when most of the page is static). const TIP_PRESETS = [10, 15, 18, 20] as const const STORAGE_KEY = '{{appName}}:last-tip' const money = (n: number): string => Number.isFinite(n) ? n.toLocaleString(undefined, { style: 'currency', currency: 'USD' }) : '—' export default function BillSplitter(): ReactNode { const [bill, setBill] = useState('') const [tipPct, setTipPct] = useState(18) const [people, setPeople] = useState(2) // Restore the last tip % from localStorage — client-only, so read it in // an effect (it never runs on the server). useEffect(() => { const saved = Number(window.localStorage.getItem(STORAGE_KEY)) if (Number.isFinite(saved) && saved > 0) setTipPct(saved) }, []) useEffect(() => { window.localStorage.setItem(STORAGE_KEY, String(tipPct)) }, [tipPct]) const billNum = Math.max(0, Number(bill) || 0) const headcount = Math.max(1, people) const tip = billNum * (tipPct / 100) const total = billNum + tip const perPerson = total / headcount return (