Skip to content

Commit

Permalink
Dev/gov qa round 1 (#19)
Browse files Browse the repository at this point in the history
* feat: properly display user vote value

* fix: tx confirmation flow fixed. no longer shows out of gas.  refetch properly processes.

* fix: improve modal flow for success/error

* fix: disable voting widget if user is not logged in.

* fix: improve quorum feedback
  • Loading branch information
burnt-sun authored Nov 20, 2024
1 parent 6837815 commit 7881684
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 68 deletions.
2 changes: 1 addition & 1 deletion src/features/core/components/TooltipPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const TooltipPopover: React.FC<TooltipPopoverProps> = ({
{showTooltip && (
<div className="absolute bottom-full left-0 mb-2 w-[200px] rounded-2xl bg-[#1a1a1a] shadow">
<div className="p-4">
<p className="text-sm text-white">{content}</p>
<p className="whitespace-pre-line text-sm text-white">{content}</p>
</div>
</div>
)}
Expand Down
78 changes: 45 additions & 33 deletions src/features/governance/components/ProposalOverview.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useAbstraxionAccount } from "@burnt-labs/abstraxion";
import React from "react";

import type { VoteType } from "../lib/types";
import type { VoteOptionType } from "../lib/types";
import { ProposalStatus } from "../lib/types";
import { formatProposalDate } from "../lib/utils";
import { ProposalStatusPill } from "./ProposalStatusPill";
Expand All @@ -15,14 +16,18 @@ interface ProposalOverviewProps {
status: ProposalStatus;
submittedDate: string;
title: string;
voteValue: undefined | VoteType;
voteValue: undefined | VoteOptionType;
yesPercentage: number;
}

const VoteEmptyState = () => (
interface VoteEmptyStateProps {
reason: "not_connected" | "voting_ended";
}

const VoteEmptyState: React.FC<VoteEmptyStateProps> = ({ reason }) => (
<div className="flex h-[184px] w-[303px] flex-col gap-6">
<div className="font-['Akkurat LL'] text-sm font-bold leading-none text-white">
Voting Period Ended
{reason === "not_connected" ? "Log In to Vote" : "Voting Period Ended"}
</div>

<div className="h-16 w-full rounded-lg border border-[#bdbdbd] hover:cursor-not-allowed">
Expand Down Expand Up @@ -59,37 +64,44 @@ export const ProposalOverview: React.FC<ProposalOverviewProps> = ({
title,
voteValue,
yesPercentage,
}) => (
<div className="rounded-lg bg-white/10 p-6 text-white shadow-lg">
<div className="flex flex-col lg:flex-row lg:justify-between lg:space-x-6">
<div className="flex-grow">
<div className="mb-4">
<ProposalStatusPill status={status} />
<h2 className="font-['Akkurat LL'] mb-[7px] mt-2 text-[32px] font-bold leading-9 text-white">
{title}
</h2>
<p className="font-['Akkurat LL'] text-sm font-normal leading-tight text-white opacity-40">
Proposed {formatProposalDate(submittedDate)}
</p>
</div>
}) => {
const { isConnected } = useAbstraxionAccount();

return (
<div className="rounded-lg bg-white/10 p-6 text-white shadow-lg">
<div className="flex flex-col lg:flex-row lg:justify-between lg:space-x-6">
<div className="flex-grow">
<div className="mb-4">
<ProposalStatusPill status={status} />
<h2 className="font-['Akkurat LL'] mb-[7px] mt-2 text-[32px] font-bold leading-9 text-white">
{title}
</h2>
<p className="font-['Akkurat LL'] text-sm font-normal leading-tight text-white opacity-40">
Proposed {formatProposalDate(submittedDate)}
</p>
</div>

<div className="mt-8">
<ProposalTallyBar
abstainPercentage={abstainPercentage}
noPercentage={noPercentage}
vetoPercentage={noWithVetoPercentage}
yesPercentage={yesPercentage}
/>
<div className="mt-8">
<ProposalTallyBar
abstainPercentage={abstainPercentage}
noPercentage={noPercentage}
vetoPercentage={noWithVetoPercentage}
yesPercentage={yesPercentage}
/>
</div>
</div>
</div>

<div className="mt-8 flex w-full flex-col justify-end lg:mt-0 lg:w-[303px] lg:flex-shrink-0">
{status === ProposalStatus.PROPOSAL_STATUS_VOTING_PERIOD ? (
<VoteWidget proposalId={proposalId} userVote={voteValue} />
) : (
<VoteEmptyState />
)}
<div className="mt-8 flex w-full flex-col justify-end lg:mt-0 lg:w-[303px] lg:flex-shrink-0">
{status === ProposalStatus.PROPOSAL_STATUS_VOTING_PERIOD &&
isConnected ? (
<VoteWidget proposalId={proposalId} userVote={voteValue} />
) : (
<VoteEmptyState
reason={!isConnected ? "not_connected" : "voting_ended"}
/>
)}
</div>
</div>
</div>
</div>
);
);
};
8 changes: 4 additions & 4 deletions src/features/governance/components/ProposalTallyingStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ const QuorumBar = ({
percentage,
quorum,
}: {
percentage: number;
quorum: number;
percentage: number; // percent achieved
quorum: number; // percent required
}) => (
<div className="flex flex-col space-y-2">
{/* top line */}
Expand All @@ -40,7 +40,7 @@ const QuorumBar = ({
className="absolute h-full bg-[#434040]"
style={{
left: `${percentage}%`,
width: `${Math.min(quorum - percentage, 100 - percentage)}%`,
width: `${percentage}%`,
}}
/>
)}
Expand Down Expand Up @@ -183,7 +183,7 @@ export const ProposalTallyingStatus = ({
<div className="w-full max-w-[1119px] rounded-lg bg-white/10 p-4 text-white shadow-lg">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
<TallyCard
infoTooltip={`Minimum participation required for the proposal to be valid. Quorum %: ${quorumPercentage.toFixed(2)}%`}
infoTooltip={`Minimum participation required for the proposal to be valid.\nQuorum Required: ${quorum * 100}%\nQuorum Achieved: ${quorumPercentage.toFixed(2)}%`}
isPositive={quorumReached}
title="Quorum"
value={quorumValue}
Expand Down
70 changes: 53 additions & 17 deletions src/features/governance/components/ProposalVoteModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import CommonModal, {
ModalDescription,
} from "@/features/core/components/common-modal";
import { loader2 } from "@/features/core/lib/icons";

import type { VoteType } from "../lib/types";
import { getReadableVoteType } from "../lib/utils";
Expand All @@ -23,6 +24,7 @@ interface ProposalVoteModalProps {
enum Step {
Completed = "completed",
Review = "review",
Submitting = "submitting",
}

const ProposalVoteModal: React.FC<ProposalVoteModalProps> = ({
Expand All @@ -32,28 +34,29 @@ const ProposalVoteModal: React.FC<ProposalVoteModalProps> = ({
voteType,
}) => {
const [step, setStep] = useState<Step>(Step.Review);
const [isLoading, setIsLoading] = useState(false);

const [error, setError] = useState<null | string>(null);
const readableVoteType = getReadableVoteType(voteType);

// reset step when modal is closed
useEffect(() => {
if (!isOpen) {
setStep(Step.Review);
setError(null);
}
}, [isOpen]);

// handle confirm
const handleConfirm = async () => {
setIsLoading(true);
setStep(Step.Submitting);
setError(null);

try {
await onConfirm();
setStep(Step.Completed);
} catch (error) {
console.error("Error submitting vote:", error);
} finally {
setIsLoading(false);
} catch (err) {
console.error("Error submitting vote:", err);
setError("Failed to submit vote. Please try again.");
setStep(Step.Review);
}
};

Expand All @@ -63,10 +66,10 @@ const ProposalVoteModal: React.FC<ProposalVoteModalProps> = ({
setStep(Step.Review);
};

return (
<CommonModal isOpen={isOpen} onRequestClose={handleClose}>
<div className="min-w-[90vw] md:min-w-[390px]">
{step === Step.Review ? (
const renderContent = () => {
switch (step) {
case Step.Review:
return (
<>
<div className="text-center">
<div className="mb-[35px] text-center uppercase">
Expand All @@ -76,16 +79,44 @@ const ProposalVoteModal: React.FC<ProposalVoteModalProps> = ({
You are about to vote "{readableVoteType}" on this proposal.
Press 'Confirm' to proceed.
</ModalDescription>
{error && <div className="mt-4 text-sm text-danger">{error}</div>}
</div>
<div className="mb-[32px] mt-[32px] flex w-full flex-col items-center justify-center gap-[12px]">
<Heading8>Vote Type</Heading8>
<Heading2>{readableVoteType}</Heading2>
</div>
<Button isLoading={isLoading} onClick={handleConfirm}>
CONFIRM
</Button>
<Button onClick={handleConfirm}>CONFIRM</Button>
</>
);

case Step.Submitting:
return (
<>
<div className="text-center">
<div className="mb-[35px] text-center uppercase">
<HeroText>SUBMITTING</HeroText>
</div>
<ModalDescription>
Please wait while your vote is being submitted...
</ModalDescription>
</div>
<div className="mb-[32px] mt-[32px] flex w-full flex-col items-center justify-center">
<div className="mb-[32px] flex w-[80px] items-center justify-center">
<span
className="animate-spin"
dangerouslySetInnerHTML={{ __html: loader2 }}
/>
</div>
<div className="flex w-full flex-col items-center justify-center gap-[12px]">
<Heading8>Vote Type</Heading8>
<Heading2>{readableVoteType}</Heading2>
</div>
</div>
</>
) : (
);

case Step.Completed:
return (
<>
<div className="text-center">
<div className="mb-[35px] text-center uppercase">
Expand All @@ -102,8 +133,13 @@ const ProposalVoteModal: React.FC<ProposalVoteModalProps> = ({
</div>
<Button onClick={handleClose}>CLOSE</Button>
</>
)}
</div>
);
}
};

return (
<CommonModal isOpen={isOpen} onRequestClose={handleClose}>
<div className="min-w-[90vw] md:min-w-[390px]">{renderContent()}</div>
</CommonModal>
);
};
Expand Down
13 changes: 8 additions & 5 deletions src/features/governance/components/ProposalVoteWidget.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import React, { useEffect, useRef, useState } from "react";

import { useGovernanceTx } from "../context/hooks";
import { VoteType } from "../lib/types";
import {
type VoteOptionType,
VoteType,
getReadableVoteOption,
} from "../lib/types";
import ProposalVoteModal from "./ProposalVoteModal";

const VotePopover: React.FC<{
Expand All @@ -28,7 +32,7 @@ const VotePopover: React.FC<{

interface VoteWidgetProps {
proposalId: string;
userVote: undefined | VoteType;
userVote: undefined | VoteOptionType;
}

export const VoteWidget: React.FC<VoteWidgetProps> = ({
Expand Down Expand Up @@ -84,18 +88,17 @@ export const VoteWidget: React.FC<VoteWidgetProps> = ({
proposalId,
voter: address!,
});

setShowModal(false);
} catch (error) {
console.error("Error submitting vote:", error);
throw error;
}
}
};

return (
<div className="w-full">
<p className="font-['Akkurat LL'] mb-4 text-sm font-bold leading-none text-white">
{userVote ? `You voted {voteValue}` : `Vote for`}
{userVote ? `You Voted ${getReadableVoteOption(userVote)}` : `Vote For`}
</p>
<button
className="font-['Akkurat LL'] mb-4 h-16 w-full rounded-lg border border-[#03c600] bg-[#03c600]/5 text-sm font-normal uppercase leading-tight text-[#03c600]"
Expand Down
2 changes: 1 addition & 1 deletion src/features/governance/context/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,6 @@ export const submitVote = async ({

return await client
.signAndBroadcast(voter, [messageWrapper], fee, memo)
.then(getTxVerifier("vote"))
.then(getTxVerifier("proposal_vote"))
.catch(handleTxError);
};
11 changes: 7 additions & 4 deletions src/features/governance/context/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,12 +284,15 @@ export const useGovernanceTx = () => {
const address = account?.bech32Address;

const voteMutation = useMutation({
mutationFn: (params: ExecuteVoteParams) =>
submitVote({
mutationFn: async (params: ExecuteVoteParams) => {
const result = await submitVote({
...params,
client: client!,
voter: address!,
}),
});

return result;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["vote"] });
queryClient.invalidateQueries({ queryKey: ["tally"] });
Expand All @@ -302,7 +305,7 @@ export const useGovernanceTx = () => {
client: isConnected ? client : undefined,
isConnected,
isVoting: voteMutation.isLoading,
submitVote: voteMutation.mutate,
submitVote: voteMutation.mutateAsync,
voteError: voteMutation.error,
};
};
Loading

0 comments on commit 7881684

Please sign in to comment.