Skip to content
This repository was archived by the owner on Mar 16, 2026. It is now read-only.

Commit c07c2cb

Browse files
committed
add veto check and veto badge in proposal card and details
1 parent c3a050b commit c07c2cb

5 files changed

Lines changed: 120 additions & 3 deletions

File tree

src/components/proposals/ProposalCard.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export default function ProposalCard({
4242
error: hasVoteDataError,
4343
refreshVoteData,
4444
isLoading: isLoadingVotes,
45+
vetoCheck,
4546
} = useProposalVote({
4647
proposal,
4748
contractPrincipal: proposal.contract_principal,
@@ -250,6 +251,13 @@ export default function ProposalCard({
250251
</>
251252
)}
252253

254+
{/* Veto Override Warning Badge */}
255+
{vetoCheck?.vetoExceedsForVote && !isActive && (
256+
<div className="px-2 py-0.5 rounded-sm text-xs font-medium border flex-shrink-0 bg-destructive/10 border-destructive/20">
257+
<span className="text-destructive">⚠️ Vetoed</span>
258+
</div>
259+
)}
260+
253261
<div className="flex items-center gap-1 text-xs text-foreground/75 flex-shrink-0">
254262
<Clock className="h-3 w-3 flex-shrink-0" />
255263
<span className="whitespace-nowrap">

src/components/proposals/VotesTable.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,9 @@ const VotesTable = ({ proposalId, limit }: VotesTableProps) => {
170170
</tr>
171171
</thead>
172172
<tbody>
173-
{displayedVotes.map((vote, index) => (
173+
{displayedVotes.map((vote) => (
174174
<tr
175-
key={vote.tx_id || index}
175+
key={vote.id}
176176
className="border-b border-border/50 hover:bg-muted/30 transition-colors"
177177
>
178178
{/* Voter */}

src/components/proposals/VotingProgressChart.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ const VotingProgressChart = ({
5757
error: voteDataError,
5858
hasData,
5959
refreshVoteData,
60+
vetoCheck,
6061
} = useProposalVote({
6162
proposal,
6263
contractPrincipal,
@@ -172,6 +173,17 @@ const VotingProgressChart = ({
172173
const getResultStatus = () => {
173174
const StatusIcon = statusConfig.icon;
174175

176+
// Check if veto amount exceeds For votes - this overrides other status
177+
if (vetoCheck.vetoExceedsForVote && !isActive) {
178+
return {
179+
status: "Failed",
180+
color: "text-destructive",
181+
icon: <XCircle className="h-4 w-4" />,
182+
bgColor: "bg-destructive/10 border-destructive/20",
183+
reason: "Veto amount exceeds For votes",
184+
};
185+
}
186+
175187
switch (status) {
176188
case "DRAFT":
177189
return {
@@ -742,6 +754,13 @@ const VotingProgressChart = ({
742754
/>
743755
</div>
744756

757+
{/* Veto override message */}
758+
{resultStatus.reason && (
759+
<div className="text-destructive">
760+
⚠️ {resultStatus.reason}
761+
</div>
762+
)}
763+
745764
{/* Additional status-specific information */}
746765
{status === "EXECUTION_WINDOW" && (
747766
<div className="text-accent-foreground">
@@ -760,7 +779,8 @@ const VotingProgressChart = ({
760779
)}
761780
{status === "FAILED" &&
762781
enhancedCalculations.metQuorum &&
763-
enhancedCalculations.metThreshold && (
782+
enhancedCalculations.metThreshold &&
783+
!resultStatus.reason && (
764784
<div className="text-destructive">
765785
⚠️ Failed despite meeting requirements
766786
</div>

src/hooks/useProposalVote.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useQuery, useQueryClient } from "@tanstack/react-query";
33
import { fetchProposalVotes } from "@/services/vote.service";
44
import { useProposalStatus } from "@/hooks/useProposalStatus";
55
import { useSmartCacheBusting } from "@/hooks/useSmartCacheBusting";
6+
import { useVetoCheck } from "@/hooks/useVetoCheck";
67
import type { Proposal, ProposalWithDAO } from "@/types";
78

89
interface UseProposalVoteProps {
@@ -270,6 +271,12 @@ export function useProposalVote({
270271
return !voteDisplayData && primaryQuery.error;
271272
}, [voteDisplayData, primaryQuery.error]);
272273

274+
// Check if veto amount exceeds For votes
275+
const vetoCheck = useVetoCheck({
276+
proposal,
277+
votesForNum: calculations?.votesForNum || 0,
278+
});
279+
273280
return {
274281
// Data
275282
voteDisplayData,
@@ -291,6 +298,9 @@ export function useProposalVote({
291298
// Raw data
292299
rawData: activeVoteData,
293300

301+
// Veto check
302+
vetoCheck,
303+
294304
// Debug info
295305
cacheInfo: {
296306
shouldAlwaysBustCache,

src/hooks/useVetoCheck.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { useQuery } from "@tanstack/react-query";
2+
import { fetchProposalVetos } from "@/services/veto.service";
3+
import type { Proposal, ProposalWithDAO } from "@/types";
4+
import { useMemo } from "react";
5+
6+
interface UseVetoCheckProps {
7+
proposal: Proposal | ProposalWithDAO;
8+
votesForNum?: number;
9+
}
10+
11+
interface VetoCheckResult {
12+
totalVetoAmount: number;
13+
rawTotalVetoAmount: string;
14+
vetoExceedsForVote: boolean;
15+
isLoading: boolean;
16+
error: boolean;
17+
}
18+
19+
/**
20+
* Hook to check if veto amount exceeds the For votes
21+
* Vetos are formatted the same way as votes (divided by 1e8)
22+
*/
23+
export function useVetoCheck({
24+
proposal,
25+
votesForNum = 0,
26+
}: UseVetoCheckProps): VetoCheckResult {
27+
// Fetch vetos for this proposal
28+
const {
29+
data: vetos,
30+
isLoading,
31+
error,
32+
} = useQuery({
33+
queryKey: ["proposalVetos", proposal.id],
34+
queryFn: async () => {
35+
if (!proposal.id) {
36+
return [];
37+
}
38+
return await fetchProposalVetos(proposal.id);
39+
},
40+
enabled: !!proposal.id,
41+
staleTime: 30000, // 30 seconds
42+
retry: 2,
43+
});
44+
45+
// Calculate total veto amount
46+
const vetoCalculations = useMemo(() => {
47+
if (!vetos || vetos.length === 0) {
48+
return {
49+
totalVetoAmount: 0,
50+
rawTotalVetoAmount: "0",
51+
vetoExceedsForVote: false,
52+
};
53+
}
54+
55+
// Sum up all veto amounts
56+
const totalRaw = vetos.reduce((sum, veto) => {
57+
const amount = veto.amount ? parseFloat(veto.amount) : 0;
58+
return sum + amount;
59+
}, 0);
60+
61+
// Format veto amount the same way as votes (divide by 1e8)
62+
const totalFormatted = totalRaw;
63+
64+
// Check if veto exceeds For votes
65+
const vetoExceedsForVote = totalFormatted > votesForNum;
66+
67+
return {
68+
totalVetoAmount: totalFormatted,
69+
rawTotalVetoAmount: totalRaw.toString(),
70+
vetoExceedsForVote,
71+
};
72+
}, [vetos, votesForNum]);
73+
74+
return {
75+
...vetoCalculations,
76+
isLoading,
77+
error: !!error,
78+
};
79+
}

0 commit comments

Comments
 (0)