Skip to content

Commit 937714e

Browse files
committed
feat: improve loading states
1 parent ad7886f commit 937714e

File tree

6 files changed

+136
-63
lines changed

6 files changed

+136
-63
lines changed

plugins/maciVoting/components/PollCard.tsx

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,40 @@ import { useCallback, useEffect, useMemo, useState } from "react";
33
import { useMaci } from "../hooks/useMaci";
44
import { VoteOption } from "../utils/types";
55
import { PleaseWaitSpinner } from "@/components/please-wait";
6+
import { PUBLIC_MACI_ADDRESS } from "@/constants";
7+
import { getPoll } from "@maci-protocol/sdk/browser";
8+
import { useEthersSigner } from "../hooks/useEthersSigner";
69

710
const PollCard = ({ pollId }: { pollId: bigint }) => {
811
// check if the user joined the poll
912
const { setPollId, onJoinPoll, onVote, isRegistered, hasJoinedPoll, isLoading, error: maciError } = useMaci();
13+
const signer = useEthersSigner();
1014
const [error, setError] = useState<string | undefined>(undefined);
15+
const [voteEnded, setVoteEnded] = useState(false);
16+
17+
const disabled = isLoading || voteEnded;
1118

1219
useEffect(() => {
1320
setError(maciError);
1421
}, [maciError]);
1522

23+
useEffect(() => {
24+
if (!signer) {
25+
return;
26+
}
27+
28+
const checkVoteEnded = async () => {
29+
const poll = await getPoll({
30+
maciAddress: PUBLIC_MACI_ADDRESS,
31+
pollId,
32+
signer,
33+
});
34+
setVoteEnded(Number(poll.endDate) < Date.now() * 1000);
35+
};
36+
37+
checkVoteEnded();
38+
}, [voteEnded, setVoteEnded, pollId, signer]);
39+
1640
useEffect(() => {
1741
setPollId(pollId);
1842
}, [pollId, setPollId]);
@@ -64,25 +88,25 @@ const PollCard = ({ pollId }: { pollId: bigint }) => {
6488
<div className="flex flex-row gap-x-1">
6589
<Button
6690
onClick={() => onClickVote(VoteOption.Yes)}
67-
disabled={isLoading}
91+
disabled={disabled}
6892
size="sm"
69-
variant={isLoading ? "tertiary" : "success"}
93+
variant={disabled ? "tertiary" : "success"}
7094
>
7195
Yes
7296
</Button>
7397
<Button
7498
onClick={() => onClickVote(VoteOption.No)}
75-
disabled={isLoading}
99+
disabled={disabled}
76100
size="sm"
77-
variant={isLoading ? "tertiary" : "critical"}
101+
variant={disabled ? "tertiary" : "critical"}
78102
>
79103
No
80104
</Button>
81105
<Button
82106
onClick={() => onClickVote(VoteOption.Abstain)}
83-
disabled={isLoading}
107+
disabled={disabled}
84108
size="sm"
85-
variant={isLoading ? "tertiary" : "warning"}
109+
variant={disabled ? "tertiary" : "warning"}
86110
>
87111
Abstain
88112
</Button>
Lines changed: 26 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,59 @@
1-
import { useEffect, useMemo } from "react";
1+
import { useMemo } from "react";
22
import { useCoordinator } from "../../hooks/useCoordinator";
33
import { Button } from "@aragon/ods";
4-
import { useAlerts } from "@/context/Alerts";
54
import { PleaseWaitSpinner } from "@/components/please-wait";
5+
import { If } from "@/components/if";
66

77
interface IFinalizeActionProps {
88
pollId: number;
99
}
1010

1111
export const FinalizeAction: React.FC<IFinalizeActionProps> = ({ pollId }) => {
1212
const { finalizeStatus, finalizeProposal } = useCoordinator();
13-
const { addAlert } = useAlerts();
1413

1514
const finalizationMessage = useMemo(() => {
1615
switch (finalizeStatus) {
1716
case "notStarted":
1817
return "";
1918
case "merging":
2019
return <PleaseWaitSpinner fullMessage="Merging poll..." />;
21-
case "merged":
22-
return "The poll has been merged.";
2320
case "proving":
2421
return <PleaseWaitSpinner fullMessage="Generating proofs..." />;
25-
case "proved":
26-
return "The proofs have been generated.";
2722
case "submitting":
2823
return <PleaseWaitSpinner fullMessage="Submitting proofs..." />;
2924
case "submitted":
30-
return "The proofs have been submitted. You can now execute the proposal.";
25+
return "";
3126
default:
3227
return "";
3328
}
3429
}, [finalizeStatus]);
3530

36-
useEffect(() => {
37-
if (finalizeStatus === "notStarted") return;
38-
if (finalizeStatus === "merged") {
39-
addAlert("Votes merged", {
40-
description: "The votes have been merged.",
41-
type: "success",
42-
});
43-
}
44-
if (finalizeStatus === "proved") {
45-
addAlert("Votes proved", {
46-
description: "The votes have been proved.",
47-
type: "success",
48-
});
49-
}
50-
if (finalizeStatus === "submitted") {
51-
addAlert("Votes submitted", {
52-
description: "The votes have been submitted.",
53-
type: "success",
54-
});
55-
}
56-
}, [addAlert, finalizeStatus]);
57-
5831
return (
5932
<div className="overflow-hidden rounded-xl bg-neutral-0 pb-2 shadow-neutral">
60-
<div className="flex flex-col gap-y-2 px-4 py-4 md:gap-y-3 md:px-6 md:py-6">
61-
<div className="flex justify-between gap-x-2 gap-y-2">
62-
<p className="text-xl leading-tight text-neutral-800 md:text-2xl">Finalize Poll</p>
63-
<Button size="md" disabled={finalizeStatus !== "notStarted"} onClick={() => finalizeProposal(pollId)}>
64-
Finalize
65-
</Button>
33+
<If condition={finalizeStatus !== "submitted"}>
34+
<div className="flex flex-col gap-y-2 px-4 py-4 md:gap-y-3 md:px-6 md:py-6">
35+
<div className="flex justify-between gap-x-2 gap-y-2">
36+
<p className="text-xl leading-tight text-neutral-800 md:text-2xl">Finalize Poll</p>
37+
<Button size="md" disabled={finalizeStatus !== "notStarted"} onClick={() => finalizeProposal(pollId)}>
38+
Finalize
39+
</Button>
40+
</div>
41+
<p className="text-base leading-normal text-neutral-500 md:text-lg">
42+
The poll must have ended in order for it to be finalized.
43+
</p>
44+
<p className="text-sm text-info-500">{finalizationMessage}</p>
45+
</div>
46+
</If>
47+
<If condition={finalizeStatus === "submitted"}>
48+
<div className="flex flex-col gap-y-2 px-4 py-4 md:gap-y-3 md:px-6 md:py-6">
49+
<div className="flex justify-between gap-x-2 gap-y-2">
50+
<p className="text-xl leading-tight text-neutral-800 md:text-2xl">Poll Finalized</p>
51+
</div>
52+
<p className="text-base leading-normal text-neutral-500 md:text-lg">
53+
The poll has been finalized. You can now execute the proposal.
54+
</p>
6655
</div>
67-
<p className="text-base leading-normal text-neutral-500 md:text-lg">
68-
The poll must have ended in order for it to be finalized.
69-
</p>
70-
<p className="text-sm text-info-500">{finalizationMessage}</p>
71-
</div>
56+
</If>
7257
</div>
7358
);
7459
};

plugins/maciVoting/contexts/CoordinatorContext.tsx

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ import {
1111
} from "./types";
1212
import { useEthersSigner } from "../hooks/useEthersSigner";
1313
import { toBackendChainFormat } from "../utils/chains";
14+
import { useAlerts } from "@/context/Alerts";
1415

1516
export const CoordinatorContext = createContext<ICoordinatorContextType | undefined>(undefined);
1617

1718
export const CoordinatorProvider = ({ children }: { children: ReactNode }) => {
1819
const [finalizeStatus, setFinalizeStatus] = useState<FinalizeStatus>("notStarted");
1920

2021
const signer = useEthersSigner();
22+
const { addAlert } = useAlerts();
2123

2224
const merge = useCallback(async (pollId: number): Promise<TCoordinatorServiceResult<boolean>> => {
2325
let response: Response;
@@ -72,7 +74,7 @@ export const CoordinatorProvider = ({ children }: { children: ReactNode }) => {
7274
poll: pollId,
7375
maciContractAddress: PUBLIC_MACI_ADDRESS,
7476
mode: EMode.NON_QV,
75-
blocksPerBatch: 20,
77+
blocksPerBatch: 1000,
7678
chain: toBackendChainFormat(PUBLIC_CHAIN_NAME),
7779
}),
7880
});
@@ -189,31 +191,53 @@ export const CoordinatorProvider = ({ children }: { children: ReactNode }) => {
189191
const mergeResult = await merge(pollId);
190192
if (!mergeResult.success) {
191193
setFinalizeStatus("notStarted");
194+
addAlert("Failed to merge", {
195+
description: "Failed to merge. Please try again.",
196+
type: "error",
197+
});
192198
return;
193199
}
194200
}
195-
setFinalizeStatus("merged");
201+
addAlert("Votes merged", {
202+
description: "The votes have been merged.",
203+
type: "success",
204+
});
196205

197206
setFinalizeStatus("proving");
198207
const proveResult = await generateProofs({
199208
pollId,
200209
});
201210
if (!proveResult.success) {
202211
setFinalizeStatus("notStarted");
212+
addAlert("Failed to generate proofs", {
213+
description: "The proofs have not been generated. Please try again.",
214+
type: "error",
215+
});
203216
return;
204217
}
205-
setFinalizeStatus("proved");
218+
addAlert("Votes proved", {
219+
description: "The votes have been proved.",
220+
type: "success",
221+
});
206222

207223
setFinalizeStatus("submitting");
208224
const submitResult = await submit(pollId);
209225
if (!submitResult.success) {
210226
setFinalizeStatus("notStarted");
227+
addAlert("Failed to submit proofs", {
228+
description: "The proofs have not been submitted. Please try again.",
229+
type: "error",
230+
});
211231
return;
212232
}
213233
setFinalizeStatus("submitted");
234+
addAlert("Votes submitted", {
235+
description: "The votes have been submitted.",
236+
type: "success",
237+
});
214238
return;
215239
},
216-
[checkMergeStatus, generateProofs, merge, signer, submit]
240+
[addAlert, checkMergeStatus, generateProofs, merge, signer, submit]
217241
);
218242

