diff --git a/src/app/api/funding-rounds/[id]/consideration-proposals/route.ts b/src/app/api/funding-rounds/[id]/consideration-proposals/route.ts index 6ea26ef..b89c0ca 100644 --- a/src/app/api/funding-rounds/[id]/consideration-proposals/route.ts +++ b/src/app/api/funding-rounds/[id]/consideration-proposals/route.ts @@ -32,7 +32,8 @@ function parseOCVVoteData(data: JsonValue | null | undefined): OCVVoteData { elegible: Boolean(voteData.elegible), votes: Array.isArray(voteData.votes) ? voteData.votes.map(vote => ({ account: String(vote.account || ''), - timestamp: Number(vote.timestamp || 0) + timestamp: Number(vote.timestamp || 0), + hash: String(vote.hash || '') })) : [] }; } @@ -145,7 +146,8 @@ export async function GET( isEligible: ocvVotes.elegible || false, voters: ocvVotes.votes?.map((v: OCVVote) => ({ address: v.account, - timestamp: v.timestamp + timestamp: v.timestamp, + hash: v.hash })) || [] }, reviewerEligible: approved >= minReviewerApprovals, diff --git a/src/components/ConsiderationProposalList.tsx b/src/components/ConsiderationProposalList.tsx index 97caa84..e22e638 100644 --- a/src/components/ConsiderationProposalList.tsx +++ b/src/components/ConsiderationProposalList.tsx @@ -300,8 +300,16 @@ export function ConsiderationProposalList({ fundingRoundId, fundingRoundName }: if (!proposal.isReviewerEligible) { return (
- - + +
); } @@ -329,8 +337,16 @@ export function ConsiderationProposalList({ fundingRoundId, fundingRoundName }: if (!proposal.isReviewerEligible) { return (
- - + +
); } diff --git a/src/components/web3/OCVVoteButton.tsx b/src/components/web3/OCVVoteButton.tsx index 36ec550..6d845b4 100644 --- a/src/components/web3/OCVVoteButton.tsx +++ b/src/components/web3/OCVVoteButton.tsx @@ -1,6 +1,6 @@ "use client" -import { useState } from "react" +import { useState, useMemo } from "react" import { Button } from "@/components/ui/button" import { useWallet } from "@/contexts/WalletContext" import { WalletConnectorDialog } from "./WalletConnectorDialog" @@ -8,18 +8,45 @@ import { ManualVoteDialog } from "./dialogs/OCVManualInstructions" import { OCVTransactionDialog } from "./dialogs/OCVTransactionDialog" import { Icons } from "@/components/icons" import { TARGET_NETWORK } from "@/contexts/WalletContext" +import { + Tooltip, + TooltipContent, + TooltipTrigger, + TooltipProvider +} from "@/components/ui/tooltip" +import { formatDistanceToNow } from "date-fns" +import type { ConsiderationVoteStats } from "@/types/consideration" interface OCVVoteButtonProps { proposalId: string useWallet?: boolean + voteStats: ConsiderationVoteStats } -export function OCVVoteButton({ proposalId, useWallet: isWalletEnabled = true }: OCVVoteButtonProps) { +// Add type for voter +interface Voter { + address: string + timestamp: number + hash: string +} + +export function OCVVoteButton({ + proposalId, + useWallet: isWalletEnabled = true, + voteStats +}: OCVVoteButtonProps) { const { state, enforceTargetNetwork } = useWallet() const [showWalletDialog, setShowWalletDialog] = useState(false) const [showManualDialog, setShowManualDialog] = useState(false) const [showTransactionDialog, setShowTransactionDialog] = useState(false) + const existingVote = useMemo(() => { + if (!state.wallet?.address) return null + return voteStats.communityVotes.voters.find( + (voter: Voter) => voter.address.toLowerCase() === state.wallet!.address.toLowerCase() + ) + }, [voteStats.communityVotes.voters, state.wallet]) + const handleVoteClick = async () => { if (!isWalletEnabled) { setShowManualDialog(true) @@ -43,20 +70,50 @@ export function OCVVoteButton({ proposalId, useWallet: isWalletEnabled = true }: const buttonText = isWalletEnabled ? state.wallet - ? "Vote with Wallet" + ? existingVote + ? "Already Voted" + : "Vote with Wallet" : "Connect Wallet to Vote" : "Vote Without Wallet" + const walletButton = ( + + ) + return ( <> - + {isWalletEnabled && state.wallet && existingVote ? ( + + + +
{walletButton}
+
+ +
+

You have already voted on this proposal

+
+

Time: {formatDistanceToNow(existingVote.timestamp)} ago

+

Transaction: {existingVote.hash}

+
+
+
+
+
+ ) : ( + walletButton + )} {state.wallet && ( diff --git a/src/components/web3/dialogs/OCVManualInstructions.tsx b/src/components/web3/dialogs/OCVManualInstructions.tsx index 55bf9e5..f9c08f2 100644 --- a/src/components/web3/dialogs/OCVManualInstructions.tsx +++ b/src/components/web3/dialogs/OCVManualInstructions.tsx @@ -10,19 +10,27 @@ import { DialogTitle, } from "@/components/ui/dialog" import { cn } from "@/lib/utils" +import { Alert, AlertDescription } from "@/components/ui/alert" +import { formatDistanceToNow } from "date-fns" interface ManualVoteDialogProps { open: boolean onOpenChange: (open: boolean) => void voteId: string voteType: "YES" | "NO" + existingVote?: { + address: string + timestamp: number + hash: string + } | null } export function ManualVoteDialog({ open, onOpenChange, voteId, - voteType + voteType, + existingVote }: ManualVoteDialogProps) { const [copied, setCopied] = useState(false) const memo = `${voteType} ${voteId}` @@ -45,21 +53,47 @@ export function ManualVoteDialog({ How do I cast my vote? -
+ +
+ {existingVote && ( + svg]:text-orange-900 dark:[&>svg]:text-orange-200" + )} + > + +
+

+ Your connected wallet has already voted on this proposal +

+
+

Time: {formatDistanceToNow(existingVote.timestamp)} ago

+

+ Transaction: {existingVote.hash} +

+
+
+
+
+ )} +
-

+

Send a transaction to yourself with the following text in the memo field:

-
- {memo} +
+ {memo}
-
-

Instructions:

-
    +
    +

    Instructions:

    +
    1. Copy the memo shown above
    2. Use your preferred wallet (or a CLI) to create a transaction
    3. Set the recipient address to your own address
    4. @@ -83,8 +117,12 @@ export function ManualVoteDialog({
    5. Set the memo field to the copied text
    6. Send the transaction to cast your vote
    -

    - Note: Make sure to follow the exact format of the memo to ensure your vote is properly recorded. The transaction must be sent to your own address, and the amount can be 0 MINA. +

    + +
    +

    + Note: Make sure to follow the exact format of the memo to ensure your vote is properly recorded. + The transaction must be sent to your own address, and the amount can be 0 MINA.

diff --git a/src/types/consideration.ts b/src/types/consideration.ts index bd0ce37..ac8a2cd 100644 --- a/src/types/consideration.ts +++ b/src/types/consideration.ts @@ -12,6 +12,7 @@ export interface ConsiderationVoteStats { voters: Array<{ address: string; timestamp: number; + hash: string; }>; }; reviewerEligible: boolean; @@ -39,6 +40,7 @@ export interface ConsiderationProposal { export interface OCVVote { account: string; timestamp: number; + hash: string; } export interface OCVVoteData {