rspace-online/modules/rfunds/demo.ts

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 &mdash; 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>`;
}