Files
dark-lord/app/vault/page.tsx
2026-04-26 22:28:40 -07:00

658 lines
33 KiB
TypeScript

"use client";
import Link from "next/link";
import { useState, useEffect, useCallback, useRef } from "react";
import { useAccount } from "@/contexts/AccountContext";
// ─── Types ───────────────────────────────────────────────────────────────────
type Msg = { id: string; from: string; text: string; ts: number; channel?: string };
type HandleRecord = {
voidCredits: number;
claimedInvoices: Record<string, number>;
unlockedTools: { toolId: string; unlockedAt: number; voidSpent: number }[];
};
type LedgerResponse = { ok: boolean; handles: Record<string, HandleRecord>; invoiceIndex: Record<string, string> };
type RaffleStats = { ok: boolean; drawAt: number; totalTickets: number; participants: number };
type RaffleEntry = { handle: string; tickets: number; boughtAt: number };
// ─── Helpers ─────────────────────────────────────────────────────────────────
function fmt(ms: number) {
if (ms <= 0) return "READY TO DRAW";
const s = Math.floor(ms / 1000);
const d = Math.floor(s / 86400);
const h = Math.floor((s % 86400) / 3600);
const m = Math.floor((s % 3600) / 60);
const sec = s % 60;
if (d > 0) return `${d}d ${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(sec).padStart(2, "0")}`;
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(sec).padStart(2, "0")}`;
}
function StatCard({ label, value, sub, accent }: { label: string; value: string | number; sub?: string; accent?: string }) {
return (
<div className="rounded-lg border border-[#00ff66]/20 bg-black/50 p-4">
<div className="text-[9px] uppercase tracking-widest text-zinc-500 mb-1">{label}</div>
<div className={`text-2xl font-black font-mono tabular-nums ${accent ?? "text-[#00ff66]"}`}>{value}</div>
{sub && <div className="text-[10px] text-zinc-600 mt-1">{sub}</div>}
</div>
);
}
type Tab = "overview" | "credits" | "raffle" | "comms" | "logs";
// ─── Main ─────────────────────────────────────────────────────────────────────
export default function VaultAdminDashboard() {
const { user, hydrated, signIn } = useAccount();
const [tab, setTab] = useState<Tab>("overview");
const [loginPass, setLoginPass] = useState("");
const [loginError, setLoginError] = useState("");
// BTC
const [btcUsd, setBtcUsd] = useState<number | null>(null);
// Ledger
const [handles, setHandles] = useState<Record<string, HandleRecord>>({});
const [invoiceIndex, setInvoiceIndex] = useState<Record<string, string>>({});
// Raffle
const [raffleStats, setRaffleStats] = useState<RaffleStats | null>(null);
const [raffleEntries, setRaffleEntries] = useState<RaffleEntry[]>([]);
const [raffleRemaining, setRaffleRemaining] = useState(0);
const [drawResult, setDrawResult] = useState<string | null>(null);
// Credit ops
const [creditHandle, setCreditHandle] = useState("");
const [creditAmt, setCreditAmt] = useState("");
const [creditMsg, setCreditMsg] = useState<{ text: string; ok: boolean } | null>(null);
// Comms
const [messages, setMessages] = useState<Msg[]>([]);
const [broadcast, setBroadcast] = useState("");
// Logs
const [terminalLines, setTerminalLines] = useState<string[]>([
`[BOOT] CyberLux admin shell initialised.`,
`[AUTH] drjones authenticated via local session.`,
]);
const logRef = useRef<HTMLDivElement>(null);
// ── Fetchers ────────────────────────────────────────────────────────────────
const fetchBtc = useCallback(async () => {
try {
const r = await fetch("https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd", { cache: "no-store" });
const j = await r.json() as { bitcoin?: { usd?: number } };
setBtcUsd(j.bitcoin?.usd ?? null);
} catch { /* ignore */ }
}, []);
const fetchLedger = useCallback(async () => {
try {
const r = await fetch("/api/admin/ledger");
if (!r.ok) return;
const d = await r.json() as LedgerResponse;
if (d.ok) { setHandles(d.handles); setInvoiceIndex(d.invoiceIndex); }
} catch { /* ignore */ }
}, []);
const fetchRaffle = useCallback(async () => {
try {
const r = await fetch("/api/raffle/buy");
const d = await r.json() as RaffleStats;
if (d.ok) setRaffleStats(d);
const r2 = await fetch("/api/admin/raffle");
if (r2.ok) {
const d2 = await r2.json() as { ok: boolean; entries: RaffleEntry[] };
if (d2.ok) setRaffleEntries(d2.entries);
}
} catch { /* ignore */ }
}, []);
const fetchMessages = useCallback(async () => {
try {
const r = await fetch("/api/messages?channel=all");
const d = await r.json() as { ok: boolean; messages: Msg[] };
if (d.ok) setMessages(d.messages);
} catch { /* ignore */ }
}, []);
const log = useCallback((line: string) => {
setTerminalLines((prev) => {
const next = [...prev.slice(-99), line];
return next;
});
setTimeout(() => { logRef.current?.scrollTo({ top: 9999, behavior: "smooth" }); }, 50);
}, []);
// ── Init ────────────────────────────────────────────────────────────────────
useEffect(() => {
if (!user || user.username !== "drjones") return;
void fetchBtc();
void fetchLedger();
void fetchRaffle();
void fetchMessages();
const id = setInterval(() => { void fetchBtc(); void fetchLedger(); void fetchRaffle(); void fetchMessages(); }, 15_000);
return () => clearInterval(id);
}, [user, fetchBtc, fetchLedger, fetchRaffle, fetchMessages]);
// Raffle countdown
useEffect(() => {
if (!raffleStats) return;
const t = setInterval(() => setRaffleRemaining(raffleStats.drawAt - Date.now()), 1000);
return () => clearInterval(t);
}, [raffleStats]);
// Simulated terminal feed
useEffect(() => {
if (!user || user.username !== "drjones") return;
const lines = [
() => `[TOR] Circuit ping ${Math.floor(Math.random() * 80 + 20)}ms — relay healthy`,
() => `[NGINX] 127.0.0.1:8080 → next:3000 — ${Math.floor(Math.random() * 8 + 1)} req/s`,
() => `[SHIELD] Null-routing clearnet probe from ${Math.floor(Math.random() * 255)}.${Math.floor(Math.random() * 255)}.x.x`,
() => `[BTC] Block height ${895000 + Math.floor(Math.random() * 500)} confirmed`,
() => `[LEDGER] Balances consistent — ${Object.keys(handles).length} handles tracked`,
];
const t = setInterval(() => log(lines[Math.floor(Math.random() * lines.length)]!()), 4000);
return () => clearInterval(t);
}, [user, handles, log]);
// ── Actions ──────────────────────────────────────────────────────────────────
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
setLoginError("");
const res = await signIn("drjones", loginPass);
if (!res.ok) setLoginError(res.error ?? "Access Denied");
};
const adminCredit = async () => {
const amt = parseFloat(creditAmt);
if (!creditHandle.trim() || isNaN(amt) || amt <= 0) {
setCreditMsg({ text: "Handle and positive amount required", ok: false });
return;
}
setCreditMsg(null);
try {
const r = await fetch("/api/admin/credit", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ handle: creditHandle.trim(), amount: Math.floor(amt) }),
});
const d = await r.json() as { ok: boolean; newBalance?: number; error?: string };
if (d.ok) {
setCreditMsg({ text: `${Math.floor(amt)} VOID → @${creditHandle.trim()} (new balance: ${d.newBalance})`, ok: true });
log(`[ADMIN] Manual credit: +${Math.floor(amt)} VOID → @${creditHandle.trim()}`);
void fetchLedger();
} else {
setCreditMsg({ text: d.error ?? "Failed", ok: false });
}
} catch { setCreditMsg({ text: "Network error", ok: false }); }
};
const doDraw = () => {
if (raffleEntries.length === 0) { setDrawResult("No entries yet."); return; }
const pool: string[] = [];
for (const e of raffleEntries) {
for (let i = 0; i < e.tickets; i++) pool.push(e.handle);
}
const winner = pool[Math.floor(Math.random() * pool.length)]!;
setDrawResult(`🎉 WINNER: @${winner} (${raffleEntries.find((e) => e.handle === winner)?.tickets ?? 1} tickets)`);
log(`[RAFFLE] Manual draw executed — winner: @${winner}`);
};
const handleBroadcast = async () => {
if (!broadcast.trim()) return;
try {
await fetch("/api/messages", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ from: "System Admin", text: `[BROADCAST] ${broadcast}`, channel: "global" }),
});
log(`[COMMS] Global broadcast sent: "${broadcast}"`);
setBroadcast("");
void fetchMessages();
} catch { log("[COMMS] Broadcast failed!"); }
};
const deleteMessage = async (id: string) => {
await fetch(`/api/messages?id=${id}`, { method: "DELETE" });
log(`[COMMS] Deleted message ${id}`);
void fetchMessages();
};
const nukeMessages = async () => {
if (!confirm("Nuke all comms?")) return;
await fetch(`/api/messages?all=true`, { method: "DELETE" });
log("[COMMS] All messages purged.");
void fetchMessages();
};
// ── Auth gate ────────────────────────────────────────────────────────────────
if (!hydrated) return (
<div className="flex min-h-screen items-center justify-center bg-[#050000] text-[#00ff66] font-mono text-sm">
Initializing
</div>
);
if (!user || user.username !== "drjones") return (
<div className="flex min-h-screen flex-col items-center justify-center bg-[#050000] p-6 font-mono text-[#00ff66]">
<div className="w-full max-w-sm rounded-xl border border-[#00ff66]/25 bg-black/80 p-8 shadow-[0_0_40px_rgba(0,255,102,0.12)]">
<div className="mb-6 text-center">
<div className="text-[10px] uppercase tracking-[0.4em] text-[#00ff66]/40 mb-2">CYBERLUX NODE</div>
<h1 className="text-2xl font-black uppercase tracking-widest text-red-500">RESTRICTED</h1>
<p className="mt-2 text-[10px] text-zinc-600">Operator access only</p>
</div>
<form onSubmit={handleLogin} className="flex flex-col gap-4">
<input
type="password"
value={loginPass}
onChange={(e) => setLoginPass(e.target.value)}
className="w-full rounded border border-[#00ff66]/25 bg-black px-4 py-3 text-[#00ff66] text-sm placeholder:text-zinc-700 focus:border-[#00ff66]/60 focus:outline-none"
placeholder="passphrase"
autoFocus
/>
{loginError && <div className="text-xs text-red-500">{loginError}</div>}
<button type="submit" className="w-full rounded bg-[#00ff66]/15 py-3 text-xs font-bold uppercase tracking-widest text-[#00ff66] hover:bg-[#00ff66]/25 transition-colors">
AUTHORIZE
</button>
</form>
<Link href="/" className="mt-6 block text-center text-[10px] uppercase text-zinc-700 hover:text-zinc-500 transition-colors">
Return to hub
</Link>
</div>
</div>
);
// ── Computed stats ───────────────────────────────────────────────────────────
const allHandles = Object.entries(handles);
const totalVoidCirculating = allHandles.reduce((s, [, r]) => s + r.voidCredits, 0);
const totalInvoices = Object.keys(invoiceIndex).length;
const totalToolUnlocks = allHandles.reduce((s, [, r]) => s + r.unlockedTools.length, 0);
const TABS: { id: Tab; label: string; icon: string }[] = [
{ id: "overview", label: "Overview", icon: "◈" },
{ id: "credits", label: "Credit Ops", icon: "◆" },
{ id: "raffle", label: "Raffle", icon: "🎟" },
{ id: "comms", label: "Comms", icon: "📡" },
{ id: "logs", label: "System Log", icon: "▸" },
];
// ── Render ────────────────────────────────────────────────────────────────────
return (
<div className="min-h-screen bg-[#030008] font-mono text-[#00ff66]">
{/* Header */}
<header className="border-b border-[#00ff66]/20 bg-black/60 backdrop-blur px-6 py-3 flex items-center justify-between gap-4 sticky top-0 z-40">
<div className="flex items-center gap-3">
<span className="inline-block h-2 w-2 rounded-full bg-[#00ff66] shadow-[0_0_8px_#00ff66] animate-pulse" />
<span className="text-sm font-black uppercase tracking-widest text-white">CYBERLUX COMMAND</span>
<span className="hidden sm:inline text-[10px] text-zinc-600 uppercase tracking-widest">// OPERATOR CONSOLE</span>
</div>
<div className="flex items-center gap-4 text-[10px] uppercase text-zinc-500">
<span>@drjones</span>
<span className="text-[#00ff66]">GOD MODE</span>
{btcUsd && <span className="text-yellow-500">BTC ${btcUsd.toLocaleString()}</span>}
<Link href="/" className="rounded border border-zinc-800 px-2 py-1 hover:border-zinc-600 transition-colors"> Hub</Link>
</div>
</header>
{/* Tab bar */}
<nav className="border-b border-[#00ff66]/10 bg-black/40 px-6 flex gap-1">
{TABS.map((t) => (
<button
key={t.id}
onClick={() => setTab(t.id)}
className={`px-4 py-3 text-xs font-bold uppercase tracking-wider transition-colors border-b-2 ${
tab === t.id
? "border-[#00ff66] text-[#00ff66]"
: "border-transparent text-zinc-600 hover:text-zinc-400"
}`}
>
{t.icon} {t.label}
</button>
))}
</nav>
{/* Body */}
<main className="p-6 max-w-7xl mx-auto">
{/* ── OVERVIEW ─────────────────────────────────────────────────────── */}
{tab === "overview" && (
<div className="space-y-6">
{/* Stats */}
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-4">
<StatCard label="VOID Circulating" value={totalVoidCirculating} sub="across all handles" />
<StatCard label="Handles" value={allHandles.length} sub="registered" />
<StatCard label="Invoices" value={totalInvoices} sub="total created" />
<StatCard label="Tool Unlocks" value={totalToolUnlocks} sub="all time" />
<StatCard label="BTC / USD" value={btcUsd ? `$${btcUsd.toLocaleString()}` : "…"} sub="live oracle" accent="text-yellow-400" />
</div>
{/* Raffle snapshot */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<StatCard label="Raffle Tickets" value={raffleStats?.totalTickets ?? 0} sub="this round" accent="text-purple-400" />
<StatCard label="Participants" value={raffleStats?.participants ?? 0} sub="this round" accent="text-purple-400" />
<div className="rounded-lg border border-purple-500/20 bg-black/50 p-4">
<div className="text-[9px] uppercase tracking-widest text-zinc-500 mb-1">Next Draw</div>
<div className="text-2xl font-black font-mono tabular-nums text-purple-400">
{raffleStats ? fmt(raffleStats.drawAt - Date.now()) : "…"}
</div>
</div>
</div>
{/* Top handles by VOID */}
<div className="rounded-xl border border-[#00ff66]/20 bg-black/50 p-5">
<h2 className="text-xs font-bold uppercase tracking-widest mb-4 text-[#00ff66]">Top VOID Holders</h2>
{allHandles.length === 0 ? (
<div className="text-xs text-zinc-600">No handles registered yet.</div>
) : (
<div className="space-y-2">
{[...allHandles].sort((a, b) => b[1].voidCredits - a[1].voidCredits).slice(0, 10).map(([handle, rec]) => (
<div key={handle} className="flex items-center gap-3">
<span className="text-xs text-zinc-400 w-32 truncate">@{handle}</span>
<div className="flex-1 h-2 rounded-full bg-zinc-900">
<div
className="h-2 rounded-full bg-[#00ff66]/60"
style={{ width: `${Math.min(100, (rec.voidCredits / Math.max(1, totalVoidCirculating)) * 100)}%` }}
/>
</div>
<span className="text-xs font-bold text-[#00ff66] w-16 text-right tabular-nums">{rec.voidCredits} V</span>
<span className="text-[10px] text-zinc-600 w-20 text-right">{rec.unlockedTools.length} tools</span>
</div>
))}
</div>
)}
</div>
{/* Quick ops */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
{[
{ label: "VOID Tools", href: "/tools", desc: "Tool marketplace", icon: "🔓" },
{ label: "DarkHost", href: "/hosting", desc: "Hosting panel", icon: "🖥️" },
{ label: "Add Funds", href: "/account/add-funds", desc: "BTC deposit", icon: "₿" },
].map((l) => (
<Link key={l.href} href={l.href} className="flex items-center gap-3 rounded-lg border border-zinc-800 bg-black/30 p-4 hover:border-[#00ff66]/40 transition-colors">
<span className="text-xl">{l.icon}</span>
<div>
<div className="text-xs font-bold text-white">{l.label}</div>
<div className="text-[10px] text-zinc-600">{l.desc}</div>
</div>
</Link>
))}
</div>
</div>
)}
{/* ── CREDIT OPS ───────────────────────────────────────────────────── */}
{tab === "credits" && (
<div className="space-y-6">
{/* Manual credit */}
<div className="rounded-xl border border-[#00ff66]/25 bg-black/50 p-6">
<h2 className="text-xs font-bold uppercase tracking-widest mb-4 text-[#00ff66]">Manual VOID Credit</h2>
<div className="flex flex-wrap gap-3 items-end">
<div className="flex flex-col gap-1">
<label className="text-[10px] uppercase text-zinc-500">Handle</label>
<input
value={creditHandle}
onChange={(e) => setCreditHandle(e.target.value)}
placeholder="username"
className="rounded border border-zinc-800 bg-black px-3 py-2 text-sm text-[#00ff66] placeholder:text-zinc-700 focus:border-[#00ff66]/50 focus:outline-none w-44"
/>
</div>
<div className="flex flex-col gap-1">
<label className="text-[10px] uppercase text-zinc-500">VOID Amount</label>
<input
value={creditAmt}
onChange={(e) => setCreditAmt(e.target.value)}
placeholder="50"
type="number"
min="1"
className="rounded border border-zinc-800 bg-black px-3 py-2 text-sm text-[#00ff66] placeholder:text-zinc-700 focus:border-[#00ff66]/50 focus:outline-none w-32"
/>
</div>
<button
onClick={adminCredit}
className="rounded bg-[#00ff66]/15 border border-[#00ff66]/30 px-5 py-2 text-xs font-bold uppercase text-[#00ff66] hover:bg-[#00ff66]/25 transition-colors"
>
CREDIT
</button>
</div>
{creditMsg && (
<div className={`mt-3 text-xs ${creditMsg.ok ? "text-[#00ff66]" : "text-red-400"}`}>
{creditMsg.text}
</div>
)}
</div>
{/* Full ledger table */}
<div className="rounded-xl border border-[#00ff66]/20 bg-black/50 p-5">
<h2 className="text-xs font-bold uppercase tracking-widest mb-4 text-[#00ff66]">
Full VOID Ledger {allHandles.length} handles
</h2>
<div className="overflow-x-auto">
<table className="w-full text-xs">
<thead>
<tr className="text-[10px] uppercase text-zinc-600 border-b border-zinc-800">
<th className="text-left py-2 pr-4">Handle</th>
<th className="text-right py-2 pr-4">VOID</th>
<th className="text-right py-2 pr-4">Invoices</th>
<th className="text-right py-2 pr-4">Tools</th>
<th className="text-right py-2">Spent (VOID)</th>
</tr>
</thead>
<tbody>
{allHandles.length === 0 ? (
<tr><td colSpan={5} className="py-6 text-center text-zinc-600">No handles yet.</td></tr>
) : (
[...allHandles]
.sort((a, b) => b[1].voidCredits - a[1].voidCredits)
.map(([handle, rec]) => {
const spent = rec.unlockedTools.reduce((s, t) => s + t.voidSpent, 0);
return (
<tr key={handle} className="border-b border-zinc-900 hover:bg-white/2 transition-colors">
<td className="py-2 pr-4 text-white font-bold">@{handle}</td>
<td className="py-2 pr-4 text-right tabular-nums text-[#00ff66] font-bold">{rec.voidCredits}</td>
<td className="py-2 pr-4 text-right tabular-nums text-zinc-400">{Object.keys(rec.claimedInvoices).length}</td>
<td className="py-2 pr-4 text-right tabular-nums text-purple-400">{rec.unlockedTools.length}</td>
<td className="py-2 text-right tabular-nums text-zinc-500">{spent}</td>
</tr>
);
})
)}
</tbody>
</table>
</div>
</div>
{/* Invoice index */}
<div className="rounded-xl border border-zinc-800 bg-black/40 p-5">
<h2 className="text-xs font-bold uppercase tracking-widest mb-4 text-zinc-500">
Invoice Index {totalInvoices} total
</h2>
<div className="overflow-x-auto max-h-48 overflow-y-auto">
<table className="w-full text-[10px]">
<thead>
<tr className="text-zinc-700 border-b border-zinc-900 uppercase">
<th className="text-left py-1 pr-4">Invoice ID</th>
<th className="text-left py-1">Handle</th>
</tr>
</thead>
<tbody>
{Object.entries(invoiceIndex).slice(-30).reverse().map(([inv, handle]) => (
<tr key={inv} className="border-b border-zinc-900/60">
<td className="py-1 pr-4 font-mono text-zinc-500">{inv.slice(0, 20)}</td>
<td className="py-1 text-zinc-400">@{handle}</td>
</tr>
))}
{totalInvoices === 0 && (
<tr><td colSpan={2} className="py-4 text-center text-zinc-700">No invoices yet.</td></tr>
)}
</tbody>
</table>
</div>
</div>
</div>
)}
{/* ── RAFFLE ───────────────────────────────────────────────────────── */}
{tab === "raffle" && (
<div className="space-y-6">
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
<StatCard label="Tickets Sold" value={raffleStats?.totalTickets ?? 0} accent="text-purple-400" />
<StatCard label="Participants" value={raffleStats?.participants ?? 0} accent="text-purple-400" />
<div className="rounded-lg border border-purple-500/20 bg-black/50 p-4 col-span-2">
<div className="text-[9px] uppercase tracking-widest text-zinc-500 mb-1">Time to Draw</div>
<div className="text-3xl font-black font-mono tabular-nums text-purple-400">{fmt(raffleRemaining)}</div>
</div>
</div>
{/* Draw control */}
<div className="rounded-xl border border-purple-500/30 bg-[#0d0010]/60 p-6">
<h2 className="text-xs font-bold uppercase tracking-widest mb-4 text-purple-400">Manual Draw</h2>
<p className="text-xs text-zinc-500 mb-4">
Executes a weighted random draw client-side for preview purposes. Winner is selected proportional to ticket count.
</p>
<button
onClick={doDraw}
className="rounded bg-purple-900/40 border border-purple-500/40 px-6 py-2 text-sm font-bold text-purple-300 hover:bg-purple-900/60 transition-colors"
>
🎲 EXECUTE DRAW
</button>
{drawResult && (
<div className="mt-4 rounded-lg border border-purple-500/30 bg-purple-900/20 p-4 text-sm font-bold text-purple-300">
{drawResult}
</div>
)}
</div>
{/* Entries */}
<div className="rounded-xl border border-[#00ff66]/20 bg-black/50 p-5">
<h2 className="text-xs font-bold uppercase tracking-widest mb-4 text-[#00ff66]">Current Entries</h2>
{raffleEntries.length === 0 ? (
<div className="text-xs text-zinc-600 py-4">No entries yet.</div>
) : (
<div className="space-y-2">
{[...raffleEntries].sort((a, b) => b.tickets - a.tickets).map((e) => {
const pct = raffleStats ? Math.round((e.tickets / raffleStats.totalTickets) * 100) : 0;
return (
<div key={e.handle} className="flex items-center gap-3">
<span className="text-xs text-zinc-400 w-32 truncate">@{e.handle}</span>
<div className="flex-1 h-2 rounded-full bg-zinc-900">
<div className="h-2 rounded-full bg-purple-500/60" style={{ width: `${pct}%` }} />
</div>
<span className="text-xs font-bold text-purple-300 w-16 text-right tabular-nums">{e.tickets} tickets</span>
<span className="text-[10px] text-zinc-600 w-10 text-right">{pct}%</span>
</div>
);
})}
</div>
)}
</div>
</div>
)}
{/* ── COMMS ────────────────────────────────────────────────────────── */}
{tab === "comms" && (
<div className="space-y-6">
{/* Broadcast */}
<div className="rounded-xl border border-red-600/30 bg-[#1a0000]/50 p-6">
<h2 className="text-xs font-bold uppercase tracking-widest mb-3 text-red-500">Global Network Broadcast</h2>
<textarea
value={broadcast}
onChange={(e) => setBroadcast(e.target.value)}
className="mb-3 h-20 w-full resize-none rounded border border-red-900/40 bg-black/60 p-3 text-xs text-red-300 placeholder:text-red-900 focus:border-red-600/50 focus:outline-none"
placeholder="Message to all connected nodes…"
/>
<button
onClick={handleBroadcast}
className="rounded bg-red-900/50 border border-red-700/50 px-6 py-2 text-xs font-bold uppercase text-white hover:bg-red-800/60 transition-colors shadow-[0_0_10px_rgba(255,0,0,0.2)]"
>
EXECUTE BROADCAST
</button>
</div>
{/* Message feed */}
<div className="rounded-xl border border-[#00ff66]/20 bg-black/50 p-5">
<div className="flex items-center justify-between mb-4">
<h2 className="text-xs font-bold uppercase tracking-widest text-[#00ff66]">
Comms Intercept {messages.length} messages
</h2>
<button
onClick={nukeMessages}
className="rounded border border-red-900/40 bg-red-900/20 px-3 py-1 text-[10px] font-bold text-red-500 hover:bg-red-900/40 transition-colors"
>
PURGE ALL
</button>
</div>
<div className="space-y-2 max-h-96 overflow-y-auto pr-1">
{messages.length === 0 ? (
<div className="py-8 text-center text-xs text-zinc-600">No traffic detected.</div>
) : (
[...messages].reverse().map((msg) => (
<div key={msg.id} className="rounded bg-[#0a110d] border border-[#00ff66]/10 p-3">
<div className="flex items-center justify-between mb-1 flex-wrap gap-1">
<div className="flex items-center gap-2">
<span className="text-xs font-bold text-[#00ff66]">@{msg.from}</span>
<span className="text-[9px] text-zinc-600 bg-black px-1.5 rounded">#{msg.channel ?? "global"}</span>
<span className="text-[9px] text-zinc-700">{new Date(msg.ts).toLocaleString()}</span>
</div>
<button onClick={() => deleteMessage(msg.id)} className="text-[10px] text-red-500 hover:text-red-400 font-bold">
[DEL]
</button>
</div>
<div className="text-sm text-white/85 font-sans break-words">{msg.text}</div>
</div>
))
)}
</div>
</div>
</div>
)}
{/* ── LOGS ─────────────────────────────────────────────────────────── */}
{tab === "logs" && (
<div className="rounded-xl border border-[#00ff66]/20 bg-black/80 p-5">
<div className="flex items-center justify-between mb-4">
<h2 className="text-xs font-bold uppercase tracking-widest text-[#00ff66]">System Log</h2>
<button
onClick={() => setTerminalLines([])}
className="text-[10px] text-zinc-600 hover:text-zinc-400 transition-colors"
>
CLEAR
</button>
</div>
<div
ref={logRef}
className="h-[60vh] overflow-y-auto rounded bg-[#020005] border border-white/5 p-4 text-[11px] leading-relaxed space-y-0.5"
>
{terminalLines.map((line, i) => (
<div
key={i}
className={
line.includes("[ADMIN]") ? "text-red-400" :
line.includes("[RAFFLE]") ? "text-purple-400" :
line.includes("[COMMS]") ? "text-yellow-400" :
line.includes("[SHIELD]") || line.includes("[AUTH]") ? "text-orange-400" :
line.includes("[BTC]") ? "text-yellow-300" :
"text-[#00ff66]/70"
}
>
<span className="text-zinc-700 select-none">{String(i + 1).padStart(4, " ")} </span>
{line}
</div>
))}
<div className="text-[#00ff66] animate-pulse">_</div>
</div>
</div>
)}
</main>
</div>
);
}