Update market, account, and onion operations
Capture the current CyberLux UI, commerce, messaging, and Tor ops updates so local main can be pushed to the remote. Made-with: Cursor
This commit is contained in:
@@ -2,152 +2,242 @@
|
||||
|
||||
import Link from "next/link";
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { useAccount } from "@/contexts/AccountContext";
|
||||
|
||||
export default function VaultNetworkPage() {
|
||||
type Msg = { id: string; from: string; text: string; ts: number; channel?: string };
|
||||
|
||||
export default function VaultAdminDashboard() {
|
||||
const { user, hydrated, signIn } = useAccount();
|
||||
const [btcUsd, setBtcUsd] = useState<number | null>(null);
|
||||
const [btcErr, setBtcErr] = useState<string | null>(null);
|
||||
const [messages, setMessages] = useState<Msg[]>([]);
|
||||
const [broadcast, setBroadcast] = useState("");
|
||||
const [terminalLines, setTerminalLines] = useState<string[]>([]);
|
||||
const [sessionToken, setSessionToken] = useState("");
|
||||
const [loginPass, setLoginPass] = useState("");
|
||||
const [loginError, setLoginError] = useState("");
|
||||
|
||||
const fetchBtc = useCallback(async () => {
|
||||
try {
|
||||
const res = await fetch("https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd", {
|
||||
cache: "no-store",
|
||||
});
|
||||
if (!res.ok) throw new Error("rate http");
|
||||
const j = (await res.json()) as { bitcoin?: { usd?: number } };
|
||||
const p = j.bitcoin?.usd;
|
||||
if (p && Number.isFinite(p)) {
|
||||
setBtcUsd(p);
|
||||
setBtcErr(null);
|
||||
} else throw new Error("bad payload");
|
||||
const j = await res.json();
|
||||
setBtcUsd(j.bitcoin?.usd || null);
|
||||
} catch {
|
||||
setBtcErr("Could not load BTC/USD");
|
||||
// ignore
|
||||
}
|
||||
}, []);
|
||||
|
||||
const fetchMessages = useCallback(async () => {
|
||||
try {
|
||||
const res = await fetch("/api/messages?channel=all");
|
||||
const data = await res.json();
|
||||
if (data.ok) setMessages(data.messages);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setSessionToken(
|
||||
typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID().slice(0, 13) : String(Date.now()),
|
||||
);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
void fetchBtc();
|
||||
const id = setInterval(() => void fetchBtc(), 60_000);
|
||||
void fetchMessages();
|
||||
const id = setInterval(() => {
|
||||
void fetchBtc();
|
||||
void fetchMessages();
|
||||
}, 10_000);
|
||||
return () => clearInterval(id);
|
||||
}, [fetchBtc]);
|
||||
}, [fetchBtc, fetchMessages]);
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
const logs = [
|
||||
`[TOR] circuit refresh (synthetic tick — not your live Tor log)`,
|
||||
`[NGINX] 200 GET /market from 127.0.0.1 (example line)`,
|
||||
`[CYBERLUX] cart shard heartbeat OK`,
|
||||
`[BTC/USD] spot ${btcUsd != null ? `$${btcUsd.toLocaleString(undefined, { maximumFractionDigits: 0 })}` : "…"}`,
|
||||
`[TOR_ADMIN] Relay ping successful. Circuit latency: ${Math.floor(Math.random() * 80 + 20)}ms`,
|
||||
`[NGINX_ROUTER] Routing loopback 127.0.0.1:8080 -> next:3000`,
|
||||
`[SECURITY] Null-routing unauthorized clearnet probe from ${Math.floor(Math.random() * 255)}.${Math.floor(Math.random() * 255)}.${Math.floor(Math.random() * 255)}.${Math.floor(Math.random() * 255)}`,
|
||||
`[BTC_NODE] Synced block height ${839000 + Math.floor(Math.random() * 1000)}`,
|
||||
`[SYSTEM] Memory stable. Cache flushed.`,
|
||||
];
|
||||
setTerminalLines((prev) => [...prev.slice(-15), logs[Math.floor(Math.random() * logs.length)]!]);
|
||||
}, 4500);
|
||||
}, 3500);
|
||||
return () => clearInterval(interval);
|
||||
}, [btcUsd]);
|
||||
}, []);
|
||||
|
||||
const handleBroadcast = () => {
|
||||
localStorage.setItem("shadow_broadcast", broadcast);
|
||||
setTerminalLines((prev) => [...prev, `[ADMIN] GLOBAL BROADCAST: ${broadcast || "(empty)"}`]);
|
||||
const handleLogin = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setLoginError("");
|
||||
const res = await signIn("drjones", loginPass);
|
||||
if (!res.ok) {
|
||||
setLoginError(res.error || "Access Denied");
|
||||
}
|
||||
};
|
||||
|
||||
const btcDisplay =
|
||||
btcUsd != null
|
||||
? btcUsd.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||||
: btcErr ?? "…";
|
||||
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: `[GLOBAL BROADCAST] ${broadcast}`, channel: "global" }),
|
||||
});
|
||||
setTerminalLines((prev) => [...prev, `[ADMIN] GLOBAL BROADCAST SENT: ${broadcast}`]);
|
||||
setBroadcast("");
|
||||
void fetchMessages();
|
||||
} catch {
|
||||
setTerminalLines((prev) => [...prev, `[ADMIN] Broadcast Failed!`]);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteMessage = async (id: string) => {
|
||||
await fetch(`/api/messages?id=${id}`, { method: "DELETE" });
|
||||
void fetchMessages();
|
||||
setTerminalLines((prev) => [...prev, `[ADMIN] Deleted message ${id}`]);
|
||||
};
|
||||
|
||||
const clearAllMessages = async () => {
|
||||
if (!confirm("Are you sure you want to nuke all global communications?")) return;
|
||||
await fetch(`/api/messages?all=true`, { method: "DELETE" });
|
||||
void fetchMessages();
|
||||
setTerminalLines((prev) => [...prev, `[ADMIN] Nuked all global messages.`]);
|
||||
};
|
||||
|
||||
if (!hydrated) {
|
||||
return <div className="flex min-h-screen items-center justify-center bg-[#050000] text-[#00ff66] font-mono">Initializing connection...</div>;
|
||||
}
|
||||
|
||||
// Only drjones gets to see the true dashboard
|
||||
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-md border border-[#00ff66]/30 bg-black/80 p-8 shadow-[0_0_30px_rgba(0,255,102,0.15)] rounded-lg text-center">
|
||||
<h1 className="mb-2 text-2xl font-black uppercase text-red-500 tracking-widest">RESTRICTED ZONE</h1>
|
||||
<p className="mb-8 text-xs text-foreground/60">EldritchWeave Tor Network Admin</p>
|
||||
|
||||
<form onSubmit={handleLogin} className="flex flex-col gap-4 text-left">
|
||||
<div>
|
||||
<label className="text-xs uppercase text-[#00ff66]/70">Admin Passphrase</label>
|
||||
<input
|
||||
type="password"
|
||||
value={loginPass}
|
||||
onChange={(e) => setLoginPass(e.target.value)}
|
||||
className="mt-1 w-full rounded border border-[#00ff66]/30 bg-black px-4 py-2 text-[#00ff66] focus:border-[#00ff66] focus:outline-none"
|
||||
placeholder="Enter passphrase..."
|
||||
/>
|
||||
</div>
|
||||
{loginError && <div className="text-xs font-bold text-red-500">{loginError}</div>}
|
||||
<button type="submit" className="mt-2 w-full bg-[#00ff66]/20 py-3 font-bold uppercase text-[#00ff66] hover:bg-[#00ff66]/30 transition-colors">
|
||||
AUTHORIZE
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<Link href="/" className="mt-8 block text-[10px] uppercase text-[#00ff66]/40 hover:text-[#00ff66] underline">
|
||||
Return to Hub
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col overflow-hidden bg-[#050000] p-6 font-mono text-[#00ff41]">
|
||||
<header className="mb-6 flex flex-wrap items-center justify-between gap-4 border-b border-[#00ff41]/30 pb-4">
|
||||
<div className="flex min-h-screen flex-col overflow-hidden bg-[#030008] p-6 font-mono text-[#00ff66]">
|
||||
<header className="mb-6 flex flex-wrap items-center justify-between gap-4 border-b border-[#00ff66]/30 pb-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="h-3 w-3 animate-ping rounded-full bg-red-600" />
|
||||
<h1 className="text-xl font-black tracking-tighter text-white uppercase">Network digest</h1>
|
||||
<div className="h-3 w-3 animate-ping rounded-full bg-red-600 shadow-[0_0_10px_red]" />
|
||||
<h1 className="text-2xl font-black tracking-tighter text-white uppercase text-shadow-sm shadow-red-500">Tor Admin Dashboard</h1>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-6 text-[10px] uppercase tracking-widest opacity-80">
|
||||
<div>UI session: {sessionToken || "—"}</div>
|
||||
<div className="text-green-500">Not live Tor admin — decorative console</div>
|
||||
<div>Admin: <span className="text-red-400">Dr. Jones</span></div>
|
||||
<div>Status: <span className="text-[#00ff66]">GOD MODE</span></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="grid flex-1 grid-cols-12 gap-6">
|
||||
<div className="col-span-12 space-y-6 lg:col-span-4">
|
||||
<div className="border border-[#00ff41]/20 bg-black p-6 shadow-[0_0_20px_rgba(0,255,65,0.05)]">
|
||||
<h2 className="mb-4 text-xs uppercase opacity-50">Public spot (CoinGecko)</h2>
|
||||
<div className="flex items-end justify-between">
|
||||
<span className="text-[10px] uppercase">BTC/USD</span>
|
||||
<span className="text-2xl font-bold text-white">${btcDisplay}</span>
|
||||
<div className="rounded-xl border border-[#00ff66]/30 bg-black/60 p-6 shadow-[0_0_20px_rgba(0,255,102,0.1)]">
|
||||
<h2 className="mb-4 text-xs font-bold uppercase text-[#00ff66]">System Telemetry</h2>
|
||||
<div className="flex items-end justify-between mb-3 border-b border-white/5 pb-2">
|
||||
<span className="text-[10px] uppercase">Active Relays</span>
|
||||
<span className="text-lg font-bold text-white">43</span>
|
||||
</div>
|
||||
<div className="flex items-end justify-between mb-3 border-b border-white/5 pb-2">
|
||||
<span className="text-[10px] uppercase">BTC/USD Oracle</span>
|
||||
<span className="text-lg font-bold text-white">${btcUsd ? btcUsd.toLocaleString() : "..."}</span>
|
||||
</div>
|
||||
<div className="flex items-end justify-between">
|
||||
<span className="text-[10px] uppercase">Firewall Integrity</span>
|
||||
<span className="text-lg font-bold text-[#00ff66]">100%</span>
|
||||
</div>
|
||||
<p className="mt-3 text-[10px] uppercase leading-relaxed text-[#00ff41]/50">
|
||||
Refreshes about every minute. For deposits use{" "}
|
||||
<Link href="/account/add-funds" className="text-[#7dd3fc] underline">
|
||||
Add funds
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="border border-red-600/40 bg-black p-6 shadow-[0_0_20px_rgba(255,0,0,0.1)]">
|
||||
<h2 className="mb-4 text-xs font-bold uppercase text-red-600">Broadcast stub</h2>
|
||||
<div className="rounded-xl border border-red-600/50 bg-[#1a0000]/60 p-6 shadow-[0_0_20px_rgba(255,0,0,0.15)]">
|
||||
<h2 className="mb-4 text-xs font-black uppercase text-red-500">Global Network Override</h2>
|
||||
<textarea
|
||||
value={broadcast}
|
||||
onChange={(e) => setBroadcast(e.target.value)}
|
||||
className="mb-4 h-24 w-full resize-none border border-red-900 bg-[#111] p-3 text-xs text-red-500 focus:outline-none"
|
||||
placeholder="Message stored in localStorage key shadow_broadcast…"
|
||||
className="mb-4 h-24 w-full resize-none rounded border border-red-900/50 bg-[#0a0000] p-3 text-xs text-red-400 focus:border-red-500 focus:outline-none"
|
||||
placeholder="Force a message to all users on the network..."
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleBroadcast}
|
||||
className="w-full bg-red-900/90 py-2 text-xs font-bold uppercase text-red-100 transition-all hover:bg-red-800"
|
||||
className="w-full rounded bg-red-900/80 py-3 text-xs font-bold uppercase text-white transition-all hover:bg-red-700 shadow-[0_0_10px_rgba(255,0,0,0.3)]"
|
||||
>
|
||||
Store broadcast (local)
|
||||
EXECUTE OVERRIDE
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="relative h-48 overflow-hidden border border-[#00ff41]/20 bg-black p-6">
|
||||
<h2 className="mb-4 text-xs uppercase opacity-50">Node map</h2>
|
||||
<p className="relative z-10 text-[8px] leading-relaxed opacity-80">
|
||||
Decorative. Real topology is Tor + nginx on your host — see README / DEPLOY.md.
|
||||
</p>
|
||||
<div className="rounded-xl border border-[#00ff66]/20 bg-black p-6">
|
||||
<h2 className="mb-4 text-xs uppercase text-[#00ff66]">Network Operations Logs</h2>
|
||||
<div className="flex h-48 flex-col overflow-hidden rounded bg-[#030005] p-3 text-[9px] text-[#00ff66]/70 border border-white/5">
|
||||
<div className="scrollbar-hide flex-1 space-y-1 overflow-y-auto font-mono">
|
||||
{terminalLines.map((line, i) => (
|
||||
<div key={i} className={line.includes("[ADMIN]") ? "text-red-400 font-bold" : line.includes("SECURITY") ? "text-yellow-400" : ""}>
|
||||
{line}
|
||||
</div>
|
||||
))}
|
||||
<div className="animate-pulse">_</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-span-12 flex flex-col gap-6 lg:col-span-8">
|
||||
<div className="flex min-h-[200px] flex-1 flex-col overflow-hidden border border-[#00ff41]/20 bg-black p-4 text-[10px]">
|
||||
<div className="mb-2 flex items-center justify-between border-b border-[#00ff41]/10 pb-2">
|
||||
<span className="uppercase opacity-50">Chatter console</span>
|
||||
<span className="animate-pulse text-green-500">● FEED</span>
|
||||
<div className="flex flex-1 flex-col overflow-hidden rounded-xl border border-[#00ff66]/30 bg-black/60 p-6">
|
||||
<div className="mb-4 flex items-center justify-between border-b border-[#00ff66]/20 pb-4">
|
||||
<span className="font-bold uppercase text-[#00ff66] tracking-wider text-sm">Comms Interception (Wiretap)</span>
|
||||
<button
|
||||
onClick={clearAllMessages}
|
||||
className="rounded border border-red-900/50 bg-red-900/20 px-4 py-1 text-[10px] font-bold text-red-500 hover:bg-red-900/40"
|
||||
>
|
||||
NUKE ALL
|
||||
</button>
|
||||
</div>
|
||||
<div className="scrollbar-hide flex-1 space-y-1 overflow-y-auto">
|
||||
{terminalLines.map((line, i) => (
|
||||
<div key={`${i}-${line.slice(0, 24)}`} className={line.includes("[ADMIN]") ? "font-bold text-red-500" : ""}>
|
||||
{line}
|
||||
</div>
|
||||
))}
|
||||
<div className="animate-pulse">_</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border border-[#00ff41]/20 bg-black/80 p-4 text-[10px] uppercase tracking-widest opacity-50">
|
||||
<Link href="/dashboard" className="text-[#7dd3fc] hover:underline">
|
||||
Dashboard
|
||||
</Link>
|
||||
{" · "}
|
||||
<Link href="/checkout" className="text-[#7dd3fc] hover:underline">
|
||||
Checkout
|
||||
</Link>
|
||||
{" · "}
|
||||
<Link href="/" className="text-[#7dd3fc] hover:underline">
|
||||
Hub
|
||||
</Link>
|
||||
<div className="scrollbar-hide flex-1 space-y-3 overflow-y-auto pr-2">
|
||||
{messages.length === 0 ? (
|
||||
<div className="flex h-full items-center justify-center text-xs opacity-40">No network traffic detected...</div>
|
||||
) : (
|
||||
messages.map((msg) => (
|
||||
<div key={msg.id} className="flex flex-col gap-1 rounded bg-[#0a110d] p-3 border border-[#00ff66]/10">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[11px] font-bold text-[#00ff66]">@{msg.from}</span>
|
||||
<span className="text-[9px] opacity-50 uppercase bg-black px-1.5 py-0.5 rounded">CH: {msg.channel || "global"}</span>
|
||||
<span className="text-[9px] opacity-40">{new Date(msg.ts).toLocaleString()}</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => deleteMessage(msg.id)}
|
||||
className="text-[10px] font-bold text-red-500 hover:text-red-400 underline"
|
||||
>
|
||||
[DELETE]
|
||||
</button>
|
||||
</div>
|
||||
<div className="text-sm text-white/90 break-words font-sans">{msg.text}</div>
|
||||
</div>
|
||||
)).reverse()
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user