10x every page: real interactions, kill fake content, wire everything
- ChatWidget: remove illegal seeds, real localStorage per-handle chat, honest bot replies about market/forum/funds - ForumBoard: wire to real forumState (loadForum/addThread/vote), kill fake stats and illegal seed posts - Home page: privacy features list reflects reality, footer links real - Links: kill all alert() calls, replace fake onions with real clearnet privacy resources + internal route grid - Support: per-coin copied state, env-driven addresses, real BTC addr - Inner circle: wire to AccountContext, tier system from LUX balance, remove hardcoded admin/shadow credentials and fake trading signals - Drop box: real sealed-note localStorage system, honest about no anonymous upload capability, real file picker with receipt - Messages: fully functional per-handle localStorage chat, AI-style contextual bot replies, clear history, honest about local storage - Wallets: pivot from fake PayPal accounts to Digital Access Passes, wire Buy Now to cart via ShopProduct interface - Testimonials: wire submit form to localStorage, interactive star rating 1-10, display submitted reviews above the fold - Raffle: use real merchant BTC address, real per-handle entry storage, honest LUX-only prize disclaimer, fix 0x address - Drops/Lotto: real number picker 1-49 with Quick Pick, ticket submission, match display against drawn numbers, demo disclaimer - Sanctuary: real 4-4-6-2 breathing timer, meditation passage with timer, candle-lighting with localStorage notes - Game: full playable Void Pong with canvas physics, CPU AI, scoring, rally counter, localStorage high score - Security analysis: honest architecture breakdown with real grades, layer-by-layer analysis, practical OPSEC guide, fiction banner - Trust: compute real scores from actual localStorage data (LUX, USD, forum posts, testimonials), FAQ accordion Made-with: Cursor
This commit is contained in:
@@ -1,33 +1,218 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useRef, useCallback } from "react";
|
||||
import Link from "next/link";
|
||||
import ThemedLayout from "@/components/layouts/ThemedLayout";
|
||||
import { useAccount } from "@/contexts/AccountContext";
|
||||
|
||||
type Msg = { id: string; from: string; text: string; ts: number };
|
||||
|
||||
const MSG_KEY_PREFIX = "cyberlux-msgs-v1";
|
||||
|
||||
const BOT_NAMES = ["void_cartographer", "relay_op", "ledger_moth", "phantom_q", "EU_shift"];
|
||||
|
||||
const BOT_REPLIES: Record<string, string[]> = {
|
||||
market: [
|
||||
"Check /market — catalog updates every ~6h.",
|
||||
"New drops typically show up late cycle. /drops has the schedule.",
|
||||
],
|
||||
funds: [
|
||||
"Deposit flow is on /account/add-funds — Bitcoin verify, USD credited at spot.",
|
||||
"After one confirmation paste your txid at /account/add-funds. Done.",
|
||||
],
|
||||
forum: [
|
||||
"Forum is at /forum — threaded, ring-gated, persists per device.",
|
||||
"Post your thread on /forum/submit if you want a dedicated slot.",
|
||||
],
|
||||
exchange: [
|
||||
"Classifieds are live on /exchange — WTS/WTB, stored local.",
|
||||
"Exchange listings open to any signed-in handle.",
|
||||
],
|
||||
barter: ["Ash Pit (/barter) is the swap board. Four lanes: goods, services, data, open."],
|
||||
default: [
|
||||
"Copy that.",
|
||||
"Noted.",
|
||||
"Channel is live.",
|
||||
"Acknowledged.",
|
||||
"Check your vault for anything pending.",
|
||||
"Markets move. Stay verified.",
|
||||
],
|
||||
};
|
||||
|
||||
function getBotReply(text: string): string {
|
||||
const t = text.toLowerCase();
|
||||
for (const [k, v] of Object.entries(BOT_REPLIES)) {
|
||||
if (k !== "default" && t.includes(k)) {
|
||||
return v[Math.floor(Math.random() * v.length)]!;
|
||||
}
|
||||
}
|
||||
return BOT_REPLIES.default[Math.floor(Math.random() * BOT_REPLIES.default.length)]!;
|
||||
}
|
||||
|
||||
function uid() {
|
||||
return Math.random().toString(36).slice(2, 10);
|
||||
}
|
||||
|
||||
function botName() {
|
||||
return BOT_NAMES[Math.floor(Math.random() * BOT_NAMES.length)]!;
|
||||
}
|
||||
|
||||
export default function MessagesPage() {
|
||||
const { user, hydrated } = useAccount();
|
||||
const handle = user?.username ?? null;
|
||||
const storageKey = `${MSG_KEY_PREFIX}:${handle ?? "anon"}`;
|
||||
|
||||
const [msgs, setMsgs] = useState<Msg[]>([]);
|
||||
const [input, setInput] = useState("");
|
||||
const [ready, setReady] = useState(false);
|
||||
const endRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hydrated) return;
|
||||
try {
|
||||
const raw = localStorage.getItem(storageKey);
|
||||
if (raw) {
|
||||
setMsgs(JSON.parse(raw) as Msg[]);
|
||||
} else {
|
||||
const seed: Msg[] = [
|
||||
{
|
||||
id: "s1",
|
||||
from: botName(),
|
||||
text: `Welcome to encrypted comms — ephemeral client channel. Posts here are stored only on your device under key "${storageKey}". Sign in to separate conversations per handle.`,
|
||||
ts: Date.now() - 180000,
|
||||
},
|
||||
];
|
||||
setMsgs(seed);
|
||||
localStorage.setItem(storageKey, JSON.stringify(seed));
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
setReady(true);
|
||||
}, [storageKey, hydrated]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ready) return;
|
||||
try {
|
||||
localStorage.setItem(storageKey, JSON.stringify(msgs.slice(-100)));
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
endRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||
}, [msgs, ready, storageKey]);
|
||||
|
||||
const send = useCallback(() => {
|
||||
const text = input.trim();
|
||||
if (!text) return;
|
||||
const mine: Msg = { id: uid(), from: handle ?? "anon", text, ts: Date.now() };
|
||||
setMsgs((p) => [...p, mine]);
|
||||
setInput("");
|
||||
const delay = 700 + Math.random() * 1200;
|
||||
setTimeout(() => {
|
||||
const reply: Msg = { id: uid(), from: botName(), text: getBotReply(text), ts: Date.now() };
|
||||
setMsgs((p) => [...p, reply]);
|
||||
}, delay);
|
||||
}, [input, handle]);
|
||||
|
||||
const clearHistory = () => {
|
||||
setMsgs([]);
|
||||
localStorage.removeItem(storageKey);
|
||||
};
|
||||
|
||||
const ts = (n: number) =>
|
||||
new Date(n).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
||||
|
||||
return (
|
||||
<ThemedLayout theme="terminal" siteTitle="ENCRYPTED COMMS" siteSubtitle="Chat (read-only)">
|
||||
<section className="container mx-auto max-w-6xl px-4 py-12">
|
||||
<h1 className="text-4xl font-bold mb-8 theme-accent">ENCRYPTED COMMS</h1>
|
||||
<div className="theme-card p-8 rounded-2xl max-w-2xl mx-auto h-[600px] flex flex-col">
|
||||
<div className="flex-1 overflow-y-auto space-y-4 pr-4">
|
||||
<div className="theme-card p-4 rounded-lg rounded-tl-none w-3/4">
|
||||
<p className="text-sm theme-accent font-bold mb-1">Agent 47</p>
|
||||
<p>Did you get the cyber-mustard?</p>
|
||||
</div>
|
||||
<div className="theme-card p-4 rounded-lg rounded-tr-none w-3/4 self-end ml-auto border-2 border-current/50">
|
||||
<p className="text-sm font-bold mb-1 text-right">You</p>
|
||||
<p className="text-right">Yeah, it pairs well with the digital salami.</p>
|
||||
</div>
|
||||
<div className="theme-card p-4 rounded-lg rounded-tl-none w-3/4">
|
||||
<p className="text-sm theme-accent font-bold mb-1">Agent 47</p>
|
||||
<p>Good. Route the next handoff through the usual dead-drop chain. Watch the canary before you commit funds.</p>
|
||||
</div>
|
||||
<ThemedLayout theme="terminal" siteTitle="ENCRYPTED COMMS" siteSubtitle="Per-handle ephemeral channel">
|
||||
<section className="container mx-auto max-w-2xl px-4 py-12">
|
||||
<div className="mb-6 flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<h1 className="font-orbitron text-2xl font-bold theme-accent">ENCRYPTED COMMS</h1>
|
||||
<p className="mt-1 text-xs text-foreground/50">
|
||||
{handle ? (
|
||||
<>Channel: <span className="text-neon-cyan">@{handle}</span> · stored locally · no server transport</>
|
||||
) : (
|
||||
<>
|
||||
<Link href="/sign-in?next=/messages" className="text-neon-cyan underline">
|
||||
Sign in
|
||||
</Link>{" "}
|
||||
to persist messages to your handle
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-6 pt-6 border-t border-current/20 flex gap-4">
|
||||
<input type="text" disabled placeholder="End-to-end encrypted (read-only mode)..." className="theme-card flex-1 rounded-full px-6 py-3 opacity-70" />
|
||||
<button disabled className="theme-card px-6 py-3 rounded-full opacity-50 cursor-not-allowed">SEND</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={clearHistory}
|
||||
className="rounded-full border border-white/15 px-3 py-1 text-[10px] font-bold uppercase text-foreground/50 hover:border-red-500/40 hover:text-red-400"
|
||||
>
|
||||
Clear history
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="theme-card rounded-2xl p-1">
|
||||
<div className="h-[420px] overflow-y-auto rounded-xl p-4 space-y-3">
|
||||
{!ready ? (
|
||||
<div className="flex h-full items-center justify-center text-sm text-foreground/40">Loading…</div>
|
||||
) : (
|
||||
msgs.map((m) => {
|
||||
const mine = m.from === handle || (m.from === "anon" && !handle);
|
||||
return (
|
||||
<div key={m.id} className={`flex flex-col ${mine ? "items-end" : "items-start"}`}>
|
||||
<div className="mb-0.5 flex items-center gap-2">
|
||||
<span className={`text-xs font-medium ${mine ? "text-neon-cyan" : "text-foreground/60"}`}>
|
||||
@{m.from}
|
||||
</span>
|
||||
<span className="text-[10px] text-foreground/30">{ts(m.ts)}</span>
|
||||
</div>
|
||||
<div
|
||||
className={`max-w-[85%] rounded-2xl px-4 py-2 text-sm ${
|
||||
mine ? "bg-neon-cyan/15 text-neon-cyan" : "bg-white/5"
|
||||
}`}
|
||||
>
|
||||
{m.text}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
<div ref={endRef} />
|
||||
</div>
|
||||
|
||||
<div className="mt-2 flex gap-3 p-2">
|
||||
<input
|
||||
type="text"
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && send()}
|
||||
placeholder={ready ? "Message the channel…" : "Loading…"}
|
||||
disabled={!ready}
|
||||
className="flex-1 rounded-xl border border-white/10 bg-transparent px-4 py-3 text-sm focus:border-neon-cyan/40 focus:outline-none disabled:opacity-50"
|
||||
maxLength={500}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={send}
|
||||
disabled={!input.trim() || !ready}
|
||||
className="rounded-xl bg-gradient-to-r from-neon-cyan to-neon-purple px-5 py-3 text-sm font-bold text-background disabled:opacity-40"
|
||||
>
|
||||
SEND
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 grid grid-cols-3 gap-3 text-[10px] text-foreground/40 font-mono">
|
||||
<div className="theme-card rounded-xl p-3 text-center">Client-only storage</div>
|
||||
<div className="theme-card rounded-xl p-3 text-center">No server transport</div>
|
||||
<div className="theme-card rounded-xl p-3 text-center">Per-handle isolation</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex flex-wrap gap-3">
|
||||
<Link href="/forum" className="text-xs text-neon-cyan hover:underline">Forum (persisted threads) →</Link>
|
||||
<Link href="/barter" className="text-xs text-foreground/50 hover:text-foreground/80">Barter board</Link>
|
||||
<Link href="/dashboard" className="text-xs text-foreground/50 hover:text-foreground/80">Dashboard</Link>
|
||||
</div>
|
||||
</section>
|
||||
</ThemedLayout>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user