Files
dark-lord/app/forum/ring/[slug]/page.tsx
drjones 78a071ba02 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
2026-04-07 21:35:52 -07:00

142 lines
5.2 KiB
TypeScript

"use client";
import { useEffect, useMemo, useState } from "react";
import Link from "next/link";
import { useParams } from "next/navigation";
import ForumFeedToolbar from "@/components/forum/ForumFeedToolbar";
import { ForumPostCard } from "@/components/forum/ForumPostCard";
import { threadMatchesQuery } from "@/lib/forumFeedFilter";
import { getRing } from "@/lib/forumRings";
import {
isPinnedThread,
loadForum,
sortPinnedThreads,
type ForumThread,
} from "@/lib/forumState";
import { sortThreads, type ForumSortMode } from "@/lib/forumSort";
export default function VoidAggregateRingPage() {
const params = useParams();
const slug = typeof params.slug === "string" ? params.slug : "";
const ring = getRing(slug);
const [threads, setThreads] = useState<ForumThread[]>([]);
const [sort, setSort] = useState<ForumSortMode>("hot");
const [search, setSearch] = useState("");
const [hydrated, setHydrated] = useState(false);
const [, setVoteBump] = useState(0);
useEffect(() => {
setThreads(loadForum());
setHydrated(true);
}, []);
const filtered = useMemo(() => {
if (!ring) return [];
return threads
.filter((t) => t.topicSlug === ring.slug)
.filter((t) => threadMatchesQuery(t, search));
}, [threads, ring, search]);
const { pinnedList, feedList } = useMemo(() => {
const pinned = sortPinnedThreads(filtered.filter(isPinnedThread));
const rest = filtered.filter((t) => !isPinnedThread(t));
return { pinnedList: pinned, feedList: sortThreads(rest, sort) };
}, [filtered, sort]);
const totalShown = pinnedList.length + feedList.length;
if (!ring) {
return (
<div className="rounded-lg border border-[#c45c26]/40 bg-[#120e0a] p-8 text-center font-mono">
<p className="text-[#ffcb8a]">Unknown ring · /r/{slug}</p>
<Link href="/forum" className="mt-4 inline-block text-sm text-[#ff9a3c] underline">
front page
</Link>
</div>
);
}
return (
<div className="space-y-6">
<header className="rounded-lg border border-[#3d3228] bg-[#120e0a]/90 p-6">
<p className="font-mono text-[10px] uppercase tracking-[0.35em] text-[#ff9a3c]/70">r/{ring.shortName}</p>
<h1 className="mt-2 font-mono text-2xl font-black text-[#ffcb8a] md:text-3xl">{ring.title}</h1>
<p className="mt-3 max-w-2xl text-sm leading-relaxed text-[#b8a995]">{ring.about}</p>
<div className="mt-4 flex flex-wrap gap-4 font-mono text-[11px] text-[#8a7d6b]">
<span>
<strong className="text-[#ffcb8a]/90">{ring.members.toLocaleString()}</strong> members
</span>
<span>
<strong className="text-[#39ff14]/80">{ring.activeNow.toLocaleString()}</strong> est. online
</span>
</div>
<div className="mt-5 flex flex-wrap gap-2">
<Link
href={`/forum/submit?ring=${encodeURIComponent(ring.slug)}`}
className="rounded border border-[#ff9a3c] bg-[#ff9a3c]/15 px-4 py-2 font-mono text-xs font-bold uppercase text-[#ffcb8a] hover:bg-[#ff9a3c]/25"
>
Post in r/{ring.shortName}
</Link>
<Link href="/forum" className="rounded border border-[#3d3228] px-4 py-2 font-mono text-xs text-[#b8a995] hover:border-[#ff9a3c]/40">
All rings
</Link>
</div>
</header>
<ForumFeedToolbar
sort={sort}
onSortChange={setSort}
search={search}
onSearchChange={setSearch}
ringFilter={ring.slug}
threadCount={totalShown}
hydrated={hydrated}
extraRight={
<Link href="/forum" className="rounded px-2 py-1 text-[#8a7d6b] hover:text-[#ffcb8a]">
Global feed
</Link>
}
/>
{!hydrated ? (
<p className="font-mono text-sm text-[#8a7d6b]">Loading</p>
) : totalShown === 0 ? (
<p className="rounded-lg border border-dashed border-[#3d3228] p-8 text-center font-mono text-sm text-[#8a7d6b]">
No threads match in r/{ring.shortName}.{" "}
<Link href={`/forum/submit?ring=${encodeURIComponent(ring.slug)}`} className="text-[#ff9a3c] underline">
Start one
</Link>
.
</p>
) : (
<div className="space-y-5">
{pinnedList.length ? (
<section className="space-y-2">
<h2 className="font-mono text-[10px] font-bold uppercase tracking-[0.28em] text-[#c45c26]/90">
Pinned here
</h2>
<div className="space-y-3">
{pinnedList.map((t) => (
<ForumPostCard key={t.id} thread={t} onVoteChange={() => setVoteBump((x) => x + 1)} />
))}
</div>
</section>
) : null}
{feedList.length ? (
<section className="space-y-3">
{pinnedList.length ? (
<h2 className="font-mono text-[10px] font-bold uppercase tracking-[0.28em] text-[#8a7d6b]">Threads</h2>
) : null}
{feedList.map((t) => (
<ForumPostCard key={t.id} thread={t} onVoteChange={() => setVoteBump((x) => x + 1)} />
))}
</section>
) : null}
</div>
)}
</div>
);
}