// 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 ;
}
/* ─────────────────────────────────────────────────────────────
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.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 ?
: (
)}
);
}
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 ?
: (
)}
);
}
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 && (
)}
{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 (
);
}
function SettingPage({ theme }) {
return (
}>
);
}
window.SettingPage = SettingPage;