Skip to content

Commit

Permalink
Enhance Voting Flow and Feedback in Proposal Modal (#149)
Browse files Browse the repository at this point in the history
* add loading spinner to vote button

* handle cast voting error

* disable voting button when is voting still pending
  • Loading branch information
rodrigoncalves authored Aug 29, 2024
1 parent 5c90fb3 commit bc1f87e
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 42 deletions.
16 changes: 9 additions & 7 deletions src/components/Modal/VoteProposalModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface Props {
proposal: any
address: string
votingPower: string
isVoting?: boolean
errorMessage?: string
}

Expand All @@ -24,13 +25,14 @@ export const VoteProposalModal: FC<Props> = ({
proposal,
address,
votingPower,
isVoting,
errorMessage,
}) => {
const [voting, setVoting] = useState<Vote | null>(null)
const [vote, setVoting] = useState<Vote | null>(null)
const handleSubmit = (e: any) => {
e.preventDefault()
if (voting) {
onSubmit(voting)
if (vote) {
onSubmit(vote)
}
}
return (
Expand Down Expand Up @@ -83,7 +85,7 @@ export const VoteProposalModal: FC<Props> = ({
Vote
</Label>
<div className="flex gap-4 mt-2">
{voting === 'for' ? (
{vote === 'for' ? (
<Button
variant="primary"
className="w-1/3 border border-white bg-st-success bg-opacity-10"
Expand All @@ -98,7 +100,7 @@ export const VoteProposalModal: FC<Props> = ({
</Button>
)}

{voting === 'against' ? (
{vote === 'against' ? (
<Button
variant="primary"
className="w-1/3 border border-white bg-st-error bg-opacity-10"
Expand All @@ -118,7 +120,7 @@ export const VoteProposalModal: FC<Props> = ({
</Button>
)}

{voting === 'abstain' ? (
{vote === 'abstain' ? (
<Button
variant="primary"
className="w-1/3 border border-white bg-gray-600"
Expand All @@ -140,7 +142,7 @@ export const VoteProposalModal: FC<Props> = ({
</div>
{errorMessage && <Label className="bg-st-error mt-2 p-4">Error: {errorMessage}</Label>}
<div className="flex justify-center mt-8">
<Button onClick={handleSubmit} disabled={!voting}>
<Button onClick={handleSubmit} disabled={!vote || isVoting} loading={isVoting}>
Submit
</Button>
</div>
Expand Down
1 change: 0 additions & 1 deletion src/lib/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ const treasuryContracts = [

const GovernorAddress = GOVERNOR_ADDRESS
const MulticallAddress = MULTICALL_ADDRESS

const TreasuryAddress = TREASURY_ADDRESS

export {
Expand Down
73 changes: 42 additions & 31 deletions src/pages/proposals/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,24 +79,50 @@ const PageWithProposal = (proposal: PageWithProposal) => {
const { onExecuteProposal, canProposalBeExecuted, proposalEtaHumanDate, isTxHashFromExecuteLoading } =
useExecuteProposal(proposalId)

const cannotCastVote = !isProposalActive || didUserVoteAlready || !doesUserHasEnoughThreshold
const cannotCastVote = !isProposalActive || didUserVoteAlready || !doesUserHasEnoughThreshold || isVoting

const cannotCastVoteReason = useMemo(() => {
if (!isProposalActive) {
return 'This proposal is not active'
}
if (didUserVoteAlready) {
return 'You already voted on this proposal'
}
if (!doesUserHasEnoughThreshold) {
/* eslint-disable quotes */
return "You don't have enough voting power to vote on this proposal"
}
if (isVoting) {
return 'Your vote is being processed'
}
return ''
}, [isProposalActive, didUserVoteAlready, doesUserHasEnoughThreshold, isVoting])

const handleVoting = async (vote: Vote) => {
try {
setErrorVoting('')
await onVote(vote)
setMessage(null)
const txHash = await onVote(vote)
setMessage(TX_MESSAGES.voting.pending)
votingModal.closeModal()
setVote(vote)
submittedModal.openModal()
await waitForTransactionReceipt(config, {
hash: txHash,
})
setMessage(TX_MESSAGES.voting.success)
} catch (err: any) {
if (err?.cause?.code !== 4001) {
setErrorVoting((err as Error).toString())
console.error(err)
setErrorVoting(err.shortMessage || err.toString())
setMessage(TX_MESSAGES.voting.error)
}
}
}

const handleQueuingProposal = async () => {
try {
setMessage(null)
const txHash = await onQueueProposal()
setMessage(TX_MESSAGES.queuing.pending)
await waitForTransactionReceipt(config, {
Expand All @@ -111,6 +137,12 @@ const PageWithProposal = (proposal: PageWithProposal) => {
}
}

const openModal = () => {
setErrorVoting('')
setMessage(null)
votingModal.openModal()
}

// @ts-ignore
return (
<div className="pl-4 grid grid-rows-1 gap-[32px] mb-[100px]">
Expand Down Expand Up @@ -140,20 +172,18 @@ const PageWithProposal = (proposal: PageWithProposal) => {
<>
{cannotCastVote ? (
<Popover
content={cannotCastVoteReason(
!isProposalActive,
didUserVoteAlready,
!doesUserHasEnoughThreshold,
)}
content={
<div className="text-[12px] font-bold mb-1">
<p>{cannotCastVoteReason}</p>
</div>
}
size="small"
trigger="hover"
>
<Button disabled>Vote on chain</Button>
</Popover>
) : (
<Button onClick={votingModal.openModal} loading={isVoting}>
Vote on chain
</Button>
<Button onClick={openModal}>Vote on chain</Button>
)}
</>
)}
Expand Down Expand Up @@ -199,6 +229,7 @@ const PageWithProposal = (proposal: PageWithProposal) => {
proposal={proposal}
address={address}
votingPower={votingPowerAtSnapshot}
isVoting={isVoting}
errorMessage={errorVoting}
/>
)}
Expand Down Expand Up @@ -303,26 +334,6 @@ const BreadcrumbSection: FC<{ title: string }> = ({ title }) => {
)
}

const cannotCastVoteReason = (
isProposalInactive: boolean,
didUserVoteAlready: boolean,
notEnoughVotingPower: boolean,
) => (
<div className="text-[12px] font-bold mb-1">
{isProposalInactive ? (
<p>This proposal is not active</p>
) : (
<>
{didUserVoteAlready ? (
<p>You already voted on this proposal</p>
) : (
notEnoughVotingPower && <p>You don&apos;t have enough voting power to vote on this proposal</p>
)}
</>
)}
</div>
)

interface CalldataRows {
calldatasParsed: CalldataDisplayProps[]
}
Expand Down
6 changes: 3 additions & 3 deletions src/shared/hooks/useVoteOnProposal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ export const useVoteOnProposal = (proposalId: string) => {
})

// If everything passed ok - user can vote
const { writeContractAsync, isPending: isVoting } = useWriteContract()
const onVote = async (vote: Vote) => {
const { writeContractAsync: castVote, isPending: isVoting } = useWriteContract()
const onVote = (vote: Vote) => {
if (!isProposalActive) {
return Promise.reject('The proposal is not active.')
}
if (hasVoted) {
return Promise.reject('The user already voted.')
}
return writeContractAsync({
return castVote({
...DEFAULT_DAO,
functionName: 'castVote',
args: [BigInt(proposalId), VOTES_MAP[vote]],
Expand Down
18 changes: 18 additions & 0 deletions src/shared/txMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,22 @@ export const TX_MESSAGES = {
severity: 'success',
},
},
voting: {
error: {
title: 'Error on voting',
content:
'An unexpected error occurred while trying to vote. Please try again later. If the issue persists, contact support for assistance.',
severity: 'error',
},
pending: {
title: 'Voting in process',
content: 'Your transaction is in progress. It will be visible when the transaction is confirmed.',
severity: 'info',
},
success: {
title: 'Voting successful',
content: 'Voting successful. Your vote has been successfully cast!',
severity: 'success',
},
},
} as const

0 comments on commit bc1f87e

Please sign in to comment.