Harden onion boot flow and deepen site surfaces
Add persistent onion key backup and restore, improve startup resilience, and flesh out the major site verticals with richer navigation, search coverage, and operator documentation. Made-with: Cursor
This commit is contained in:
142
app/chatter/page.tsx
Normal file
142
app/chatter/page.tsx
Normal file
@@ -0,0 +1,142 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useMemo } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { formatRingLabel } from "@/lib/forumRings";
|
||||
import { SHOP_CHATTER_COUNT, getChatterSlice } from "@/lib/shopChatterThreads";
|
||||
|
||||
const PAGE_SIZE = 12;
|
||||
|
||||
function formatAgo(ts: number): string {
|
||||
const s = Math.floor((Date.now() - ts) / 1000);
|
||||
if (s < 60) return `${s}s ago`;
|
||||
const m = Math.floor(s / 60);
|
||||
if (m < 60) return `${m}m ago`;
|
||||
const h = Math.floor(m / 60);
|
||||
if (h < 48) return `${h}h ago`;
|
||||
const d = Math.floor(h / 24);
|
||||
return `${d}d ago`;
|
||||
}
|
||||
|
||||
export default function HubChatterArchivePage() {
|
||||
const sp = useSearchParams();
|
||||
const page = Math.max(1, parseInt(sp.get("page") ?? "1", 10) || 1);
|
||||
|
||||
const { items, totalPages, total } = useMemo(
|
||||
() => getChatterSlice(page, PAGE_SIZE),
|
||||
[page],
|
||||
);
|
||||
|
||||
const pages = useMemo(() => {
|
||||
const window = 2;
|
||||
const lo = Math.max(1, page - window);
|
||||
const hi = Math.min(totalPages, page + window);
|
||||
const out: number[] = [];
|
||||
for (let i = lo; i <= hi; i++) out.push(i);
|
||||
return out;
|
||||
}, [page, totalPages]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#050403] text-[#d4c4b0]">
|
||||
<div
|
||||
className="pointer-events-none fixed inset-0 z-0 opacity-[0.08]"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(255,180,120,0.12) 3px)",
|
||||
}}
|
||||
/>
|
||||
<header className="relative z-10 border-b border-[#3d3028] bg-[#0c0906]/95 px-4 py-8">
|
||||
<div className="mx-auto max-w-3xl">
|
||||
<p className="font-mono text-[10px] uppercase tracking-[0.4em] text-[#c45c26]/90">archive</p>
|
||||
<h1 className="mt-2 font-mono text-2xl font-black text-[#ffcb8a] md:text-3xl">Hub chatter index</h1>
|
||||
<p className="mt-3 max-w-2xl text-sm leading-relaxed text-[#9a8d7c]">
|
||||
Scraped-style threads about the <strong className="text-[#ffcb8a]">CyberLux main storefront</strong> — mirror
|
||||
checks, checkout banter, UI roasts, class-lab meta. Each row opens in Void Aggregate for full comments.
|
||||
</p>
|
||||
<nav className="mt-6 flex flex-wrap gap-3 font-mono text-xs">
|
||||
<Link
|
||||
href="/forum"
|
||||
className="rounded border border-[#c45c26] bg-[#c45c26]/15 px-4 py-2 font-bold text-[#ffcb8a] hover:bg-[#c45c26]/25"
|
||||
>
|
||||
Void Aggregate forum
|
||||
</Link>
|
||||
<Link href="/" className="rounded border border-[#3d3028] px-4 py-2 text-[#b8a995] hover:border-[#c45c26]/50">
|
||||
Main hub
|
||||
</Link>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="relative z-10 mx-auto max-w-3xl px-4 pb-24 pt-8">
|
||||
<p className="mb-6 font-mono text-xs text-[#6b6358]">
|
||||
Showing {(page - 1) * PAGE_SIZE + 1}–{Math.min(page * PAGE_SIZE, total)} of {total} indexed posts ·{" "}
|
||||
{PAGE_SIZE} per page
|
||||
</p>
|
||||
|
||||
<ul className="space-y-4">
|
||||
{items.map((row) => (
|
||||
<li key={row.id} className="rounded-lg border border-[#2a2218] bg-[#0a0705]/95 p-4">
|
||||
<div className="flex flex-wrap items-baseline justify-between gap-2 font-mono text-[10px] uppercase tracking-wider text-[#7a6e62]">
|
||||
<span className="text-[#ff9a3c]/90">{formatRingLabel(row.topicSlug)}</span>
|
||||
<span>
|
||||
{row.author} · {formatAgo(row.ts)}
|
||||
</span>
|
||||
</div>
|
||||
<Link
|
||||
href={`/forum/post/${row.id}`}
|
||||
className="mt-2 block font-mono text-base font-bold leading-snug text-[#ffcb8a] hover:underline"
|
||||
>
|
||||
{row.title}
|
||||
</Link>
|
||||
<p className="mt-2 line-clamp-3 text-sm leading-relaxed text-[#b0a090]">{row.body}</p>
|
||||
<div className="mt-3 flex flex-wrap gap-3 font-mono text-[11px] text-[#6b6358]">
|
||||
<span>{row.replies.length} comments</span>
|
||||
<span>score ~{row.baseScore ?? 1}</span>
|
||||
<Link href={`/forum/post/${row.id}`} className="text-[#ff9a3c] hover:underline">
|
||||
open thread →
|
||||
</Link>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<nav className="mt-12 flex flex-wrap items-center justify-center gap-2 font-mono text-xs">
|
||||
{page > 1 ? (
|
||||
<Link
|
||||
href={`/chatter?page=${page - 1}`}
|
||||
className="rounded border border-[#3d3028] px-3 py-2 text-[#ffcb8a] hover:border-[#c45c26]"
|
||||
>
|
||||
← Newer
|
||||
</Link>
|
||||
) : (
|
||||
<span className="rounded border border-transparent px-3 py-2 text-[#4a433c]">← Newer</span>
|
||||
)}
|
||||
{pages.map((n) => (
|
||||
<Link
|
||||
key={n}
|
||||
href={`/chatter?page=${n}`}
|
||||
className={`rounded border px-3 py-2 ${
|
||||
n === page ? "border-[#c45c26] bg-[#c45c26]/20 text-[#ffcb8a]" : "border-[#3d3028] text-[#b8a995] hover:border-[#c45c26]/40"
|
||||
}`}
|
||||
>
|
||||
{n}
|
||||
</Link>
|
||||
))}
|
||||
{page < totalPages ? (
|
||||
<Link
|
||||
href={`/chatter?page=${page + 1}`}
|
||||
className="rounded border border-[#3d3028] px-3 py-2 text-[#ffcb8a] hover:border-[#c45c26]"
|
||||
>
|
||||
Older →
|
||||
</Link>
|
||||
) : (
|
||||
<span className="rounded border border-transparent px-3 py-2 text-[#4a433c]">Older →</span>
|
||||
)}
|
||||
</nav>
|
||||
|
||||
<p className="mt-10 text-center font-mono text-[10px] text-[#4a4338]">Total catalogued: {SHOP_CHATTER_COUNT} threads</p>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user