From 1b20d61bb2d38cd253d2206a44a6b3b08d18437f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Diamond?= <32074058+Andre-Diamond@users.noreply.github.com> Date: Wed, 3 Dec 2025 07:07:30 +0200 Subject: [PATCH 1/4] Enhance governance components with proposal count and ballot selection improvements - Added total proposal count calculation to the governance page for better visibility. - Updated FloatingBallotSidebar to display total proposals alongside selected ballot proposals. - Improved ballot selection logic in BallotCard to automatically select a ballot with proposals if none is currently selected. - Enhanced VoteButton to handle moving proposals between ballots with a confirmation modal, ensuring a proposal can only exist on one ballot at a time. - Refined UI elements for better clarity and user experience across governance components. --- .../pages/wallet/governance/ballot/ballot.tsx | 63 +++++--- .../pages/wallet/governance/index.tsx | 28 +++- .../wallet/governance/proposal/index.tsx | 3 +- .../governance/proposal/voteButtton.tsx | 139 ++++++++++++++++-- 4 files changed, 200 insertions(+), 33 deletions(-) diff --git a/src/components/pages/wallet/governance/ballot/ballot.tsx b/src/components/pages/wallet/governance/ballot/ballot.tsx index ffcb793c..bb1f43c2 100644 --- a/src/components/pages/wallet/governance/ballot/ballot.tsx +++ b/src/components/pages/wallet/governance/ballot/ballot.tsx @@ -94,17 +94,35 @@ export default function BallotCard({ // Delete ballot mutation const deleteBallot = api.ballot.delete.useMutation(); - // Refresh ballots after submit or on load + // Refresh ballots after submit or on load and ensure a sensible default is selected React.useEffect(() => { - if (getBallots.data) { - // Sort newest first - const sorted = [...getBallots.data].sort( - (a, b) => - new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), - ); - setBallots(sorted); + if (!getBallots.data) return; + + // Sort newest first + const sorted = [...getBallots.data].sort( + (a, b) => + new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), + ); + setBallots(sorted); + + // If nothing is selected yet (or the selection no longer exists), + // automatically select the first ballot, preferring one that already has proposals. + if (!onSelectBallot) return; + + const hasSelected = + selectedBallotId && sorted.some((b) => b.id === selectedBallotId); + + if (!hasSelected) { + const ballotWithProposals = + sorted.find( + (b) => Array.isArray(b.items) && b.items.length > 0, + ) ?? sorted[0]; + + if (!ballotWithProposals) return; + + onSelectBallot(ballotWithProposals.id); } - }, [getBallots.data]); + }, [getBallots.data, onSelectBallot, selectedBallotId]); // Proxy ballot vote submission logic @@ -411,7 +429,7 @@ export default function BallotCard({
@@ -430,9 +448,18 @@ export default function BallotCard({ ))}
{creating && ( @@ -560,9 +587,9 @@ function BallotOverviewTable({ return (
- +
- + @@ -572,11 +599,9 @@ function BallotOverviewTable({ {ballot.items.map((item: string, idx: number) => ( - + @@ -609,8 +617,8 @@ function BallotOverviewTable({ - )} - - {ballot.items.map((item: string, idx: number) => ( - - - - + + + - - ))} + + + + + Yes + No + Abstain + + + + + + + + + ); + })}
# Title Choice / Delete
{idx + 1} diff --git a/src/components/pages/wallet/governance/index.tsx b/src/components/pages/wallet/governance/index.tsx index caac526b..aeadaec9 100644 --- a/src/components/pages/wallet/governance/index.tsx +++ b/src/components/pages/wallet/governance/index.tsx @@ -23,6 +23,11 @@ export default function PageGovernance() { const { refresh, ballots } = useBallot(appWallet?.id); const selected = ballots?.find((b) => b.id === selectedBallotId); const proposalCount = selected?.items?.length ?? 0; + const totalProposalCount = + ballots?.reduce( + (sum, b) => sum + (Array.isArray(b.items) ? b.items.length : 0), + 0, + ) ?? 0; if (appWallet === undefined) return <>; return ( @@ -71,6 +76,7 @@ export default function PageGovernance() { selectedBallotId={selectedBallotId} onSelectBallot={setSelectedBallotId} ballotCount={ballots?.length ?? 0} + totalProposalCount={totalProposalCount} proposalCount={proposalCount} manualUtxos={manualUtxos} /> @@ -84,6 +90,7 @@ interface FloatingBallotSidebarProps { selectedBallotId?: string; onSelectBallot: (id: string) => void; ballotCount: number; + totalProposalCount: number; proposalCount: number; manualUtxos: UTxO[]; } @@ -93,6 +100,7 @@ function FloatingBallotSidebar({ selectedBallotId, onSelectBallot, ballotCount, + totalProposalCount, proposalCount, manualUtxos }: FloatingBallotSidebarProps) { @@ -118,9 +126,15 @@ function FloatingBallotSidebar({ >
- {(ballotCount > 0 || proposalCount > 0) && ( + {(ballotCount > 0 || totalProposalCount > 0 || proposalCount > 0) && ( - {proposalCount > 0 ? proposalCount : ""} + {open + ? proposalCount > 0 + ? proposalCount + : "" + : totalProposalCount > 0 + ? totalProposalCount + : ""} )}
@@ -175,9 +189,15 @@ function FloatingBallotSidebar({ className={open ? "absolute -left-12 top-8 p-1.5 rounded-full bg-white/80 shadow hover:bg-gray-100 border" : "absolute top-0 right-0 p-1"} > - {(ballotCount > 0 || proposalCount > 0) && ( + {(ballotCount > 0 || totalProposalCount > 0 || proposalCount > 0) && ( - {proposalCount > 0 ? proposalCount : ''} + {open + ? proposalCount > 0 + ? proposalCount + : "" + : totalProposalCount > 0 + ? totalProposalCount + : ""} )} diff --git a/src/components/pages/wallet/governance/proposal/index.tsx b/src/components/pages/wallet/governance/proposal/index.tsx index 64e03581..71923793 100644 --- a/src/components/pages/wallet/governance/proposal/index.tsx +++ b/src/components/pages/wallet/governance/proposal/index.tsx @@ -113,8 +113,9 @@ export default function WalletGovernanceProposal({ {appWallet && ( setSelectedBallotId(ballotId)} /> )} {appWallet && ( diff --git a/src/components/pages/wallet/governance/proposal/voteButtton.tsx b/src/components/pages/wallet/governance/proposal/voteButtton.tsx index 8c99ab2c..b66913fc 100644 --- a/src/components/pages/wallet/governance/proposal/voteButtton.tsx +++ b/src/components/pages/wallet/governance/proposal/voteButtton.tsx @@ -18,11 +18,19 @@ import { import { ToastAction } from "@/components/ui/toast"; import useMultisigWallet from "@/hooks/useMultisigWallet"; import { api } from "@/utils/api"; -import {useBallot} from "@/hooks/useBallot"; +import { useBallot } from "@/hooks/useBallot"; import { useProxy } from "@/hooks/useProxy"; import { MeshProxyContract } from "@/components/multisig/proxy/offchain"; import { useWallet } from "@meshsdk/react"; import { useUserStore } from "@/lib/zustand/user"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import type { BallotType } from "../ballot/ballot"; interface VoteButtonProps { appWallet: Wallet; @@ -67,6 +75,11 @@ export default function VoteButton({ const drepInfo = useWalletsStore((state) => state.drepInfo); const [loading, setLoading] = useState(false); const [voteKind, setVoteKind] = useState<"Yes" | "No" | "Abstain">("Abstain"); + const [moveModal, setMoveModal] = useState<{ + targetBallotId: string; + conflictBallots: BallotType[]; + } | null>(null); + const [moveLoading, setMoveLoading] = useState(false); const { toast } = useToast(); const setAlert = useSiteStore((state) => state.setAlert); const network = useSiteStore((state) => state.network); @@ -315,15 +328,37 @@ export default function VoteButton({ } } + async function performAddToBallot(targetBallotId: string) { + await addProposalMutation.mutateAsync({ + ballotId: targetBallotId, + itemDescription: proposalTitle ?? description, + item: proposalId, + choice: voteKind, + }); + } + async function addProposalToBallot() { if (!selectedBallotId) return; try { - await addProposalMutation.mutateAsync({ - ballotId: selectedBallotId, - itemDescription: proposalTitle ?? description, - item: proposalId, - choice: voteKind, - }); + // Ensure a proposal can only exist on a single ballot at a time. + // If it already exists on a different ballot, open a modal to confirm moving it. + const ballotsWithProposal = + ballots?.filter( + (b) => + b.id !== selectedBallotId && + Array.isArray(b.items) && + b.items.includes(proposalId), + ) ?? []; + + if (ballotsWithProposal.length > 0) { + setMoveModal({ + targetBallotId: selectedBallotId, + conflictBallots: ballotsWithProposal, + }); + return; + } + + await performAddToBallot(selectedBallotId); toast({ title: "Added to Ballot", description: "Proposal successfully added to the ballot.", @@ -339,6 +374,44 @@ export default function VoteButton({ } } + async function confirmMoveProposal() { + if (!moveModal) return; + + try { + setMoveLoading(true); + + // Remove proposal from all other ballots before adding to the selected one + for (const b of moveModal.conflictBallots) { + const index = b.items.findIndex((item: string) => item === proposalId); + if (index >= 0) { + await removeProposalMutation.mutateAsync({ + ballotId: b.id, + index, + }); + } + } + + await performAddToBallot(moveModal.targetBallotId); + + toast({ + title: "Proposal moved", + description: + "Proposal was moved from the other ballot to the selected ballot.", + duration: 1500, + }); + } catch (error) { + toast({ + title: "Failed to Move Proposal", + description: `Error: ${error}`, + duration: 10000, + variant: "destructive", + }); + } finally { + setMoveLoading(false); + setMoveModal(null); + } + } + async function removeProposalFromBallot() { if (!selectedBallotId || proposalIndex === undefined || proposalIndex < 0) return; try { @@ -397,7 +470,11 @@ export default function VoteButton({ disabled={loading || utxos.length === 0} className="w-full rounded-md bg-blue-600 px-6 py-2 font-semibold text-white shadow hover:bg-blue-700" > - {loading ? "Voting..." : utxos.length > 0 ? `Vote${hasValidProxy ? " (Proxy Mode)" : ""}` : "No UTxOs Available"} + {loading + ? "Voting..." + : utxos.length > 0 + ? `Vote${hasValidProxy ? " (Proxy Mode)" : ""}` + : "No UTxOs Available"} {selectedBallotId && ( @@ -409,9 +486,53 @@ export default function VoteButton({ : "bg-green-600 hover:bg-green-700" } px-6 py-2 font-semibold text-white shadow`} > - {isInBallot ? "Remove proposal from ballot" : "Add proposal to ballot"} + {isInBallot + ? "Remove proposal from ballot" + : "Add proposal to ballot"} )} + + {/* Modal to confirm moving proposal between ballots */} + { + if (!open) setMoveModal(null); + }} + > + + + Move proposal to selected ballot? + + {moveModal && ( + + This proposal is already on ballot{" "} + {moveModal.conflictBallots + .map((b) => b.description || "Untitled ballot") + .join(", ")} + . If you continue, it will be removed from that ballot and + added to the currently selected ballot. + + )} + + +
+ + +
+
+
); } From 27ee498af6f74ab1f2d61fdf0a57351687ed73bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Diamond?= <32074058+Andre-Diamond@users.noreply.github.com> Date: Wed, 3 Dec 2025 09:45:22 +0200 Subject: [PATCH 2/4] Refactor governance components for improved UI and functionality - Added Minimize2 icon to FloatingBallotSidebar for better visual feedback when collapsing/expanding the ballot panel. - Enhanced styling of BallotCard and buttons for improved clarity and user experience. - Updated ballot selection display to include drop shadows for selected ballots, enhancing visibility. - Adjusted layout and alignment in BallotOverviewTable for better presentation of choices and actions. - Refined button styles to ensure consistency and responsiveness across different states. --- .../pages/wallet/governance/ballot/ballot.tsx | 40 ++++++++++++------- .../pages/wallet/governance/index.tsx | 24 +++++++++-- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/src/components/pages/wallet/governance/ballot/ballot.tsx b/src/components/pages/wallet/governance/ballot/ballot.tsx index bb1f43c2..f4086a41 100644 --- a/src/components/pages/wallet/governance/ballot/ballot.tsx +++ b/src/components/pages/wallet/governance/ballot/ballot.tsx @@ -429,7 +429,7 @@ export default function BallotCard({
@@ -438,16 +438,24 @@ export default function BallotCard({ key={b.id} className={`px-3 py-1 rounded-t-md font-medium transition ${ b.id === selectedBallotId - ? "bg-white text-blue-600 border border-b-0 border-blue-400 dark:bg-gray-900 dark:text-blue-400" - : "bg-gray-200 text-gray-700 hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600" + ? "bg-white text-gray-900 border border-b-0 border-gray-300 shadow-sm dark:bg-slate-900 dark:text-gray-100 dark:border-slate-700" + : "bg-white/70 text-gray-700 hover:bg-white dark:bg-slate-800 dark:text-gray-200 dark:hover:bg-slate-700 border border-transparent" }`} onClick={() => onSelectBallot && onSelectBallot(b.id)} > - {b.description || "Untitled"} + + {b.description || "Untitled"} + ))} @@ -520,15 +528,15 @@ export default function BallotCard({ )}
# TitleChoice / DeleteChoice / Delete
-
+
+
{idx + 1} - {ballot.itemDescriptions?.[idx] || ( - - - )} - -
-
{idx + 1} + {ballot.itemDescriptions?.[idx] || ( + - + )} + +
+ - - - - -
-
diff --git a/src/components/pages/wallet/governance/index.tsx b/src/components/pages/wallet/governance/index.tsx index 0ffe0bfb..781a3ee0 100644 --- a/src/components/pages/wallet/governance/index.tsx +++ b/src/components/pages/wallet/governance/index.tsx @@ -1,5 +1,4 @@ import CardInfo from "./card-info"; -import { useWalletsStore } from "@/lib/zustand/wallets"; import { useSiteStore } from "@/lib/zustand/site"; import AllProposals from "./proposals"; import useAppWallet from "@/hooks/useAppWallet"; @@ -7,11 +6,10 @@ import VoteCard from "./vote-card"; import ClarityCard from "./clarity/card-clarity"; import VoteCC from "./cCommitee/voteCC"; import UTxOSelector from "../new-transaction/utxoSelector"; -import { UTxO } from "@meshsdk/core"; -import BallotCard from "./ballot/ballot"; -import { useState, useEffect, useMemo } from "react"; +import type { UTxO } from "@meshsdk/core"; +import { useState } from "react"; import { useBallot } from "@/hooks/useBallot"; -import { Vote, Minimize2 } from "lucide-react"; +import FloatingBallotSidebar from "./ballot/FloatingBallotSidebar"; export default function PageGovernance() { const { appWallet } = useAppWallet(); @@ -20,7 +18,7 @@ export default function PageGovernance() { const [manualSelected, setManualSelected] = useState(false); const [selectedBallotId, setSelectedBallotId] = useState(undefined); - const { refresh, ballots } = useBallot(appWallet?.id); + const { ballots } = useBallot(appWallet?.id); const selected = ballots?.find((b) => b.id === selectedBallotId); const proposalCount = selected?.items?.length ?? 0; const totalProposalCount = @@ -83,153 +81,3 @@ export default function PageGovernance() { ); } - -// FloatingBallotSidebar component -interface FloatingBallotSidebarProps { - appWallet: any; - selectedBallotId?: string; - onSelectBallot: (id: string) => void; - ballotCount: number; - totalProposalCount: number; - proposalCount: number; - manualUtxos: UTxO[]; -} - -function FloatingBallotSidebar({ - appWallet, - selectedBallotId, - onSelectBallot, - ballotCount, - totalProposalCount, - proposalCount, - manualUtxos -}: FloatingBallotSidebarProps) { - const [open, setOpen] = useState(false); - const [isMobile, setIsMobile] = useState(false); - - useEffect(() => { - function handleResize() { - setIsMobile(typeof window !== "undefined" ? window.innerWidth < 768 : false); - } - handleResize(); - window.addEventListener("resize", handleResize); - return () => window.removeEventListener("resize", handleResize); - }, []); - - if (isMobile) { - return ( - <> - - - {open && ( -
-
- Your Ballots - {proposalCount > 0 && ( - - {proposalCount} - - )} - -
-
- -
-
- )} - - ); - } - - return ( -
-
- - {open && ( -
- -
- ) } -
-
- ); -} diff --git a/src/components/pages/wallet/governance/proposal/index.tsx b/src/components/pages/wallet/governance/proposal/index.tsx index 71923793..c18c2c15 100644 --- a/src/components/pages/wallet/governance/proposal/index.tsx +++ b/src/components/pages/wallet/governance/proposal/index.tsx @@ -2,47 +2,52 @@ import CardUI from "@/components/ui/card-content"; import { getProvider } from "@/utils/get-provider"; import RowLabelInfo from "@/components/common/row-label-info"; import { useSiteStore } from "@/lib/zustand/site"; -import { ProposalMetadata } from "@/types/governance"; +import type { ProposalMetadata } from "@/types/governance"; import { useEffect, useState } from "react"; import Link from "next/link"; import Button from "@/components/common/button"; -import { useWalletsStore } from "@/lib/zustand/wallets"; import useAppWallet from "@/hooks/useAppWallet"; import VoteCard from "../vote-card"; -import { UTxO } from "@meshsdk/core"; +import type { UTxO } from "@meshsdk/core"; import UTxOSelector from "../../new-transaction/utxoSelector"; -import BallotCard from "../ballot/ballot"; +import FloatingBallotSidebar from "../ballot/FloatingBallotSidebar"; +import { useBallot } from "@/hooks/useBallot"; -export default function WalletGovernanceProposal({ - id, -}: { - id: string; -}) { +export default function WalletGovernanceProposal({ id }: { id: string }) { const network = useSiteStore((state) => state.network); const [proposalMetadata, setProposalMetadata] = useState< ProposalMetadata | undefined >(undefined); - const drepInfo = useWalletsStore((state) => state.drepInfo); const { appWallet } = useAppWallet(); - const loading = useSiteStore((state) => state.loading); const [manualUtxos, setManualUtxos] = useState([]); - const [manualSelected, setManualSelected] = useState(false); - const [selectedBallotId, setSelectedBallotId] = useState(undefined); + const [selectedBallotId, setSelectedBallotId] = useState( + undefined, + ); + const [isBallotSidebarOpen, setIsBallotSidebarOpen] = useState(false); + + const { ballots } = useBallot(appWallet?.id); + const selected = ballots?.find((b) => b.id === selectedBallotId); + const proposalCount = selected?.items?.length ?? 0; + const totalProposalCount = + ballots?.reduce( + (sum, b) => sum + (Array.isArray(b.items) ? b.items.length : 0), + 0, + ) ?? 0; useEffect(() => { const blockchainProvider = getProvider(network); async function get() { const [txHash, certIndex] = id.split(":"); - const proposalData = await blockchainProvider.get( + const proposalData = (await blockchainProvider.get( `/governance/proposals/${txHash}/${certIndex}/metadata`, - ); + )) as ProposalMetadata; if (proposalData) { setProposalMetadata(proposalData); } } - get(); - }, []); + void get(); + }, [id, network]); if (!proposalMetadata) return <>; @@ -76,8 +81,8 @@ export default function WalletGovernanceProposal({ > author.name) + value={(proposalMetadata.json_metadata.authors as { name: string }[]) + .map((author) => author.name) .join(", ")} allowOverflow={true} /> @@ -104,27 +109,34 @@ export default function WalletGovernanceProposal({ { - setManualUtxos(utxos); - setManualSelected(manual); - }} + onSelectionChange={(utxos) => { + setManualUtxos(utxos); + }} /> )} {appWallet && ( - setIsBallotSidebarOpen(true)} /> )} {appWallet && ( - )} diff --git a/src/components/pages/wallet/governance/proposal/voteButtton.tsx b/src/components/pages/wallet/governance/proposal/voteButtton.tsx index b66913fc..10b30228 100644 --- a/src/components/pages/wallet/governance/proposal/voteButtton.tsx +++ b/src/components/pages/wallet/governance/proposal/voteButtton.tsx @@ -40,6 +40,12 @@ interface VoteButtonProps { utxos: UTxO[]; selectedBallotId?: string; proposalTitle?: string; + /** + * Optional handler from the proposal page to open the ballot sidebar. + * When provided, the \"Add proposal to ballot\" button will simply + * open the ballot card instead of mutating ballots directly. + */ + onOpenBallotSidebar?: () => void; } export default function VoteButton({ @@ -50,36 +56,14 @@ export default function VoteButton({ utxos, selectedBallotId, proposalTitle, + onOpenBallotSidebar, }: VoteButtonProps) { - // Use the custom hook for ballots - const { ballots, refresh } = useBallot(appWallet?.id); - const selectedBallot = useMemo(() => { - return ballots?.find((b) => b.id === selectedBallotId); - }, [ballots, selectedBallotId]); - - const proposalIndex = selectedBallot?.items.findIndex((item) => item === proposalId); - const isInBallot = proposalIndex !== undefined && proposalIndex >= 0; - - const addProposalMutation = api.ballot.addProposalToBallot.useMutation({ - onSuccess: () => { - refresh(); - }, - }); - - const removeProposalMutation = api.ballot.removeProposalFromBallot.useMutation({ - onSuccess: () => { - refresh(); - }, - }); + // Use the custom hook for ballots (still used for proxy / context where needed) + const { ballots } = useBallot(appWallet?.id); const drepInfo = useWalletsStore((state) => state.drepInfo); const [loading, setLoading] = useState(false); const [voteKind, setVoteKind] = useState<"Yes" | "No" | "Abstain">("Abstain"); - const [moveModal, setMoveModal] = useState<{ - targetBallotId: string; - conflictBallots: BallotType[]; - } | null>(null); - const [moveLoading, setMoveLoading] = useState(false); const { toast } = useToast(); const setAlert = useSiteStore((state) => state.setAlert); const network = useSiteStore((state) => state.network); @@ -328,112 +312,6 @@ export default function VoteButton({ } } - async function performAddToBallot(targetBallotId: string) { - await addProposalMutation.mutateAsync({ - ballotId: targetBallotId, - itemDescription: proposalTitle ?? description, - item: proposalId, - choice: voteKind, - }); - } - - async function addProposalToBallot() { - if (!selectedBallotId) return; - try { - // Ensure a proposal can only exist on a single ballot at a time. - // If it already exists on a different ballot, open a modal to confirm moving it. - const ballotsWithProposal = - ballots?.filter( - (b) => - b.id !== selectedBallotId && - Array.isArray(b.items) && - b.items.includes(proposalId), - ) ?? []; - - if (ballotsWithProposal.length > 0) { - setMoveModal({ - targetBallotId: selectedBallotId, - conflictBallots: ballotsWithProposal, - }); - return; - } - - await performAddToBallot(selectedBallotId); - toast({ - title: "Added to Ballot", - description: "Proposal successfully added to the ballot.", - duration: 500, - }); - } catch (error) { - toast({ - title: "Failed to Add to Ballot", - description: `Error: ${error}`, - duration: 10000, - variant: "destructive", - }); - } - } - - async function confirmMoveProposal() { - if (!moveModal) return; - - try { - setMoveLoading(true); - - // Remove proposal from all other ballots before adding to the selected one - for (const b of moveModal.conflictBallots) { - const index = b.items.findIndex((item: string) => item === proposalId); - if (index >= 0) { - await removeProposalMutation.mutateAsync({ - ballotId: b.id, - index, - }); - } - } - - await performAddToBallot(moveModal.targetBallotId); - - toast({ - title: "Proposal moved", - description: - "Proposal was moved from the other ballot to the selected ballot.", - duration: 1500, - }); - } catch (error) { - toast({ - title: "Failed to Move Proposal", - description: `Error: ${error}`, - duration: 10000, - variant: "destructive", - }); - } finally { - setMoveLoading(false); - setMoveModal(null); - } - } - - async function removeProposalFromBallot() { - if (!selectedBallotId || proposalIndex === undefined || proposalIndex < 0) return; - try { - await removeProposalMutation.mutateAsync({ - ballotId: selectedBallotId, - index: proposalIndex, - }); - toast({ - title: "Removed from Ballot", - description: "Proposal successfully removed from the ballot.", - duration: 500, - }); - } catch (error) { - toast({ - title: "Failed to Remove from Ballot", - description: `Error: ${error}`, - duration: 10000, - variant: "destructive", - }); - } - } - return (