Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev/gov qa round 1 #19

Merged
merged 5 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading