From 8a1f2d164345096d49812d15235e568ec093854d Mon Sep 17 00:00:00 2001 From: "Builder.io" Date: Sat, 18 Oct 2025 23:54:36 +0000 Subject: [PATCH] Add VoteWidget component for sneak peek prioritization cgen-fc7192f5b776427489e24fc222518fda --- client/components/roadmap/VoteWidget.tsx | 71 ++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 client/components/roadmap/VoteWidget.tsx diff --git a/client/components/roadmap/VoteWidget.tsx b/client/components/roadmap/VoteWidget.tsx new file mode 100644 index 00000000..fa7925d1 --- /dev/null +++ b/client/components/roadmap/VoteWidget.tsx @@ -0,0 +1,71 @@ +import { useEffect, useMemo, useState } from "react"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; + +export default function VoteWidget({ options }: { options: { id: string; label: string }[] }) { + const key = "aethex_roadmap_votes_v1"; + const [votes, setVotes] = useState>({}); + const [choice, setChoice] = useState(null); + + useEffect(() => { + try { + const raw = localStorage.getItem(key); + if (raw) setVotes(JSON.parse(raw)); + } catch {} + }, []); + + useEffect(() => { + try { + localStorage.setItem(key, JSON.stringify(votes)); + } catch {} + }, [votes]); + + const total = useMemo(() => Object.values(votes).reduce((a, b) => a + b, 0), [votes]); + + const vote = (id: string) => { + setChoice(id); + setVotes((m) => ({ ...m, [id]: (m[id] || 0) + 1 })); + }; + + const reset = () => { + setChoice(null); + setVotes({}); + }; + + return ( + + + What should we ship next? + Local voting preview. Public voting will sync later. + + +
+ {options.map((o) => { + const pct = total ? Math.round(((votes[o.id] || 0) / total) * 100) : 0; + return ( +
+
+
{o.label}
+
+
+
+
+
+ {pct}% + +
+
+ ); + })} +
+
+ +
Votes are stored locally on your device.
+
+ + + ); +}