// pages.jsx — route page components for saraslot. // Loaded after games.jsx, before app.jsx. All data comes from Supabase via the // hooks in data.jsx; no MOCK_* arrays remain. const { useState: useStateP, useMemo: useMemoP } = React; /* ───────────────────────────────────────────────────────────── Shared utilities ──────────────────────────────────────────────────────────── */ function fmtAmount(n, currency) { const cur = currency || "MYR"; const v = Number(n || 0); return `${cur} ${v.toFixed(2)}`; } function fmtTs(iso) { if (!iso) return ""; try { const d = new Date(iso); const date = d.toLocaleDateString("en-GB", { day: "2-digit", month: "short", year: "numeric" }); const time = d.toLocaleTimeString("en-GB", { hour: "numeric", minute: "2-digit", hour12: true }).toUpperCase().replace(" ", ""); return `${date} ${time}`; } catch (e) { return String(iso); } } function PageEmpty({ title, sub, action }) { return (
{title}
{sub &&
{sub}
} {action &&
{action}
}
); } function PageLoading({ label }) { return
{label || "Loading…"}
; } /* ───────────────────────────────────────────────────────────── HomePage ──────────────────────────────────────────────────────────── */ function HomePage({ theme }) { return (
); } window.HomePage = HomePage; /* ───────────────────────────────────────────────────────────── ShopPage — products from `products` table ──────────────────────────────────────────────────────────── */ function ProductThumb({ p }) { const hue = p.hue ?? 38; const id = p.id || p.slug || "p"; return (
{(p.name || "").split(" ")[0]} {(p.name || "").split(" ").slice(1).join(" ") || "SLOT"} {p.badge && {p.badge}}
); } function ProductCard({ p }) { return (

{p.name}

{p.tagline}

{p.price}
); } function ShopPage({ theme }) { const { data, loading } = window.useProducts(); const b = window.useBranding ? window.useBranding() : window.BRANDING_DEFAULTS; const products = Array.isArray(data) ? data : []; return (
{b.shop_eyebrow}

{b.shop_title}

{products.length} titles
{loading && products.length === 0 ? : (
{products.map((p) => )}
)}
); } window.ShopPage = ShopPage; /* ───────────────────────────────────────────────────────────── HistoryPage — gated; pulls own transactions + bets ──────────────────────────────────────────────────────────── */ function TxRow({ tx }) { const typeClass = String(tx.kind || tx.type || "").toLowerCase(); const statusClass = String(tx.status || "").toLowerCase(); const amt = Number(tx.amount || 0); return (
{tx.kind || tx.type} ● {tx.status}
#{(tx.id || "").toString().slice(0, 12)}
Amount : {fmtAmount(amt, tx.currency)}
{tx.bank_name &&
Bank : {tx.bank_name}
} {tx.account_name &&
BankAccountName : {tx.account_name}
} {tx.account_no && tx.account_no !== "—" &&
BankAccountNumber : {tx.account_no}
} {tx.method &&
Method : {tx.method}
} {tx.remarks &&
Remarks : {tx.remarks}
}
{fmtTs(tx.created_at)}
); } function BetRow({ bet }) { const win = bet.result === "WIN"; return (
{bet.game} ● {bet.result}
#{(bet.id || "").toString().slice(0, 12)} · {bet.provider}
Stake : MYR {Number(bet.stake || 0).toFixed(2)}
Payout : MYR {Number(bet.payout || 0).toFixed(2)}
{fmtTs(bet.played_at)}
); } function HistoryBody() { const [tab, setTab] = useStateP("tx"); const txq = window.useTransactions(); const betq = window.useBets(); const txs = Array.isArray(txq.data) ? txq.data : []; const bets = Array.isArray(betq.data) ? betq.data : []; const loading = (tab === "tx" ? txq.loading : betq.loading); return (
{tab === "tx" ? "Transactions" : "Bet History"} Status
{loading ? : (
{tab === "tx" ? (txs.length === 0 ? : txs.map((t) => )) : (bets.length === 0 ? : bets.map((b) => )) }
)}
); } function HistoryPage({ theme }) { return ( }> ); } window.HistoryPage = HistoryPage; /* ───────────────────────────────────────────────────────────── PromoPage — promos table ──────────────────────────────────────────────────────────── */ function PromoCard({ p }) { const id = p.id || p.slug; const tags = Array.isArray(p.tags) ? p.tags : []; return (
{p.percent}
{p.title}

{p.body}

{tags.map((t) => {t})}
); } function PromoPage({ theme }) { const { data, loading } = window.usePromos(); const b = window.useBranding ? window.useBranding() : window.BRANDING_DEFAULTS; const promos = Array.isArray(data) ? data : []; return (

{b.promo_title}

{b.promo_sub}

{loading && promos.length === 0 ? : (
{promos.map((p) => )}
)}
); } window.PromoPage = PromoPage; /* ───────────────────────────────────────────────────────────── LiveChatPage — intro from site_settings('chat_intro') ──────────────────────────────────────────────────────────── */ const CHAT_INTRO_FALLBACK = { ways: [], deposit: [], contacts: [], sites: [] }; function LiveChatPage({ theme }) { const [draft, setDraft] = useStateP(""); const [history, setHistory] = useStateP([ { from: "bot", text: "Selamat Datang ke SARASLOT. How can we help today?" }, ]); const cfgQuery = window.useSiteSetting("chat_intro"); const cfg = (cfgQuery.data && cfgQuery.data.value) || CHAT_INTRO_FALLBACK; const b = window.useBranding ? window.useBranding() : window.BRANDING_DEFAULTS; const send = () => { const v = draft.trim(); if (!v) return; setHistory((h) => [...h, { from: "me", text: v }]); setDraft(""); setTimeout(() => { setHistory((h) => [...h, { from: "bot", text: "Thanks! A live agent will reply shortly." }]); }, 600); }; return (
9:14pm
{b.chat_brand}

{b.chat_welcome}

{b.chat_brand}
{cfg.ways?.length > 0 && (
CARA UNTUK
{cfg.ways.map((w) => ( • {w.label} ))}
)} {cfg.deposit?.length > 0 && (
CARA DEPOSIT
{cfg.deposit.map((d) => {d})}
)} {cfg.contacts?.length > 0 && (
CONTACT
{cfg.contacts.map((c) => (
{c.label}{c.value}
))}
)} {cfg.sites?.length > 0 && (
{cfg.sites.map((s) => ◆ {s})}
)}
{history.map((m, i) => (
{m.text}
))}
setDraft(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") send(); }} />
); } window.LiveChatPage = LiveChatPage; /* ───────────────────────────────────────────────────────────── SettingPage — gated; pulls own profile ──────────────────────────────────────────────────────────── */ function ProfileRow({ label, value }) { return (
{label} : {value || "—"}
); } function SettingMenuRow({ label, onClick }) { return ( ); } function SettingBody() { const { data: p, loading } = window.useProfile(); const handleLogout = async () => { if (window.signOut) await window.signOut(); }; if (loading || !p) return ; return (

PROFILE

SETTING

); } function SettingPage({ theme }) { return ( }> ); } window.SettingPage = SettingPage;