/* ============================================================ UNIQUE Customer App — Pay, Sona AI chat, TabBar, Sheets, Toast ============================================================ */ const { useState: useStateP, useRef, useEffect } = React; // ── PAY / BILLING ────────────────────────────────────── function PayScreen({ paid, openPay, autopay, setAutopay, onBell, toast }) { const d = U_DATA; return (
{paid ? 'Current balance' : `Amount due · ${d.balance.dueDate}`}
{money(paid ? '0.00' : d.balance.due)}
{paid ? 'You\u2019re all paid up. Next bill 5 Jun.' : `${d.plan.name} plan · monthly`}
Auto-pay
Charge {d.methods[0].label} on the 5th
{ setAutopay(!autopay); toast(autopay ? 'Auto-pay off' : 'Auto-pay on'); }} style={{ width: 46, height: 28, borderRadius: 99, background: autopay ? 'var(--green)' : 'var(--line-2)', position: 'relative', cursor: 'pointer', transition: 'background .2s' }}>
toast('Add payment method')}>Payment methods
{d.methods.map((m, i) => (
{m.label.slice(0,4)}
{m.label}
{m.detail}
{i === 0 && Default}
))}
toast('Showing all invoices')}>Recent invoices
{d.invoices.map((inv, i) => (
toast(`${inv.id} — opening PDF…`)} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '14px 16px', borderBottom: i < d.invoices.length - 1 ? '1px solid var(--line)' : 'none', cursor: 'pointer' }}>
{inv.month}
{inv.id} · {inv.date}
{money(inv.amount)}
{inv.status}
))}
); } // ── SONA AI CHAT ─────────────────────────────────────── const SONA_REPLIES = [ { q: /slow|speed|buffer|lag/i, a: "I just ran a check on your line in Serrekunda 🛰️\n\n• Router: healthy\n• Local node: 6% congestion (normal)\n• Current: 73 Mbps down / 41 up\n\nThat's right where Surge should be. If a specific app feels slow, tell me which and I'll dig in." }, { q: /bill|pay|invoice|charge|cost/i, a: "Your next bill is D 2,800, due 5 Jun, and auto-pay is on with Wave. Nothing's overdue. Want me to email your May invoice or turn off auto-pay?" }, { q: /plan|upgrade|faster|apex/i, a: "You're on Surge (75 Mbps). Based on your evenings, Apex (200 Mbps, D 4,900) would give every TV its own lane. I can switch you from your next cycle — no downtime. Shall I prepare it?" }, { q: /engineer|visit|book|technician|install/i, a: "I can book a Unique engineer. Next available slots near you:\n\n• Tomorrow, 9–11am\n• Tomorrow, 2–4pm\n• Fri, 10am–12pm\n\nWhich works? It's free on your plan." }, { q: /outage|down|offline|no internet/i, a: "Good news — there's no outage on your node right now and your line shows Connected. If your Wi-Fi feels off, try the 'Pause & resume Wi-Fi' action on Home. Want me to restart your router remotely?" }, ]; const SONA_DEFAULT = "I'm Sona, your Unique assistant. I can check your connection, explain your bill, change your plan or book an engineer — all in plain English. What's up?"; function SonaScreen({ messages, setMessages, onBell, toast }) { const [input, setInput] = useStateP(''); const endRef = useRef(null); const [typing, setTyping] = useStateP(false); useEffect(() => { if (endRef.current) endRef.current.parentNode.scrollTop = endRef.current.parentNode.scrollHeight; }, [messages, typing]); const send = (text) => { const t = (text || input).trim(); if (!t) return; setMessages(m => [...m, { me: true, text: t }]); setInput(''); setTyping(true); setTimeout(() => { const hit = SONA_REPLIES.find(r => r.q.test(t)); setTyping(false); setMessages(m => [...m, { me: false, text: hit ? hit.a : SONA_DEFAULT }]); }, 1100); }; const chips = ['My internet is slow', 'Explain my bill', 'Upgrade my plan', 'Book an engineer']; return (
{messages.map((m, i) => (
{!m.me &&
}
{m.text}
))} {typing && (
{[0,1,2].map(i => )}
)}
{chips.map((c, i) => )}
setInput(e.target.value)} onKeyDown={e => e.key === 'Enter' && send()} placeholder="Message Sona…" style={{ flex: 1, padding: '13px 16px', borderRadius: 99, border: '1px solid var(--line-2)', background: 'var(--card)', fontSize: 14.5, fontFamily: 'var(--sans)', outline: 'none', color: 'var(--ink)' }} />
); } // ── TAB BAR ──────────────────────────────────────────── function TabBar({ active, go }) { const tabs = [['home', 'Home'], ['usage', 'Usage'], ['pay', 'Pay'], ['sona', 'Sona'], ['account', 'Account']]; return (
{tabs.map(([id, label]) => { const on = active === id; return ( ); })}
); } // ── PAY SHEET (flow) ─────────────────────────────────── function PaySheet({ open, onClose, onPaid }) { const [stage, setStage] = useStateP('select'); // select | processing | done const [method, setMethod] = useStateP(U_DATA.methods[0]); useEffect(() => { if (open) setStage('select'); }, [open]); const pay = () => { setStage('processing'); setTimeout(() => setStage('done'), 1700); }; return ( {stage === 'select' && (
Amount due {money(U_DATA.balance.due)}
Pay with
{U_DATA.methods.map((m, i) => ( ))}
)} {stage === 'processing' && (
Processing with {method.label}…
)} {stage === 'done' && (
Payment received
{money(U_DATA.balance.due)} paid with {method.label}. A receipt is on its way.
)} ); } // ── PLAN SHEET ───────────────────────────────────────── function PlanSheet({ open, onClose, plan, setPlan, toast }) { const [sel, setSel] = useStateP(plan.name); useEffect(() => { if (open) setSel(plan.name); }, [open, plan]); return (
{U_DATA.plans.map((p, i) => ( ))}
Changes apply from your next billing cycle. No downtime.
); } // ── SPEED TEST SHEET ─────────────────────────────────── function SpeedSheet({ open, onClose }) { const [val, setVal] = useStateP(0); const [done, setDone] = useStateP(false); useEffect(() => { if (!open) return; setVal(0); setDone(false); let v = 0; const id = setInterval(() => { v += Math.random() * 9; if (v >= 73.4) { v = 73.4; clearInterval(id); setDone(true); } setVal(v); }, 80); return () => clearInterval(id); }, [open]); return (
{val.toFixed(1)} Mbps down
{[['Upload', done ? '41.2' : '—', 'Mbps'], ['Ping', done ? '12' : '—', 'ms'], ['Jitter', done ? '3' : '—', 'ms']].map((m, i) => (
{m[0]}
{m[1]}
))}
{done &&
✓ Excellent — right on target for Surge
}
); } // ── TOAST ────────────────────────────────────────────── function Toast({ msg }) { if (!msg) return null; return (
{msg}
); } Object.assign(window, { PayScreen, SonaScreen, TabBar, PaySheet, PlanSheet, SpeedSheet, Toast, SONA_DEFAULT });