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
65 lines
1.6 KiB
TypeScript
65 lines
1.6 KiB
TypeScript
"use client";
|
|
|
|
import { useCallback, useState } from "react";
|
|
import type { ForumThread } from "@/lib/forumState";
|
|
import {
|
|
displayScore,
|
|
getVoteForThread,
|
|
setVoteForThread,
|
|
type VoteChoice,
|
|
} from "@/lib/forumState";
|
|
|
|
export function ThreadVoteColumn({
|
|
thread,
|
|
dense,
|
|
onVoteChange,
|
|
}: {
|
|
thread: ForumThread;
|
|
dense?: boolean;
|
|
onVoteChange?: () => void;
|
|
}) {
|
|
const [tick, setTick] = useState(0);
|
|
const choice = getVoteForThread(thread.id);
|
|
const score = displayScore(thread);
|
|
|
|
const apply = useCallback(
|
|
(dir: 1 | -1) => {
|
|
const cur = getVoteForThread(thread.id);
|
|
let next: VoteChoice = dir;
|
|
if (cur === dir) next = 0;
|
|
setVoteForThread(thread.id, next);
|
|
setTick((t) => t + 1);
|
|
onVoteChange?.();
|
|
},
|
|
[thread.id, onVoteChange],
|
|
);
|
|
|
|
void tick;
|
|
|
|
const btn = dense ? "px-1 py-0.5 text-[10px]" : "px-1.5 py-1 text-xs";
|
|
|
|
return (
|
|
<div
|
|
className={`flex flex-col items-center rounded border border-[#3d3228] bg-[#0d0a07] font-mono ${dense ? "min-w-[44px] py-1" : "min-w-[52px] py-2"}`}
|
|
>
|
|
<button
|
|
type="button"
|
|
aria-label="Upvote"
|
|
onClick={() => apply(1)}
|
|
className={`${btn} leading-none text-[#8a7d6b] hover:text-[#ff9a3c] ${choice === 1 ? "text-[#ff9a3c]" : ""}`}
|
|
>
|
|
▲
|
|
</button>
|
|
<span className="text-[11px] font-bold tabular-nums text-[#ffcb8a]">{score}</span>
|
|
<button
|
|
type="button"
|
|
aria-label="Downvote"
|
|
onClick={() => apply(-1)}
|
|
className={`${btn} leading-none text-[#8a7d6b] hover:text-[#6b9fff] ${choice === -1 ? "text-[#6b9fff]" : ""}`}
|
|
>
|
|
▼
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|