219243
const value = useMemo<ICoordinatorContextType>(

plugins/maciVoting/contexts/MaciContext.tsx

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { useEthersSigner } from "../hooks/useEthersSigner";
1919
import { keccak256, stringToHex, type Hex } from "viem";
2020
import { VoteOption } from "../utils/types";
2121
import { useAlerts } from "@/context/Alerts";
22+
import { unixTimestampToDate } from "../utils/formatPollDate";
2223

2324
export const DEFAULT_SG_DATA = "0x0000000000000000000000000000000000000000000000000000000000000000";
2425
export const DEFAULT_IVCP_DATA = "0x0000000000000000000000000000000000000000000000000000000000000000";
@@ -262,27 +263,55 @@ export const MaciProvider = ({ children }: { children: ReactNode }) => {
262263
break;
263264
}
264265

265-
await publish({
266-
publicKey: maciKeypair.publicKey.serialize(),
267-
stateIndex: BigInt(pollStateIndex),
268-
voteOptionIndex,
269-
nonce: 1n,
270-
pollId,
271-
newVoteWeight: 1n,
266+
const poll = await getPoll({
272267
maciAddress: PUBLIC_MACI_ADDRESS,
273-
privateKey: maciKeypair.privateKey.serialize(),
268+
pollId,
274269
signer,
275270
});
271+
if (!poll) {
272+
return;
273+
}
274+
275+
try {
276+
await publish({
277+
publicKey: maciKeypair.publicKey.serialize(),
278+
stateIndex: BigInt(pollStateIndex),
279+
voteOptionIndex,
280+
nonce: 1n,
281+
pollId,
282+
newVoteWeight: 1n,
283+
maciAddress: PUBLIC_MACI_ADDRESS,
284+
privateKey: maciKeypair.privateKey.serialize(),
285+
signer,
286+
});
287+
} catch (error: any) {
288+
let message: string | undefined;
289+
if (error.message.includes("0xa47dcd48")) {
290+
const endDate = poll.endDate;
291+
message = `The voting period finished at ${unixTimestampToDate(endDate)}. You can no longer submit a vote.`;
292+
}
293+
if (error.message.includes("0x256eadc8")) {
294+
const startDate = poll.startDate;
295+
message = `The voting period has not begun. It will start at ${unixTimestampToDate(startDate)}`;
296+
}
297+
298+
setIsLoading(false);
299+
setError(message ?? "There was an error submitting your vote");
300+
addAlert("Failure submitting vote", {
301+
description: message ?? "Error submitting vote",
302+
type: "error",
303+
});
304+
return;
305+
}
276306

277307
setIsLoading(false);
278308
setError(undefined);
279-
280309
addAlert("Vote submitted", {
281310
description: "Your vote is in the ballot. You can submit another vote to override it.",
282311
type: "success",
283312
});
284313
},
285-
[addAlert, hasJoinedPoll, maciKeypair, pollId, signer, pollStateIndex]
314+
[signer, maciKeypair, pollId, pollStateIndex, hasJoinedPoll, addAlert]
286315
);
287316

288317
// check if user is connected

plugins/maciVoting/contexts/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { type Keypair } from "@maci-protocol/domainobjs";
2-
import { IProof, ITallyData } from "@maci-protocol/sdk/browser";
2+
import { type IProof, type ITallyData } from "@maci-protocol/sdk/browser";
33
import { type VoteOption } from "../utils/types";
44

55
export interface IVoteArgs {
@@ -18,7 +18,7 @@ export interface IGenerateProofsArgs {
1818
pollId: number;
1919
}
2020

21-
export type FinalizeStatus = "notStarted" | "merging" | "merged" | "proving" | "proved" | "submitting" | "submitted";
21+
export type FinalizeStatus = "notStarted" | "merging" | "proving" | "submitting" | "submitted";
2222

2323
export interface ICoordinatorContextType {
2424
finalizeStatus: FinalizeStatus;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { type BigNumberish } from "ethers";
2+
3+
export function unixTimestampToDate(timestamp: BigNumberish) {
4+
return new Date(Number(timestamp) * 1000).toLocaleString(undefined, {
5+
year: "numeric",
6+
month: "numeric",
7+
day: "numeric",
8+
hour: "2-digit",
9+
minute: "2-digit",
10+
});
11+
}

0 commit comments

Comments
 (0)