diff --git a/components/TreasuryVotingWidget/index.tsx b/components/TreasuryVotingWidget/index.tsx
index 879af615..167c1194 100644
--- a/components/TreasuryVotingWidget/index.tsx
+++ b/components/TreasuryVotingWidget/index.tsx
@@ -4,10 +4,12 @@ import { ProposalExtended } from "@lib/api/treasury";
import { ProposalVotingPower } from "@lib/api/types/get-treasury-proposal";
import dayjs from "@lib/dayjs";
import { abbreviateNumber, fromWei, shortenAddress } from "@lib/utils";
-import { Box, Button, Flex, Heading, Text } from "@livepeer/design-system";
+import { Box, Button, Flex, Link, Text } from "@livepeer/design-system";
+import { InfoCircledIcon } from "@modulz/radix-icons";
import { useAccountAddress } from "hooks";
import numbro from "numbro";
-import { useState } from "react";
+import { useMemo, useState } from "react";
+import { zeroAddress } from "viem";
import VoteButton from "../VoteButton";
@@ -19,328 +21,430 @@ type Props = {
const formatPercent = (percent: number) =>
numbro(percent).format({
output: "percent",
- mantissa: 4,
+ mantissa: 2,
});
const formatLPT = (lpt: string | undefined) =>
abbreviateNumber(fromWei(lpt ?? "0"), 4);
+const SectionLabel = ({ children }: { children: React.ReactNode }) => (
+
+ {children}
+
+);
+
const TreasuryVotingWidget = ({ proposal, vote, ...props }: Props) => {
const accountAddress = useAccountAddress();
const [reason, setReason] = useState("");
+ const hasVotingPower =
+ !!vote && !!vote.self && parseFloat(vote.self.votes) > 0;
+ const canVoteNow =
+ proposal.state === "Active" && vote?.self?.hasVoted === false;
+ const isIneligible = canVoteNow && !hasVotingPower;
+ const hasDelegate =
+ !!vote?.delegate && vote.delegate.address.toLowerCase() !== zeroAddress;
+
+ const userVoteStatus = useMemo(() => {
+ if (!vote?.self) return null;
+ if (isIneligible) return "Ineligible";
+ if (vote.self.hasVoted) return "Voted";
+ return "Not voted";
+ }, [vote?.self, isIneligible]);
+
return (
-
-
-
- Do you support{" "}
- {proposal.attributes?.lip
- ? `LIP-${proposal.attributes?.lip}`
- : "this proposal"}
- ?
-
+
+ {/* ========== RESULTS SECTION ========== */}
+
+ Results
-
-
+ {/* Vote bars - read-only styled */}
+
+ {/* Against bar */}
+
+
+ Against
+
-
- Against
-
-
- {formatPercent(proposal.votes.percent.against)}
+
+
+ {formatPercent(proposal.votes.percent.against)}
+
+
+
+ {/* For bar */}
+
+
+ For
+
-
- For
-
-
- {formatPercent(proposal.votes.percent.for)}
+
+
+ {formatPercent(proposal.votes.percent.for)}
+
+
+
+ {/* Abstain bar */}
+
+
+ Abstain
+
-
- Abstain
-
-
- {formatPercent(proposal.votes.percent.abstain)}
+
-
-
- {abbreviateNumber(proposal.votes.total.voters, 4)} LPT voted ·{" "}
- {proposal.state !== "Pending" && proposal.state !== "Active"
- ? "Final Results"
- : dayjs.duration(proposal.votes.voteEndTime.diff()).humanize() +
- " left"}
-
+
+ {formatPercent(proposal.votes.percent.abstain)}
+
+
- {accountAddress ? (
- <>
-
- {vote && !vote?.delegate ? null : (
-
-
- Your Delegate{" "}
- {vote?.delegate &&
- `(${shortenAddress(vote?.delegate.address)})`}
- {vote?.delegate &&
- ` ${
- vote.delegate.hasVoted
- ? "already voted"
- : "hasn't voted yet"
- }`}
-
-
- {vote?.delegate ? formatLPT(vote.delegate.votes) : "N/A"}
-
-
- )}
-
+ {abbreviateNumber(proposal.votes.total.voters, 4)} LPT voted ·{" "}
+ {proposal.state !== "Pending" && proposal.state !== "Active"
+ ? "Final Results"
+ : dayjs.duration(proposal.votes.voteEndTime.diff()).humanize() +
+ " left"}
+
+
+
+ {/* ========== YOUR VOTE SECTION ========== */}
+ {accountAddress ? (
+
+ Your vote
+
+ {/* Delegate vote status */}
+ {hasDelegate && (
+
+
+ Delegate vote ({shortenAddress(vote.delegate!.address)})
+
+
+ {vote.delegate!.hasVoted ? "Voted" : "Not voted"}
+
+
+ )}
+
+ {/* User vote status */}
+
+ Status
+
+ {userVoteStatus}
+
+
+
+ {/* Voting power */}
+
+ Voting power
+
+ {vote?.self ? formatLPT(vote.self.votes) : "0"} LPT
+
+
+
+ {/* ========== ACTION AREA ========== */}
+
+ {/* Eligible: show vote buttons + reason */}
+ {canVoteNow && hasVotingPower && (
+
+
-
- You (
- {accountAddress.replace(accountAddress.slice(5, 39), "…")})
- {vote?.self &&
- ` ${
- vote.self.hasVoted
- ? "already voted"
- : "haven't voted yet"
- }`}
-
-
- {vote?.self ? formatLPT(vote.self.votes) : "N/A"}
-
-
- {!vote?.self.hasVoted && proposal.state === "Active" && (
-
-
- My Voting Power
-
-
- {formatLPT(vote?.self.votes)} LPT
-
-
- )}
+ Against
+
+
+ For
+
+
+ Abstain
+
+
+
+
+
+ )}
- {proposal?.state === "Active" &&
- vote?.self.hasVoted === false && (
+ {/* Ineligible: show info banner + links, no buttons, no reason */}
+ {isIneligible && (
+
+
- 0)}
- variant="red"
- size="4"
- choiceId={0}
- proposalId={proposal?.id}
- reason={reason}
- >
- Against
-
- 0)}
- variant="primary"
- choiceId={1}
- size="4"
- proposalId={proposal?.id}
- reason={reason}
+ />
+
+
- For
-
- 0)}
- variant="gray"
- size="4"
- choiceId={2}
- proposalId={proposal?.id}
- reason={reason}
+ Ineligible to vote
+
+
- Abstain
-
-
- 0)}
- />
+ You had 0 LPT staked on{" "}
+ {proposal.votes.voteStartTime.format("MMM D, YYYY")} when
+ this proposal was created.
+
+
+
+ Learn about stake snapshots
+
+
- )}
+
+
+ )}
- {["Succeeded", "Queued"].includes(proposal?.state) && (
-
-
-
- )}
- >
- ) : (
+ {/* Queue/Execute buttons for passed proposals */}
+ {["Succeeded", "Queued"].includes(proposal?.state) && (
+
+
+
+ )}
+
+ ) : (
+ /* No wallet connected */
+
+ Your vote
-
+
Connect your wallet to vote.
- )}
-
+
+ )}
);
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ed8a1625..97f96d23 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8015,8 +8015,8 @@ packages:
numbro@2.5.0:
resolution: {integrity: sha512-xDcctDimhzko/e+y+Q2/8i3qNC9Svw1QgOkSkQoO0kIPI473tR9QRbo2KP88Ty9p8WbPy+3OpTaAIzehtuHq+A==}
- nwsapi@2.2.22:
- resolution: {integrity: sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==}
+ nwsapi@2.2.23:
+ resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==}
obj-multiplex@1.0.0:
resolution: {integrity: sha512-0GNJAOsHoBHeNTvl5Vt6IWnpUEcc3uSRxzBri7EDyIcMgYvnY2JL2qdeV5zTMjWQX5OHcD5amcW2HFfDh0gjIA==}
@@ -19797,7 +19797,7 @@ snapshots:
http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.6
is-potential-custom-element-name: 1.0.1
- nwsapi: 2.2.22
+ nwsapi: 2.2.23
parse5: 7.3.0
rrweb-cssom: 0.8.0
saxes: 6.0.0
@@ -20929,7 +20929,7 @@ snapshots:
dependencies:
bignumber.js: 9.3.1
- nwsapi@2.2.22: {}
+ nwsapi@2.2.23: {}
obj-multiplex@1.0.0:
dependencies:
@@ -22515,14 +22515,6 @@ snapshots:
tmpl@1.0.5: {}
- tldts-core@6.1.86: {}
-
- tldts@6.1.86:
- dependencies:
- tldts-core: 6.1.86
-
- tmpl@1.0.5: {}
-
to-buffer@1.2.2:
dependencies:
isarray: 2.0.5