257 lines
12 KiB
TypeScript
257 lines
12 KiB
TypeScript
/**
|
|
* rFunds demo page — group expense tracking for "Alpine Explorer 2026".
|
|
*
|
|
* Renders server-side HTML skeleton with budget overview, expense list,
|
|
* balances, settlements, category breakdown, and per-person stats.
|
|
* The client-side funds-demo.ts hydrates via WebSocket (DemoSync).
|
|
*/
|
|
|
|
/* ─── Constants ─────────────────────────────────────────────── */
|
|
|
|
const MEMBERS = [
|
|
{ name: "Maya", initial: "M", color: "#10b981", bgClass: "rd-bg-emerald" },
|
|
{ name: "Liam", initial: "L", color: "#06b6d4", bgClass: "rd-bg-cyan" },
|
|
{ name: "Priya", initial: "P", color: "#8b5cf6", bgClass: "rd-bg-violet" },
|
|
{ name: "Omar", initial: "O", color: "#f59e0b", bgClass: "rd-bg-amber" },
|
|
];
|
|
|
|
const CATEGORIES = [
|
|
{ key: "transport", icon: "\u{1F682}", label: "Transport", colorClass: "rd-progress__fill--cyan", badgeClass: "rd-badge--sky", textClass: "rd-cyan" },
|
|
{ key: "accommodation", icon: "\u{1F3E8}", label: "Accommodation", colorClass: "rd-progress__fill--violet", badgeClass: "rd-badge--teal", textClass: "rd-violet" },
|
|
{ key: "activity", icon: "\u26F7", label: "Activities", colorClass: "rd-progress__fill--amber", badgeClass: "rd-badge--amber", textClass: "rd-amber" },
|
|
{ key: "food", icon: "\u{1F372}", label: "Food & Drink", colorClass: "rd-progress__fill--rose", badgeClass: "rd-badge--rose", textClass: "rd-rose" },
|
|
];
|
|
|
|
/* ─── Render ─────────────────────────────────────────────── */
|
|
|
|
export function renderDemo(): string {
|
|
return `
|
|
<div class="rd-root" style="--rd-accent-from: #f59e0b; --rd-accent-to: #10b981;">
|
|
|
|
<!-- ── Hero ── -->
|
|
<section class="rd-hero">
|
|
<h1>Alpine Explorer 2026</h1>
|
|
<p class="rd-subtitle">Group Expenses</p>
|
|
<div class="rd-meta">
|
|
<span>\u{1F4C5} Jul 6-20, 2026</span>
|
|
<span style="color:#475569">|</span>
|
|
<span>\u{1F465} ${MEMBERS.length} travelers</span>
|
|
<span style="color:#475569">|</span>
|
|
<span>\u{1F3D4} Chamonix \u2192 Zermatt \u2192 Dolomites</span>
|
|
</div>
|
|
<div class="rd-avatars">
|
|
${MEMBERS.map(
|
|
(m) =>
|
|
`<div class="rd-avatar ${m.bgClass}" title="${m.name}">${m.initial}</div>`,
|
|
).join("\n ")}
|
|
<span class="rd-count">${MEMBERS.length} members</span>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ── Status bar ── -->
|
|
<div class="rd-section rd-section--narrow">
|
|
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:1.5rem; flex-wrap:wrap; gap:0.75rem">
|
|
<div style="display:flex; align-items:center; gap:0.75rem; flex-wrap:wrap">
|
|
<span id="rd-conn-badge" class="rd-status rd-status--disconnected">Disconnected</span>
|
|
<span class="rd-badge rd-badge--amber" style="font-size:0.7rem">Live — synced across all r* demos</span>
|
|
</div>
|
|
<button id="rd-reset-btn" class="rd-btn rd-btn--ghost" disabled>
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
|
|
Reset Demo
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Loading state ── -->
|
|
<div class="rd-section rd-section--narrow">
|
|
<div id="rd-loading" class="rd-card" style="border:2px dashed rgba(100,116,139,0.4); display:none">
|
|
<div class="rd-card-body" style="padding:3rem; text-align:center">
|
|
<div style="width:2rem; height:2rem; margin:0 auto 0.75rem; border:3px solid rgba(245,158,11,0.2); border-top-color:#f59e0b; border-radius:50%; animation:rd-spin 0.8s linear infinite"></div>
|
|
<p class="rd-text-muted">Connecting to rSpace...</p>
|
|
</div>
|
|
</div>
|
|
<style>@keyframes rd-spin { to { transform: rotate(360deg); } }</style>
|
|
|
|
<!-- Empty state -->
|
|
<div id="rd-empty" class="rd-card" style="border:2px dashed rgba(100,116,139,0.4); display:none">
|
|
<div class="rd-card-body" style="padding:3rem; text-align:center">
|
|
<div style="font-size:2rem; margin-bottom:0.75rem">\u{1F4B0}</div>
|
|
<p class="rd-text-muted">No expense data found. Try resetting the demo.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Budget Overview ── -->
|
|
<section id="rd-budget-section" class="rd-section rd-section--narrow" style="display:none">
|
|
<div class="rd-card" style="padding:1.5rem; margin-bottom:1.5rem;">
|
|
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:1rem;">
|
|
<h2 style="font-size:1.125rem; font-weight:600; color:#f1f5f9; margin:0; display:flex; align-items:center; gap:0.5rem;">
|
|
\u{1F4CA} Trip Budget
|
|
</h2>
|
|
<span class="rd-live">live</span>
|
|
</div>
|
|
|
|
<!-- Budget totals: 3-column stat grid -->
|
|
<div class="rd-grid rd-grid--3" style="margin-bottom:1rem;">
|
|
<div class="rd-stat">
|
|
<p class="rd-stat__value" id="rd-budget-total">\u20AC4,000</p>
|
|
<p class="rd-stat__label">Total Budget</p>
|
|
</div>
|
|
<div class="rd-stat">
|
|
<p class="rd-stat__value rd-emerald" id="rd-budget-spent">\u20AC0</p>
|
|
<p class="rd-stat__label">Spent</p>
|
|
</div>
|
|
<div class="rd-stat">
|
|
<p class="rd-stat__value rd-cyan" id="rd-budget-remaining">\u20AC4,000</p>
|
|
<p class="rd-stat__label" id="rd-budget-remaining-label">Remaining</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Progress bar -->
|
|
<div style="margin-bottom:1rem;">
|
|
<div style="display:flex; align-items:center; justify-content:space-between; font-size:0.75rem; color:#94a3b8; margin-bottom:0.375rem;">
|
|
<span id="rd-budget-pct-label">0% used</span>
|
|
<span id="rd-budget-left-label">\u20AC4,000 left</span>
|
|
</div>
|
|
<div class="rd-progress">
|
|
<div class="rd-progress__fill rd-progress__fill--emerald" id="rd-budget-bar" style="width:0%"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Category breakdown -->
|
|
<div>
|
|
<h3 style="font-size:0.875rem; font-weight:600; color:#cbd5e1; margin:0 0 0.75rem;">Budget by Category</h3>
|
|
<div class="rd-grid rd-grid--2" id="rd-category-breakdown">
|
|
${CATEGORIES.map(
|
|
(cat) => `
|
|
<div class="rd-stat" style="padding:0.75rem;" data-category="${cat.key}">
|
|
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:0.5rem;">
|
|
<span style="display:flex; align-items:center; gap:0.5rem; font-size:0.875rem; color:#e2e8f0;">
|
|
${cat.icon} ${cat.label}
|
|
</span>
|
|
<span class="rd-text-xs rd-text-muted" data-cat-amounts>\u20AC0 / \u20AC1,000</span>
|
|
</div>
|
|
<div class="rd-progress rd-progress--sm">
|
|
<div class="${cat.colorClass}" style="height:100%; border-radius:9999px; width:0%; transition:width 0.3s" data-cat-bar></div>
|
|
</div>
|
|
<p class="rd-text-xs rd-text-dim" style="margin:0.25rem 0 0;" data-cat-pct>0% used</p>
|
|
</div>`,
|
|
).join("")}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ── Expenses + Balances grid ── -->
|
|
<section id="rd-expenses-section" class="rd-section" style="display:none">
|
|
<div style="display:grid; grid-template-columns:1fr; gap:1rem;">
|
|
|
|
<!-- Expense list (left 2/3 on desktop) -->
|
|
<div class="rd-card" id="rd-expense-card" style="grid-column:1;">
|
|
<div class="rd-card-header">
|
|
<div class="rd-card-title"><span class="rd-icon">\u{1F4DD}</span> <span id="rd-expense-count">Expenses (0)</span></div>
|
|
<span class="rd-text-xs rd-text-muted">Click amount to edit</span>
|
|
</div>
|
|
<div id="rd-expense-list">
|
|
<!-- Populated by funds-demo.ts -->
|
|
</div>
|
|
<!-- Total row -->
|
|
<div style="display:flex; align-items:center; justify-content:space-between; padding:0.75rem 1.25rem; border-top:1px solid rgba(51,65,85,0.5); background:rgba(51,65,85,0.2);">
|
|
<span style="font-size:0.875rem; font-weight:600; color:#cbd5e1;">Total</span>
|
|
<span style="font-size:1.125rem; font-weight:700; color:white;" id="rd-expense-total">\u20AC0</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Balances (right 1/3 on desktop) -->
|
|
<div style="display:flex; flex-direction:column; gap:1rem;">
|
|
<div class="rd-card" id="rd-balances">
|
|
<div class="rd-card-header">
|
|
<div class="rd-card-title"><span class="rd-icon">\u2696\uFE0F</span> Balances</div>
|
|
</div>
|
|
<div class="rd-card-body" id="rd-balances-body">
|
|
<!-- Populated by funds-demo.ts -->
|
|
</div>
|
|
</div>
|
|
|
|
<div class="rd-card" id="rd-settlements">
|
|
<div class="rd-card-header">
|
|
<div class="rd-card-title"><span class="rd-icon">\u{1F4B8}</span> Settle Up</div>
|
|
</div>
|
|
<div class="rd-card-body" id="rd-settlements-body">
|
|
<!-- Populated by funds-demo.ts -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ── Spending by Category ── -->
|
|
<section id="rd-spending-section" class="rd-section" style="display:none">
|
|
<div class="rd-card" style="padding:1.5rem;">
|
|
<h2 style="font-size:1.125rem; font-weight:600; margin:0 0 1rem; display:flex; align-items:center; gap:0.5rem;">
|
|
\u{1F4CA} Spending by Category
|
|
</h2>
|
|
<div class="rd-grid rd-grid--4" id="rd-spending-grid">
|
|
${CATEGORIES.map(
|
|
(cat) => `
|
|
<div class="rd-stat" data-spending-cat="${cat.key}">
|
|
<div style="font-size:1.5rem; margin-bottom:0.5rem;">${cat.icon}</div>
|
|
<p style="font-size:0.875rem; color:#cbd5e1; font-weight:500; margin:0;">${cat.label}</p>
|
|
<p style="font-size:1.125rem; font-weight:700; color:white; margin:0.25rem 0;" data-spending-amount>\u20AC0</p>
|
|
<div class="rd-progress rd-progress--xs" style="margin-bottom:0.25rem;">
|
|
<div class="${cat.colorClass}" style="height:100%; border-radius:9999px; width:0%; transition:width 0.3s" data-spending-bar></div>
|
|
</div>
|
|
<p class="rd-text-xs rd-text-dim" style="margin:0;" data-spending-pct>0% of total</p>
|
|
</div>`,
|
|
).join("")}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ── Per Person ── -->
|
|
<section id="rd-person-section" class="rd-section" style="display:none">
|
|
<div class="rd-card" style="padding:1.5rem;">
|
|
<h2 style="font-size:1.125rem; font-weight:600; margin:0 0 1rem; display:flex; align-items:center; gap:0.5rem;">
|
|
\u{1F464} Per Person
|
|
</h2>
|
|
<div class="rd-grid rd-grid--4" id="rd-person-grid">
|
|
${MEMBERS.map(
|
|
(m) => `
|
|
<div class="rd-stat" data-person="${m.name}">
|
|
<div class="rd-avatar ${m.bgClass}" style="width:3rem; height:3rem; font-size:1.125rem; margin:0 auto 0.5rem; box-shadow:0 0 0 2px #1e293b;">${m.initial}</div>
|
|
<p style="font-size:0.875rem; color:#cbd5e1; font-weight:500; margin:0;">${m.name}</p>
|
|
<p style="font-size:1.125rem; font-weight:700; color:white; margin:0.25rem 0;" data-person-paid>\u20AC0</p>
|
|
<p class="rd-text-xs rd-text-dim" style="margin:0;" data-person-pct>paid (0%)</p>
|
|
<p style="font-size:0.875rem; font-weight:600; margin:0.25rem 0 0;" data-person-balance>\u20AC0</p>
|
|
</div>`,
|
|
).join("")}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ── CTA ── -->
|
|
<section class="rd-section rd-section--narrow">
|
|
<div class="rd-cta">
|
|
<h2>Track Your Group Expenses</h2>
|
|
<p>
|
|
rFunds makes it easy to manage shared costs, track budgets, and settle up.
|
|
Design custom funding flows with threshold-based mechanisms.
|
|
</p>
|
|
<a href="/create-space" style="background:linear-gradient(135deg, #f59e0b, #10b981); box-shadow:0 8px 24px rgba(245,158,11,0.25);">
|
|
Create Your Space
|
|
</a>
|
|
</div>
|
|
</section>
|
|
|
|
</div>
|
|
|
|
<style>
|
|
/* Responsive grid for expenses + balances on desktop */
|
|
@media (min-width: 768px) {
|
|
#rd-expenses-section > div {
|
|
grid-template-columns: 2fr 1fr;
|
|
}
|
|
}
|
|
</style>`;
|
|
}
|