- 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
227 lines
9.7 KiB
TypeScript
227 lines
9.7 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import Link from "next/link";
|
|
import { useAccount } from "@/contexts/AccountContext";
|
|
import { useWallet } from "@/contexts/WalletContext";
|
|
|
|
type ActivityMetric = { label: string; value: string; raw: number; max: number; description: string };
|
|
|
|
function buildMetrics(
|
|
luxCredits: number,
|
|
usdBalance: number,
|
|
testimonials: number,
|
|
forumPosts: number,
|
|
): ActivityMetric[] {
|
|
return [
|
|
{
|
|
label: "LUX Balance",
|
|
value: luxCredits.toLocaleString() + " LUX",
|
|
raw: Math.min(luxCredits / 8000, 1) * 100,
|
|
max: 8000,
|
|
description: "Earned via purchases. Drives Inner Circle tier.",
|
|
},
|
|
{
|
|
label: "USD Balance",
|
|
value: "$" + usdBalance.toFixed(2),
|
|
raw: Math.min(usdBalance / 500, 1) * 100,
|
|
max: 500,
|
|
description: "Bitcoin-funded USD credit on your handle.",
|
|
},
|
|
{
|
|
label: "Forum Activity",
|
|
value: forumPosts + " posts",
|
|
raw: Math.min(forumPosts / 20, 1) * 100,
|
|
max: 20,
|
|
description: "Threads and replies posted to the Void Aggregate.",
|
|
},
|
|
{
|
|
label: "Testimonials",
|
|
value: testimonials + " submitted",
|
|
raw: Math.min(testimonials / 5, 1) * 100,
|
|
max: 5,
|
|
description: "Testimonials written and stored locally.",
|
|
},
|
|
];
|
|
}
|
|
|
|
function loadForumPostCount(username: string): number {
|
|
if (typeof window === "undefined") return 0;
|
|
try {
|
|
const raw = localStorage.getItem("cyberlux-forum-v3");
|
|
if (!raw) return 0;
|
|
const data = JSON.parse(raw) as { threads?: { authorHandle: string }[] };
|
|
return (data.threads ?? []).filter((t) => t.authorHandle === username).length;
|
|
} catch {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
function loadTestimonialCount(): number {
|
|
if (typeof window === "undefined") return 0;
|
|
try {
|
|
const raw = localStorage.getItem("cyberlux-testimonials-v1");
|
|
if (!raw) return 0;
|
|
return (JSON.parse(raw) as unknown[]).length;
|
|
} catch {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
const WHAT_IS = [
|
|
{ q: "What does this score reflect?", a: "Your own activity on this device — LUX credits, USD balance, forum posts, and testimonials you've submitted. Scores are computed locally from your localStorage data." },
|
|
{ q: "Is this a global reputation system?", a: "No. Data never leaves your browser. There is no server-side scoring. This page is a personal activity dashboard dressed in the CyberLux aesthetic." },
|
|
{ q: "How do I improve my score?", a: "Fund your account via Bitcoin at /account/add-funds, make purchases (earns LUX), post in /forum, and submit testimonials at /testimonials." },
|
|
{ q: "What happens if I clear my browser data?", a: "All local data is lost. Export your account bundle from /dashboard before clearing site data to preserve your handle and vault." },
|
|
];
|
|
|
|
export default function TrustPage() {
|
|
const { user } = useAccount();
|
|
const { luxCredits, usdStoreCredit } = useWallet();
|
|
const [metrics, setMetrics] = useState<ActivityMetric[]>([]);
|
|
const [hydrated, setHydrated] = useState(false);
|
|
const [openFaq, setOpenFaq] = useState<number | null>(null);
|
|
|
|
useEffect(() => {
|
|
const forumPosts = user ? loadForumPostCount(user.username) : 0;
|
|
const testimonials = loadTestimonialCount();
|
|
setMetrics(buildMetrics(luxCredits, usdStoreCredit, testimonials, forumPosts));
|
|
setHydrated(true);
|
|
}, [luxCredits, usdStoreCredit, user]);
|
|
|
|
const overallScore = hydrated
|
|
? Math.round(metrics.reduce((s, m) => s + m.raw, 0) / metrics.length)
|
|
: 0;
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gradient-to-b from-gray-950 to-black text-gray-100">
|
|
<header className="border-b border-gray-800">
|
|
<div className="container mx-auto max-w-6xl px-4 py-5">
|
|
<div className="flex flex-wrap items-center justify-between gap-4">
|
|
<div className="flex items-center gap-3">
|
|
<div className="h-9 w-9 rounded-full bg-gradient-to-r from-green-500 to-emerald-600" />
|
|
<div>
|
|
<h1 className="text-xl font-bold">Activity Dashboard</h1>
|
|
<p className="text-xs text-gray-400">Your CyberLux score — computed from this device</p>
|
|
</div>
|
|
</div>
|
|
<nav className="flex flex-wrap items-center gap-4 text-sm">
|
|
<Link href="/testimonials" className="text-gray-300 hover:text-green-400">Testimonials</Link>
|
|
<Link href="/forum" className="text-gray-300 hover:text-green-400">Forum</Link>
|
|
<Link href="/" className="rounded-full bg-green-600 px-5 py-2 font-bold hover:bg-green-700">Hub</Link>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<section className="container mx-auto max-w-6xl px-4 py-12">
|
|
<div className="rounded-2xl border border-emerald-900/30 bg-gradient-to-r from-gray-900 to-gray-800 p-10">
|
|
<span className="rounded-full bg-emerald-900/30 px-3 py-1 text-xs font-bold text-emerald-300">
|
|
LOCAL SCORE · YOUR DEVICE ONLY
|
|
</span>
|
|
<h1 className="mt-5 text-4xl font-bold md:text-5xl">
|
|
{user ? (
|
|
<>
|
|
@{user.username} — Trust Score:{" "}
|
|
<span className="text-emerald-400">{overallScore}/100</span>
|
|
</>
|
|
) : (
|
|
<>
|
|
Activity Score: <span className="text-emerald-400">{overallScore}/100</span>
|
|
</>
|
|
)}
|
|
</h1>
|
|
<p className="mt-4 max-w-2xl text-gray-300">
|
|
Computed from your actual local data: LUX credits, USD balance, forum posts, and testimonials stored
|
|
in this browser. Not a global ranking — your data never leaves your device.
|
|
</p>
|
|
{!user && (
|
|
<p className="mt-4 text-sm text-amber-300">
|
|
<Link href="/sign-in?next=/trust" className="underline">Sign in</Link> to see per-handle scores.
|
|
</p>
|
|
)}
|
|
</div>
|
|
</section>
|
|
|
|
<section id="metrics" className="container mx-auto max-w-6xl px-4 pb-16">
|
|
<h2 className="mb-6 text-2xl font-bold">Your Metrics</h2>
|
|
{!hydrated ? (
|
|
<div className="text-gray-500">Loading…</div>
|
|
) : (
|
|
<div className="grid grid-cols-1 gap-5 md:grid-cols-2">
|
|
{metrics.map((m) => (
|
|
<div key={m.label} className="rounded-2xl border border-gray-800 bg-gray-900/50 p-7">
|
|
<div className="mb-4 flex items-start justify-between gap-2">
|
|
<h3 className="text-lg font-bold">{m.label}</h3>
|
|
<span className="text-2xl font-bold text-emerald-400">{m.value}</span>
|
|
</div>
|
|
<p className="mb-4 text-sm text-gray-400">{m.description}</p>
|
|
<div className="h-2 rounded-full bg-gray-800">
|
|
<div
|
|
className="h-full rounded-full bg-gradient-to-r from-emerald-600 to-green-400 transition-all"
|
|
style={{ width: `${m.raw}%` }}
|
|
/>
|
|
</div>
|
|
<div className="mt-1 text-right text-xs text-gray-600">{Math.round(m.raw)}%</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</section>
|
|
|
|
<section className="container mx-auto max-w-6xl px-4 pb-16">
|
|
<h2 className="mb-6 text-2xl font-bold">Improve Your Score</h2>
|
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
{[
|
|
{ label: "Add Bitcoin funds", sub: "Boosts USD balance", href: "/account/add-funds", color: "border-orange-700/40 text-orange-400" },
|
|
{ label: "Make a purchase", sub: "Earns LUX credits", href: "/market", color: "border-cyan-700/40 text-cyan-400" },
|
|
{ label: "Post in forum", sub: "Grows forum activity", href: "/forum", color: "border-purple-700/40 text-purple-400" },
|
|
{ label: "Write a testimonial", sub: "Adds to your score", href: "/testimonials#submit", color: "border-emerald-700/40 text-emerald-400" },
|
|
].map((item) => (
|
|
<Link
|
|
key={item.href}
|
|
href={item.href}
|
|
className={`rounded-xl border p-5 transition-all hover:bg-white/[0.03] ${item.color}`}
|
|
>
|
|
<div className={`font-bold ${item.color.split(" ")[1]}`}>{item.label}</div>
|
|
<p className="mt-1 text-xs text-gray-500">{item.sub}</p>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</section>
|
|
|
|
<section id="methodology" className="container mx-auto max-w-6xl px-4 pb-16">
|
|
<h2 className="mb-6 text-2xl font-bold">FAQ</h2>
|
|
<div className="space-y-3 max-w-3xl">
|
|
{WHAT_IS.map((item, i) => (
|
|
<div key={i} className="rounded-xl border border-gray-800 bg-gray-900/30">
|
|
<button
|
|
type="button"
|
|
onClick={() => setOpenFaq(openFaq === i ? null : i)}
|
|
className="flex w-full items-center justify-between px-6 py-4 text-left font-medium hover:text-green-300"
|
|
>
|
|
{item.q}
|
|
<span className={`transition-transform ${openFaq === i ? "rotate-180" : ""}`}>▾</span>
|
|
</button>
|
|
{openFaq === i && (
|
|
<div className="border-t border-gray-800 px-6 py-4 text-sm text-gray-400 leading-relaxed">
|
|
{item.a}
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</section>
|
|
|
|
<footer className="border-t border-gray-800 px-4 py-8 text-center text-sm text-gray-600">
|
|
<p>CyberLux Activity Dashboard · All data client-side · No tracking</p>
|
|
<div className="mt-2 flex justify-center gap-6">
|
|
<Link href="/" className="hover:text-gray-300">Hub</Link>
|
|
<Link href="/testimonials" className="hover:text-gray-300">Testimonials</Link>
|
|
<Link href="/reviews" className="hover:text-gray-300">Reviews</Link>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
);
|
|
}
|