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:
143
app/forum/page.tsx
Normal file
143
app/forum/page.tsx
Normal file
@@ -0,0 +1,143 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import ForumFeedToolbar from "@/components/forum/ForumFeedToolbar";
|
||||
import { ForumPostCard } from "@/components/forum/ForumPostCard";
|
||||
import { FORUM_RINGS } from "@/lib/forumRings";
|
||||
import { threadMatchesQuery } from "@/lib/forumFeedFilter";
|
||||
import {
|
||||
isPinnedThread,
|
||||
loadForum,
|
||||
sortPinnedThreads,
|
||||
type ForumThread,
|
||||
} from "@/lib/forumState";
|
||||
import { sortThreads, type ForumSortMode } from "@/lib/forumSort";
|
||||
|
||||
export default function VoidAggregateHomePage() {
|
||||
const [threads, setThreads] = useState<ForumThread[]>([]);
|
||||
const [sort, setSort] = useState<ForumSortMode>("hot");
|
||||
const [ringFilter, setRingFilter] = useState<string | null>(null);
|
||||
const [search, setSearch] = useState("");
|
||||
const [hydrated, setHydrated] = useState(false);
|
||||
const [, setVoteBump] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
setThreads(loadForum());
|
||||
setHydrated(true);
|
||||
}, []);
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
return threads.filter((t) => {
|
||||
if (ringFilter !== null && t.topicSlug !== ringFilter) return false;
|
||||
return threadMatchesQuery(t, search);
|
||||
});
|
||||
}, [threads, ringFilter, 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;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-8 lg:flex-row">
|
||||
<main className="min-w-0 flex-1 space-y-5">
|
||||
<ForumFeedToolbar
|
||||
sort={sort}
|
||||
onSortChange={setSort}
|
||||
search={search}
|
||||
onSearchChange={setSearch}
|
||||
ringFilter={ringFilter}
|
||||
onRingChange={setRingFilter}
|
||||
threadCount={totalShown}
|
||||
hydrated={hydrated}
|
||||
/>
|
||||
|
||||
{!hydrated ? (
|
||||
<p className="font-mono text-sm text-[#8a7d6b]">Loading feed…</p>
|
||||
) : totalShown === 0 ? (
|
||||
<div className="rounded-lg border border-dashed border-[#3d3228] p-10 text-center font-mono text-sm text-[#8a7d6b]">
|
||||
<p>No threads match.</p>
|
||||
<p className="mt-2 text-xs text-[#5c5044]">Clear search or pick another ring.</p>
|
||||
<Link href="/forum/submit" className="mt-4 inline-block text-[#ff9a3c] underline">
|
||||
Start a thread
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{pinnedList.length ? (
|
||||
<section className="space-y-2">
|
||||
<h2 className="font-mono text-[10px] font-bold uppercase tracking-[0.28em] text-[#c45c26]/90">
|
||||
Pinned · rules & mirrors
|
||||
</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]">
|
||||
Feed
|
||||
</h2>
|
||||
) : null}
|
||||
{feedList.map((t) => (
|
||||
<ForumPostCard key={t.id} thread={t} onVoteChange={() => setVoteBump((x) => x + 1)} />
|
||||
))}
|
||||
</section>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</main>
|
||||
|
||||
<aside className="w-full shrink-0 space-y-4 lg:w-72">
|
||||
<div className="rounded-lg border border-[#3d3228] bg-[#120e0a]/90 p-4">
|
||||
<h3 className="font-mono text-xs font-bold uppercase tracking-widest text-[#ff9a3c]/80">Void Aggregate</h3>
|
||||
<p className="mt-3 text-sm leading-relaxed text-[#b8a995]">
|
||||
Ringed boards — each namespace is an <code className="text-[#ff9a3c]">r/name</code> slice. Votes and new threads stay in your
|
||||
browser profile; seeded "Staff" threads stay read-only so the front page stays legible.
|
||||
</p>
|
||||
<Link
|
||||
href="/forum/submit"
|
||||
className="mt-4 block w-full rounded border border-[#ff9a3c] bg-[#ff9a3c]/15 py-2 text-center font-mono text-xs font-bold uppercase text-[#ffcb8a] hover:bg-[#ff9a3c]/25"
|
||||
>
|
||||
Create post
|
||||
</Link>
|
||||
<Link
|
||||
href="/chatter"
|
||||
className="mt-3 block w-full rounded border border-[#3d3228] py-2 text-center font-mono text-xs text-[#b8a995] hover:border-[#ff9a3c]/40"
|
||||
>
|
||||
Hub chatter archive
|
||||
</Link>
|
||||
<Link
|
||||
href="/account/hidden-services"
|
||||
className="mt-2 block w-full rounded border border-[#3d3228] py-2 text-center font-mono text-xs text-[#8a7d6b] hover:border-[#ff9a3c]/40"
|
||||
>
|
||||
Identity on mirrors
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-[#3d3228] bg-[#120e0a]/90 p-4">
|
||||
<h3 className="font-mono text-xs font-bold uppercase tracking-widest text-[#ff9a3c]/80">Rings</h3>
|
||||
<ol className="mt-3 max-h-[min(50vh,22rem)] space-y-2 overflow-y-auto font-mono text-sm text-[#b8a995] pr-1">
|
||||
{FORUM_RINGS.map((r) => (
|
||||
<li key={r.slug} className="flex justify-between gap-2 border-b border-[#2a2218]/80 pb-2 last:border-0">
|
||||
<Link href={`/forum/ring/${r.slug}`} className="min-w-0 truncate text-[#ffcb8a] hover:underline">
|
||||
r/{r.shortName}
|
||||
</Link>
|
||||
<span className="shrink-0 text-[#8a7d6b]">{r.members > 999 ? `${Math.round(r.members / 1000)}k` : r.members}</span>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user