@@ -16,6 +16,8 @@ import { useProposalVote } from "@/hooks/useProposalVote";
1616import { Button } from "@/components/ui/button" ;
1717import { RefreshCw , AlertCircle } from "lucide-react" ;
1818import { motion } from "framer-motion" ;
19+ import { safeNumberFromBigInt } from "@/utils/proposal" ;
20+ import { cn } from "@/lib/utils" ;
1921
2022interface ProposalCardProps {
2123 proposal : Proposal | ProposalWithDAO ;
@@ -31,14 +33,16 @@ export default function ProposalCard({
3133 const router = useRouter ( ) ;
3234
3335 // Use the unified status system
34- const { statusConfig , isActive , isPassed } = useProposalStatus ( proposal ) ;
36+ const { status , statusConfig , isActive } = useProposalStatus ( proposal ) ;
3537
3638 // Use centralized vote hook for consistent data fetching
3739 const {
3840 voteDisplayData,
41+ calculations,
3942 error : hasVoteDataError ,
4043 refreshVoteData,
4144 isLoading : isLoadingVotes ,
45+ vetoCheck,
4246 } = useProposalVote ( {
4347 proposal,
4448 contractPrincipal : proposal . contract_principal ,
@@ -90,6 +94,43 @@ export default function ProposalCard({
9094 // const liquidTokens = Number(proposal.liquid_tokens);
9195 const { totalVotes, hasVoteData } = voteSummary ;
9296
97+ // Enhanced calculations for quorum and threshold display
98+ const enhancedCalculations = useMemo ( ( ) => {
99+ if ( ! calculations ) return null ;
100+
101+ const quorumPercentage = safeNumberFromBigInt ( proposal . voting_quorum ) ;
102+ const thresholdPercentage = safeNumberFromBigInt ( proposal . voting_threshold ) ;
103+
104+ // Calculate if requirements are met
105+ const metQuorum = calculations . participationRate >= quorumPercentage ;
106+ const metThreshold =
107+ calculations . totalVotes > 0
108+ ? calculations . approvalRate >= thresholdPercentage
109+ : false ;
110+
111+ return {
112+ ...calculations ,
113+ quorumPercentage,
114+ thresholdPercentage,
115+ metQuorum,
116+ metThreshold,
117+ } ;
118+ } , [ calculations , proposal ] ) ;
119+
120+ // Helper function for status display
121+ const getStatusText = ( met : boolean , percentage ?: number ) => {
122+ // If voting hasn't started (PENDING, DRAFT), show "Pending"
123+ if ( status === "PENDING" || status === "DRAFT" ) {
124+ return "Pending" ;
125+ }
126+
127+ if ( isActive ) {
128+ return percentage !== undefined ? `${ percentage . toFixed ( 1 ) } %` : "0%" ;
129+ }
130+
131+ return met ? "Passed" : "Failed" ;
132+ } ;
133+
93134 // Memoize DAO info
94135 const daoInfo = useMemo ( ( ) => {
95136 const proposalWithDAO = proposal as ProposalWithDAO ;
@@ -130,12 +171,93 @@ export default function ProposalCard({
130171 ? `#${ proposal . proposal_id } : ${ proposal . title } `
131172 : proposal . title }
132173 </ h3 >
133- < div className = "flex items-center gap-2 sm:gap-3 mb-2" >
174+ < div className = "flex flex-wrap items-center gap-2 mb-2" >
134175 < ProposalStatusBadge
135176 proposal = { proposal }
136177 size = "sm"
137178 className = "flex-shrink-0"
138179 />
180+
181+ { /* Quorum and Threshold Badges */ }
182+ { enhancedCalculations &&
183+ statusConfig . label !== "Pending" &&
184+ statusConfig . label !== "Draft" && (
185+ < >
186+ { /* Quorum Badge */ }
187+ < div
188+ className = { cn (
189+ "px-2 py-0.5 rounded-sm text-xs font-medium border flex-shrink-0" ,
190+ isActive
191+ ? enhancedCalculations . metQuorum
192+ ? "bg-success/10 border-success/20"
193+ : "bg-primary/10 border-primary/20"
194+ : enhancedCalculations . metQuorum
195+ ? "bg-success/10 border-success/20"
196+ : "bg-destructive/10 border-destructive/20"
197+ ) }
198+ >
199+ < span className = "text-muted-foreground" > Quorum:</ span > { " " }
200+ < span
201+ className = { cn (
202+ isActive
203+ ? enhancedCalculations . metQuorum
204+ ? "text-success"
205+ : "text-primary"
206+ : enhancedCalculations . metQuorum
207+ ? "text-success"
208+ : "text-destructive"
209+ ) }
210+ >
211+ { getStatusText (
212+ enhancedCalculations . metQuorum ,
213+ enhancedCalculations . participationRate
214+ ) }
215+ </ span >
216+ </ div >
217+
218+ { /* Threshold Badge */ }
219+ < div
220+ className = { cn (
221+ "px-2 py-0.5 rounded-sm text-xs font-medium border flex-shrink-0" ,
222+ isActive
223+ ? enhancedCalculations . metThreshold
224+ ? "bg-success/10 border-success/20"
225+ : "bg-primary/10 border-primary/20"
226+ : enhancedCalculations . metThreshold
227+ ? "bg-success/10 border-success/20"
228+ : "bg-destructive/10 border-destructive/20"
229+ ) }
230+ >
231+ < span className = "text-muted-foreground" >
232+ Threshold:
233+ </ span > { " " }
234+ < span
235+ className = { cn (
236+ isActive
237+ ? enhancedCalculations . metThreshold
238+ ? "text-success"
239+ : "text-primary"
240+ : enhancedCalculations . metThreshold
241+ ? "text-success"
242+ : "text-destructive"
243+ ) }
244+ >
245+ { getStatusText (
246+ enhancedCalculations . metThreshold ,
247+ enhancedCalculations . approvalRate
248+ ) }
249+ </ span >
250+ </ div >
251+ </ >
252+ ) }
253+
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+
139261 < div className = "flex items-center gap-1 text-xs text-foreground/75 flex-shrink-0" >
140262 < Clock className = "h-3 w-3 flex-shrink-0" />
141263 < span className = "whitespace-nowrap" >
@@ -347,28 +469,11 @@ export default function ProposalCard({
347469 </ div >
348470 ) }
349471
350- { /* Completed Status */ }
351- { /* {isPassed && (
352- <div className="text-sm">
353- <span className="text-foreground/75">Final result: </span>
354- <span className="font-medium">
355- <span className="text-success">
356- <TokenBalance variant="abbreviated" value={votesFor} /> For
357- </span>
358- ,{" "}
359- <span className="text-destructive">
360- <TokenBalance variant="abbreviated" value={votesAgainst} />{" "}
361- Against
362- </span>
363- </span>
364- </div>
365- )} */ }
366-
367- { /* Enhanced Chart Section for detailed view - Hide for pending proposals */ }
472+ { /* Vote Status Chart - Show for active, veto period, execution window, passed, and failed proposals */ }
368473 { ( isActive ||
369474 statusConfig . label === "Veto Period" ||
370475 statusConfig . label === "Execution Window" ||
371- isPassed ||
476+ statusConfig . label === "Passed" ||
372477 statusConfig . label === "Failed" ) &&
373478 statusConfig . label !== "Pending" && (
374479 < div className = "" >
0 commit comments