Files
Kontrollverlust/frontend/src/components/Votes.tsx
T
irrlichtandClaude Opus 5.5 bd5c59bcad React-Frontend: Feed mit Beitragskarte, Votes und Repost
- Feed per useInfiniteQuery mit Sentinel-Nachladen, Duplikate gefiltert,
  Stopp an der Backend-Seitengrenze
- EntryCard mit unverändertem Markup (inkl. [deleted]-Platzhalter)
- Votes mit optimistischem Umschalten, Rollback bei Fehler
- Repost mit Cooldown-Anzeige und 429-Auswertung
- linkify ohne innerHTML

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiXsPUqw7oeomZ8wZrQW5q
2026-09-26 23:30:17 +02:00

70 lines
2.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { api, errMsg } from "../api";
import type { VoteMode, VoteTally } from "../types";
// toggled rechnet vor, was der Server aus einer Stimme macht (siehe
// handleVote): gleiche Stimme zieht sie zurück, andere wechselt. Damit
// schaltet der Knopf sofort um; die Serverantwort überschreibt danach.
function toggled(t: VoteTally, mode: VoteMode): VoteTally {
const next = { ...t };
if (t.selected !== "none") next[t.selected]--;
if (t.selected === mode) {
next.selected = "none";
} else {
next[mode]++;
next.selected = mode;
}
return next;
}
export function Votes({ pid }: { pid: number }) {
const qc = useQueryClient();
const key = ["votes", pid];
const tally = useQuery({
queryKey: key,
queryFn: () => api.get<VoteTally>(`/entry/${pid}/votes`),
});
const vote = useMutation({
mutationFn: (mode: VoteMode) => {
const fd = new FormData();
fd.append("mode", mode);
return api.post<VoteTally>(`/entry/${pid}/vote`, fd);
},
onMutate: async (mode) => {
await qc.cancelQueries({ queryKey: key });
const prev = qc.getQueryData<VoteTally>(key);
if (prev) qc.setQueryData(key, toggled(prev, mode));
return { prev };
},
onError: (_err, _mode, ctx) => {
if (ctx?.prev) qc.setQueryData(key, ctx.prev);
},
onSuccess: (data) => qc.setQueryData(key, data),
});
const t = tally.data;
let counts = "– / –";
if (vote.isError) counts = errMsg(vote.error, "Fehler");
else if (tally.isError) counts = errMsg(tally.error, "Fehler");
else if (t) counts = `${t.left} / ${t.right}`;
const button = (mode: VoteMode, label: string) => (
<button
className={t?.selected === mode ? "ghost selected" : "ghost"}
data-mode={mode}
onClick={() => vote.mutate(mode)}
>
{label}
</button>
);
return (
<div className="row">
{button("left", "Links")}
<span className="muted">{counts}</span>
{button("right", "Rechts")}
</div>
);
}