Files
dark-lord/components/ForumBoard.tsx
drjones 2f928fbdc4 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
2026-04-16 00:55:28 -07:00

169 lines
6.3 KiB
TypeScript

"use client";
import Link from "next/link";
import { useState, useEffect } from "react";
import { useAccount } from "@/contexts/AccountContext";
import {
loadForum,
addThread,
displayScore,
setVoteForThread,
getVoteForThread,
type ForumThread,
} from "@/lib/forumState";
const CATEGORIES = ["All", "Security", "Market", "OPSEC", "Tech", "General"];
export default function ForumBoard() {
const { user } = useAccount();
const [threads, setThreads] = useState<ForumThread[]>([]);
const [newPost, setNewPost] = useState("");
const [selectedCat, setSelectedCat] = useState("All");
const [voteBump, setVoteBump] = useState(0);
const [posting, setPosting] = useState(false);
useEffect(() => {
setThreads(loadForum().slice(0, 8));
}, []);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!newPost.trim() || posting) return;
setPosting(true);
const thread = addThread({
title: newPost.slice(0, 80),
body: newPost,
author: user?.username ?? "anon",
topicSlug: "general",
category: "General",
replies: [],
});
setThreads([thread, ...threads].slice(0, 8));
setNewPost("");
setPosting(false);
};
const handleVote = (id: string, delta: 1 | -1) => {
const existing = getVoteForThread(id);
const next = existing === delta ? 0 : delta;
setVoteForThread(id, next as 1 | -1 | 0);
setVoteBump((v) => v + 1);
};
const visible = selectedCat === "All"
? threads
: threads.filter((t) => t.category === selectedCat || t.topicSlug === selectedCat.toLowerCase());
return (
<div className="glass rounded-3xl border border-white/10 p-8">
<div className="mb-8 flex flex-wrap items-start justify-between gap-4">
<div>
<h2 className="font-orbitron text-3xl font-bold">VOID AGGREGATE</h2>
<p className="mt-1 text-sm text-foreground/60">
{threads.length} threads shown · full board at{" "}
<Link href="/forum" className="text-neon-cyan hover:underline">/forum</Link>
</p>
</div>
</div>
{/* Category filter */}
<div className="mb-6 flex flex-wrap gap-2">
{CATEGORIES.map((cat) => (
<button
key={cat}
type="button"
onClick={() => setSelectedCat(cat)}
className={`rounded-full px-4 py-1.5 text-xs font-medium transition-all ${
selectedCat === cat
? "bg-gradient-to-r from-neon-cyan to-neon-purple text-background"
: "glass border border-white/10 hover:border-neon-cyan/30"
}`}
>
{cat}
</button>
))}
</div>
{/* Quick post form */}
<form onSubmit={handleSubmit} className="mb-8">
<div className="glass rounded-2xl border border-white/10 p-5">
<textarea
className="w-full bg-transparent text-sm text-foreground placeholder-foreground/40 focus:outline-none"
rows={2}
placeholder={user ? `Post as @${user.username}` : "Quick post (stored in your browser)…"}
value={newPost}
onChange={(e) => setNewPost(e.target.value)}
maxLength={500}
/>
<div className="mt-3 flex items-center justify-between">
<span className="text-[10px] text-foreground/30">{newPost.length}/500 · local storage</span>
<button
type="submit"
disabled={!newPost.trim() || posting}
className="rounded-full bg-gradient-to-r from-neon-cyan to-neon-purple px-5 py-2 text-sm font-bold text-background disabled:opacity-40"
>
Post
</button>
</div>
</div>
</form>
{/* Thread list */}
<div className="space-y-4">
{visible.length === 0 ? (
<p className="py-6 text-center text-sm text-foreground/40">No threads in this category yet.</p>
) : (
visible.map((t) => {
const score = displayScore(t);
const voted = typeof window !== "undefined" ? getVoteForThread(t.id) : 0;
return (
<div key={t.id + voteBump} className="glass rounded-2xl border border-white/10 p-5">
<div className="flex items-start justify-between gap-3">
<div className="flex items-center gap-3">
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-gradient-to-br from-neon-cyan/20 to-neon-purple/20 text-sm font-bold">
{(t.author ?? "?").charAt(0).toUpperCase()}
</div>
<div>
<div className="flex flex-wrap items-center gap-2">
<span className="text-sm font-bold">@{t.author ?? "anon"}</span>
{t.category && (
<span className="rounded-full bg-white/5 px-2 py-0.5 text-[10px]">{t.category}</span>
)}
</div>
<div className="text-xs text-foreground/40">{new Date(t.ts).toLocaleDateString()}</div>
</div>
</div>
<div className="flex shrink-0 items-center gap-3">
<button
type="button"
onClick={() => handleVote(t.id, 1)}
className={`text-xs transition-colors ${voted === 1 ? "text-neon-cyan" : "text-foreground/40 hover:text-neon-cyan"}`}
>
{score}
</button>
<span className="text-xs text-foreground/30">💬 {t.replies.length}</span>
</div>
</div>
<div className="mt-3">
<Link href="/forum" className="text-sm hover:text-neon-cyan/80">
{t.title ?? t.body?.slice(0, 100)}
</Link>
</div>
</div>
);
})
)}
</div>
<div className="mt-6 text-center">
<Link
href="/forum"
className="inline-flex items-center gap-2 rounded-full border border-neon-cyan/30 px-6 py-2 text-sm font-bold text-neon-cyan hover:bg-neon-cyan/10"
>
Open full forum
</Link>
</div>
</div>
);
}