/* P.E.S.T Web App — Votes (custom polls, quorum, %, runtime, past votes) */ function voteTotals(v) { const total = Object.values(v.tally || {}).reduce((a, b) => a + b, 0); return total; } function fmtLeft(ms) { if (ms <= 0) return "ended"; const d = Math.floor(ms / 86400000); const h = Math.floor((ms % 86400000) / 3600000); if (d > 0) return d + "d " + h + "h left"; const m = Math.floor((ms % 3600000) / 60000); return h + "h " + m + "m left"; } function voteOutcome(v, eligible) { const total = voteTotals(v); const quorum = total >= v.threshold; let winner = null, max = -1; v.options.forEach((o) => { const c = v.tally[o.id] || 0; if (c > max) { max = c; winner = o; } }); const passed = quorum && winner && winner.id === v.options[0].id; return { total, quorum, winner, passed }; } function VoteCard({ v }) { const store = useStore(); const eligible = store.members.length; const total = voteTotals(v); const myChoice = store.myVotes[v.id]; const expired = v.closed || v.endsAt <= Date.now(); const revealed = !!myChoice || expired; const out = voteOutcome(v, eligible); const stateChip = v.closed || expired ? (out.passed ? Passed : {out.quorum ? "Rejected" : "No quorum"}) : ● Live; const cast = (optId) => { if (revealed) return; store.castVote(v.id, optId); toast("Vote cast"); }; return (

{v.title}

{v.desc}
{stateChip}
{total} / {eligible} votes Threshold {v.threshold} {v.closed || expired ? "Ended " + window.timeAgo(v.endsAt) : fmtLeft(v.endsAt - Date.now())}
Quorum {out.quorum ? "reached" : "not reached"} {Math.round((total / eligible) * 100)}% turnout
{v.options.map((o) => { const c = v.tally[o.id] || 0; const pct = total > 0 ? Math.round((c / total) * 100) : 0; const chosen = myChoice === o.id; const isWinner = revealed && out.winner && out.winner.id === o.id; return ( ); })}
{revealed ? (myChoice ? You voted · {v.options.find((o) => o.id === myChoice)?.label} : Voting closed) : Pick an option to cast your vote} {store.user.isAdmin && !v.closed && (
)}
); } function Votes() { const store = useStore(); const [tab, setTab] = useState("active"); const [creating, setCreating] = useState(false); const now = Date.now(); const active = store.votes.filter((v) => !v.closed && v.endsAt > now); const past = store.votes.filter((v) => v.closed || v.endsAt <= now); const list = tab === "active" ? active : past; return (
Governance

Hive votes

Custom polls decided by the Core. One vote per member · {store.members.length} eligible voters.

{store.user.isAdmin && }
{list.length === 0 ?
No {tab} votes.
:
{list.map((v) => )}
} {creating && setCreating(false)} />}
); } function CreateVoteModal({ onClose }) { const store = useStore(); const [title, setTitle] = useState(""); const [desc, setDesc] = useState(""); const [options, setOptions] = useState(["Approve", "Reject"]); const [threshold, setThreshold] = useState(Math.ceil(store.members.length / 2) + 1); const [days, setDays] = useState(3); useEffect(() => { const k = (e) => e.key === "Escape" && onClose(); window.addEventListener("keydown", k); return () => window.removeEventListener("keydown", k); }, []); const setOpt = (i, val) => setOptions((o) => o.map((x, k) => k === i ? val : x)); const addOpt = () => setOptions((o) => o.length < 6 ? [...o, ""] : o); const delOpt = (i) => setOptions((o) => o.length > 2 ? o.filter((_, k) => k !== i) : o); const save = () => { if (!title.trim()) { toast("Add a title", "err"); return; } const opts = options.map((l) => l.trim()).filter(Boolean); if (opts.length < 2) { toast("Add at least 2 options", "err"); return; } const id = title.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 24) + "-" + Math.random().toString(36).slice(2, 5); const vote = { id, title: title.trim(), desc: desc.trim(), options: opts.map((l, i) => ({ id: "o" + i + "-" + Math.random().toString(36).slice(2, 5), label: l })), tally: {}, threshold: Number(threshold) || 1, createdAt: Date.now(), endsAt: Date.now() + (Number(days) || 1) * 86400000, closed: false, }; store.addVote(vote); toast("Vote created"); onClose(); }; return (
e.stopPropagation()}>

New vote

setTitle(e.target.value)} placeholder="What are we deciding?" />