diff --git a/bun.lockb b/bun.lockb index 08591c86..64ce89ee 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/components/alert/alert-container.tsx b/components/alert/alert-container.tsx new file mode 100644 index 00000000..30a43fd6 --- /dev/null +++ b/components/alert/alert-container.tsx @@ -0,0 +1,36 @@ +import { FC } from "react"; +import { useAlertContext } from "@/context/AlertContext"; +import { AlertCard, AlertVariant } from "@aragon/ods"; +import { IAlert } from "@/utils/types"; + +const AlertContainer: FC = () => { + const { alerts } = useAlertContext(); + + return ( +
+ {alerts.map((alert: IAlert) => ( + + ))} +
+ ); +}; + +function resolveVariant(type: IAlert["type"]) { + let result: AlertVariant; + switch (type) { + case "error": + result = "critical"; + break; + default: + result = type; + } + return result; +} + +export default AlertContainer; diff --git a/components/alert/alerts.tsx b/components/alert/alerts.tsx deleted file mode 100644 index 3dc40153..00000000 --- a/components/alert/alerts.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import Alert from '@/components/alert' -import { useAlertContext } from '@/context/AlertContext' -import { IAlert } from '@/utils/types' -import { FC } from 'react' - -const Alerts: FC = () => { - const { alerts } = useAlertContext() - - return ( - <> - {alerts.map((alert: IAlert) => ( - - ))} - - ) -} - -export default Alerts diff --git a/components/alert/index.tsx b/components/alert/index.tsx deleted file mode 100644 index ee05b8e3..00000000 --- a/components/alert/index.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { AlertContext } from "@/context/AlertContext"; -import React, { useEffect, useContext, useState, useRef } from "react"; -import { IAlert } from "@/utils/types"; -import { useWaitForTransactionReceipt } from "wagmi"; -import { AlertCard } from "@aragon/ods"; - -const ALERT_TIMEOUT = 9 * 1000; - -const Alert: React.FC = ({ message, txHash, id }) => { - const alertContext = useContext(AlertContext); - const removeAlert = alertContext ? alertContext.removeAlert : () => { }; - const [hide, setHide] = useState(false); - const { isSuccess } = useWaitForTransactionReceipt({ - hash: txHash as `0x${string}`, - }); - const timerRef = useRef(null); - - useEffect(() => { - timerRef.current = setTimeout(() => { - setHide(true); - }, ALERT_TIMEOUT); - - return () => { - if (timerRef.current) clearTimeout(timerRef.current); - }; - }, [id, setHide]); - - useEffect(() => { - if (isSuccess) { - removeAlert(id); - setHide(false); - if (timerRef.current) clearTimeout(timerRef.current); - timerRef.current = setTimeout(() => { - setHide(true); - }, ALERT_TIMEOUT); - } - }, [isSuccess, removeAlert]); - - if (hide) return
; - - /** bg-success-50 bg-primary-50 text-success-900 text-primary-900 */ - return ( -
- - -
- ); -}; - -export default Alert; diff --git a/context/AlertContext.tsx b/context/AlertContext.tsx index d559b918..5a027bd2 100644 --- a/context/AlertContext.tsx +++ b/context/AlertContext.tsx @@ -1,29 +1,69 @@ -import React, { createContext, useState, useContext } from 'react'; -import { IAlert } from '@/utils/types' +import React, { createContext, useState, useContext } from "react"; +import { IAlert } from "@/utils/types"; +import { usePublicClient } from "wagmi"; + +const DEFAULT_ALERT_TIMEOUT = 7 * 1000; + +export type AlertOptions = { + type?: "success" | "info" | "error"; + description?: string; + txHash?: string; + timeout?: number; +}; export interface AlertContextProps { alerts: IAlert[]; - addAlert: (message: string, txHash: string) => void; - removeAlert: (id: number) => void; + addAlert: (message: string, alertOptions?: AlertOptions) => void; } -export const AlertContext = createContext(undefined); +export const AlertContext = createContext( + undefined +); -export const AlertProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { +export const AlertProvider: React.FC<{ children: React.ReactNode }> = ({ + children, +}) => { const [alerts, setAlerts] = useState([]); + const client = usePublicClient(); + + // Add a new alert to the list + const addAlert = (message: string, alertOptions?: AlertOptions) => { + // Clean duplicates + const idx = alerts.findIndex((a) => { + if (a.message !== message) return false; + else if (a.description !== alertOptions?.description) return false; + else if (a.type !== alertOptions?.type) return false; + + return true; + }); + if (idx >= 0) removeAlert(idx); + + const newAlert: IAlert = { + id: Date.now(), + message, + description: alertOptions?.description, + type: alertOptions?.type ?? "info", + }; + if (alertOptions?.txHash && client) { + newAlert.explorerLink = + client.chain.blockExplorers?.default.url + "/tx/" + alertOptions.txHash; + } + setAlerts(alerts.concat(newAlert)); - // Function to add a new alert - const addAlert = (message: string, txHash: string) => { - setAlerts([...alerts, { message, txHash, id: Date.now() }]); + // Schedule the clean-up + const timeout = alertOptions?.timeout ?? DEFAULT_ALERT_TIMEOUT; + setTimeout(() => { + removeAlert(newAlert.id); + }, timeout); }; // Function to remove an alert const removeAlert = (id: number) => { - setAlerts(alerts?.filter((alert) => alert.id !== id)); + setAlerts(alerts.filter((alert) => alert.id !== id)); }; return ( - + {children} ); @@ -33,7 +73,7 @@ export const useAlertContext = () => { const context = useContext(AlertContext); if (!context) { - throw new Error('useThemeContext must be used inside the AlertProvider'); + throw new Error("useContext must be used inside the AlertProvider"); } return context; diff --git a/context/index.tsx b/context/index.tsx index 275d3106..cb2a6f35 100644 --- a/context/index.tsx +++ b/context/index.tsx @@ -1,5 +1,4 @@ import { AlertProvider } from "./AlertContext"; -import Alerts from "@/components/alert/alerts"; import { ReactNode } from "react"; import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { config } from "@/context/Web3Modal"; @@ -22,7 +21,6 @@ export function RootContextProvider({ children, initialState }: { children: Reac {children} - diff --git a/pages/_app.tsx b/pages/_app.tsx index 4737ee6d..f357c143 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,5 +1,6 @@ import { RootContextProvider } from "@/context"; import { Layout } from "@/components/layout"; +import AlertContainer from "@/components/alert/alert-container"; import { Manrope } from "next/font/google"; import "@aragon/ods/index.css"; import "@/pages/globals.css"; @@ -22,6 +23,7 @@ export default function AragonetteApp({ Component, pageProps }: any) { +
); diff --git a/plugins/dualGovernance/components/proposal/header.tsx b/plugins/dualGovernance/components/proposal/header.tsx index c77a5e1b..7081b1a9 100644 --- a/plugins/dualGovernance/components/proposal/header.tsx +++ b/plugins/dualGovernance/components/proposal/header.tsx @@ -4,12 +4,13 @@ import { Proposal } from "@/plugins/dualGovernance/utils/types"; import { AlertVariant } from "@aragon/ods"; import { Else, If, IfCase, Then } from "@/components/if"; import { AddressText } from "@/components/text/address"; -import { useWriteContract } from "wagmi"; +import { useWaitForTransactionReceipt, useWriteContract } from "wagmi"; import { OptimisticTokenVotingPluginAbi } from "../../artifacts/OptimisticTokenVotingPlugin.sol"; import { AlertContextProps, useAlertContext } from "@/context/AlertContext"; import { useProposalVariantStatus } from "../../hooks/useProposalVariantStatus"; import { PUB_CHAIN, PUB_DUAL_GOVERNANCE_PLUGIN_ADDRESS } from "@/constants"; import { PleaseWaitSpinner } from "@/components/please-wait"; +import { useRouter } from "next/router"; const DEFAULT_PROPOSAL_TITLE = "(No proposal title)"; @@ -28,23 +29,62 @@ const ProposalHeader: React.FC = ({ transactionLoading, onVetoPressed, }) => { - const { writeContract: executeWrite, data: executeResponse } = useWriteContract() + const { reload } = useRouter(); const { addAlert } = useAlertContext() as AlertContextProps; const proposalVariant = useProposalVariantStatus(proposal); + const { + writeContract: executeWrite, + data: executeTxHash, + error, + status, + } = useWriteContract(); + const { isLoading: isConfirming, isSuccess: isConfirmed } = + useWaitForTransactionReceipt({ hash: executeTxHash }); + const executeButtonPressed = () => { executeWrite({ chainId: PUB_CHAIN.id, abi: OptimisticTokenVotingPluginAbi, address: PUB_DUAL_GOVERNANCE_PLUGIN_ADDRESS, - functionName: 'execute', - args: [proposalNumber] - }) - } + functionName: "execute", + args: [proposalNumber], + }); + }; useEffect(() => { - if (executeResponse) addAlert('Your execution has been submitted', executeResponse) - }, [executeResponse]) + if (status === "idle" || status === "pending") return; + else if (status === "error") { + if (error?.message?.startsWith("User rejected the request")) { + addAlert("Transaction rejected by the user", { + timeout: 4 * 1000, + }); + } else { + console.error(error); + addAlert("Could not execute the proposal", { type: "error" }); + } + return; + } + + // success + if (!executeTxHash) return; + else if (isConfirming) { + addAlert("Proposal submitted", { + description: "Waiting for the transaction to be validated", + type: "info", + txHash: executeTxHash, + }); + return; + } else if (!isConfirmed) return; + + addAlert("Proposal executed", { + description: "The transaction has been validated", + type: "success", + txHash: executeTxHash, + }); + + setTimeout(() => reload(), 1000 * 2); + }, [status, executeTxHash, isConfirming, isConfirmed]); return (
@@ -54,13 +94,13 @@ const ProposalHeader: React.FC = ({ {/** bg-info-200 bg-success-200 bg-critical-200 * text-info-800 text-success-800 text-critical-800 */} -
- -
+
+ +
Proposal {proposalNumber + 1} @@ -76,8 +116,8 @@ const ProposalHeader: React.FC = ({ size="lg" variant="primary" onClick={() => onVetoPressed()} - > - Veto + > + Veto @@ -88,15 +128,15 @@ const ProposalHeader: React.FC = ({ - + + className="flex h-5 items-center" + size="lg" + variant="success" + onClick={() => executeButtonPressed()} + > + Execute + diff --git a/plugins/dualGovernance/pages/new.tsx b/plugins/dualGovernance/pages/new.tsx index 0e79a96e..132dc71b 100644 --- a/plugins/dualGovernance/pages/new.tsx +++ b/plugins/dualGovernance/pages/new.tsx @@ -63,19 +63,31 @@ export default function Create() { useEffect(() => { if (status === "idle" || status === "pending") return; else if (status === "error") { - if (error?.message?.startsWith("User rejected the request")) return; - alert("Could not create the proposal"); + if (error?.message?.startsWith("User rejected the request")) { + addAlert("Transaction rejected by the user", { + timeout: 4 * 1000, + }); + } else { + addAlert("Could not create the proposal", { type: "error" }); + } return; } // success if (!createTxHash) return; else if (isConfirming) { - addAlert("The proposal has been submitted", createTxHash); + addAlert("Proposal submitted", { + description: "Waiting for the transaction to be validated", + txHash: createTxHash, + }); return; } else if (!isConfirmed) return; - addAlert("The proposal has been confirmed", createTxHash); + addAlert("Proposal created", { + description: "The transaction has been validated", + type: "success", + txHash: createTxHash, + }); setTimeout(() => { push("#/"); }, 1000 * 2); @@ -83,11 +95,14 @@ export default function Create() { const submitProposal = async () => { // Check metadata - if (!title.trim()) return alert("Please, enter a title"); + if (!title.trim()) + return addAlert("Please, enter a title", { type: "error" }); const plainSummary = getPlainText(summary).trim(); if (!plainSummary.trim()) - return alert("Please, enter a summary of what the proposal is about"); + return addAlert("Please, enter a summary of what the proposal is about", { + type: "error", + }); // Check the action switch (actionType) { @@ -95,15 +110,17 @@ export default function Create() { break; case ActionType.Withdrawal: if (!actions.length) { - return alert( - "Please ensure that the withdrawal address and the amount to transfer are valid" + return addAlert( + "Please ensure that the withdrawal address and the amount to transfer are valid", + { type: "error" } ); } break; default: if (!actions.length || !actions[0].data || actions[0].data === "0x") { - return alert( - "Please ensure that the values of the action to execute are correct" + return addAlert( + "Please ensure that the values of the action to execute are correct", + { type: "error" } ); } } diff --git a/plugins/dualGovernance/pages/proposal.tsx b/plugins/dualGovernance/pages/proposal.tsx index 46c152e6..e6a612fb 100644 --- a/plugins/dualGovernance/pages/proposal.tsx +++ b/plugins/dualGovernance/pages/proposal.tsx @@ -57,19 +57,31 @@ export default function ProposalDetail({ id: proposalId }: { id: string }) { useEffect(() => { if (status === "idle" || status === "pending") return; else if (status === "error") { - if (error?.message?.startsWith("User rejected the request")) return; - alert("Could not create the proposal"); + if (error?.message?.startsWith("User rejected the request")) { + addAlert("Transaction rejected by the user", { + timeout: 4 * 1000, + }); + } else { + addAlert("Could not create the proposal", { type: "error" }); + } return; } // success if (!vetoTxHash) return; else if (isConfirming) { - addAlert("The veto has been submitted", vetoTxHash); + addAlert("Veto submitted", { + description: "Waiting for the transaction to be validated", + txHash: vetoTxHash, + }); return; } else if (!isConfirmed) return; - // addAlert("The veto has been registered", vetoTxHash); + addAlert("Veto registered", { + description: "The transaction has been validated", + type: "success", + txHash: vetoTxHash, + }); reload(); }, [status, vetoTxHash, isConfirming, isConfirmed]); diff --git a/plugins/tokenVoting/pages/new.tsx b/plugins/tokenVoting/pages/new.tsx index e24aeada..96f2b60e 100644 --- a/plugins/tokenVoting/pages/new.tsx +++ b/plugins/tokenVoting/pages/new.tsx @@ -1,199 +1,263 @@ -import { create } from 'ipfs-http-client'; -import { Button, IconType, Icon, InputText, TextAreaRichText } from '@aragon/ods' -import React, { useEffect, useState } from 'react' -import { uploadToIPFS } from '@/utils/ipfs' -import { useWaitForTransactionReceipt, useWriteContract } from 'wagmi'; -import { toHex } from 'viem' -import { TokenVotingAbi } from '@/plugins/tokenVoting/artifacts/TokenVoting.sol'; -import { useAlertContext } from '@/context/AlertContext'; -import WithdrawalInput from '@/components/input/withdrawal' -import CustomActionInput from '@/components/input/custom-action' -import { Action } from '@/utils/types' -import { getPlainText } from '@/utils/html'; -import { useRouter } from 'next/router'; -import { Else, IfCase, Then } from '@/components/if'; -import { PleaseWaitSpinner } from '@/components/please-wait'; +import { create } from "ipfs-http-client"; import { - PUB_IPFS_API_KEY, - PUB_IPFS_ENDPOINT, - PUB_TOKEN_VOTING_PLUGIN_ADDRESS -} from '@/constants'; + Button, + IconType, + Icon, + InputText, + TextAreaRichText, +} from "@aragon/ods"; +import React, { useEffect, useState } from "react"; +import { uploadToIPFS } from "@/utils/ipfs"; +import { useWaitForTransactionReceipt, useWriteContract } from "wagmi"; +import { toHex } from "viem"; +import { TokenVotingAbi } from "@/plugins/tokenVoting/artifacts/TokenVoting.sol"; +import { useAlertContext } from "@/context/AlertContext"; +import WithdrawalInput from "@/components/input/withdrawal"; +import CustomActionInput from "@/components/input/custom-action"; +import { Action } from "@/utils/types"; +import { getPlainText } from "@/utils/html"; +import { useRouter } from "next/router"; +import { Else, IfCase, Then } from "@/components/if"; +import { PleaseWaitSpinner } from "@/components/please-wait"; +import { + PUB_IPFS_API_KEY, + PUB_IPFS_ENDPOINT, + PUB_TOKEN_VOTING_PLUGIN_ADDRESS, +} from "@/constants"; enum ActionType { - Signaling, - Withdrawal, - Custom + Signaling, + Withdrawal, + Custom, } const ipfsClient = create({ - url: PUB_IPFS_ENDPOINT, - headers: { 'X-API-KEY': PUB_IPFS_API_KEY, 'Accept': 'application/json' } + url: PUB_IPFS_ENDPOINT, + headers: { "X-API-KEY": PUB_IPFS_API_KEY, Accept: "application/json" }, }); export default function Create() { - const { push } = useRouter() - const [title, setTitle] = useState(''); - const [summary, setSummary] = useState(''); - const [actions, setActions] = useState([]); - const { addAlert } = useAlertContext() - const { - writeContract: createProposalWrite, - data: createTxHash, - status, - error - } = useWriteContract(); - const { isLoading: isConfirming, isSuccess: isConfirmed } = - useWaitForTransactionReceipt({ hash: createTxHash }); - const [actionType, setActionType] = useState(ActionType.Signaling) - - const changeActionType = (actionType: ActionType) => { - setActions([]) - setActionType(actionType) + const { push } = useRouter(); + const [title, setTitle] = useState(""); + const [summary, setSummary] = useState(""); + const [actions, setActions] = useState([]); + const { addAlert } = useAlertContext(); + const { + writeContract: createProposalWrite, + data: createTxHash, + status, + error, + } = useWriteContract(); + const { isLoading: isConfirming, isSuccess: isConfirmed } = + useWaitForTransactionReceipt({ hash: createTxHash }); + const [actionType, setActionType] = useState( + ActionType.Signaling + ); + + const changeActionType = (actionType: ActionType) => { + setActions([]); + setActionType(actionType); + }; + + useEffect(() => { + if (status === "idle" || status === "pending") return; + else if (status === "error") { + if (error?.message?.startsWith("User rejected the request")) { + addAlert("Transaction rejected by the user", { + timeout: 4 * 1000, + }); + } else { + addAlert("Could not create the proposal", { type: "error" }); + } + return; } - useEffect(() => { - if (status === "idle" || status === "pending") return; - else if (status === "error") { - if (error?.message?.startsWith("User rejected the request")) return; - alert("Could not create the proposal"); - return; + // success + if (!createTxHash) return; + else if (isConfirming) { + addAlert("Proposal submitted", { + description: "Waiting for the transaction to be validated", + txHash: createTxHash, + }); + return; + } else if (!isConfirmed) return; + + addAlert("Proposal created", { + description: "The transaction has been validated", + type: "success", + txHash: createTxHash, + }); + + setTimeout(() => { + push("#/"); + }, 1000 * 2); + }, [status, createTxHash, isConfirming, isConfirmed]); + + const submitProposal = async () => { + // Check metadata + if (!title.trim()) + return addAlert("Please, enter a title", { type: "error" }); + + const plainSummary = getPlainText(summary).trim(); + if (!plainSummary.trim()) + return addAlert("Please, enter a summary of what the proposal is about", { + type: "error", + }); + + // Check the action + switch (actionType) { + case ActionType.Signaling: + break; + case ActionType.Withdrawal: + if (!actions.length) { + return addAlert( + "Please ensure that the withdrawal address and the amount to transfer are valid", + { type: "error" } + ); } - - // success - if (!createTxHash) return; - else if (isConfirming) { - addAlert("The proposal has been submitted", createTxHash); - return; - } else if (!isConfirmed) return; - - addAlert("The proposal has been confirmed", createTxHash); - setTimeout(() => { - push("#/"); - }, 1000 * 2); - }, [status, createTxHash, isConfirming, isConfirmed]); - - const submitProposal = async () => { - // Check metadata - if (!title.trim()) return alert("Please, enter a title"); - - const plainSummary = getPlainText(summary).trim() - if (!plainSummary.trim()) return alert("Please, enter a summary of what the proposal is about"); - - // Check the action - switch (actionType) { - case ActionType.Signaling: break; - case ActionType.Withdrawal: - if (!actions.length) { - return alert("Please ensure that the withdrawal address and the amount to transfer are valid"); - } - break - default: - if (!actions.length || !actions[0].data || actions[0].data === "0x") { - return alert("Please ensure that the values of the action to execute are correct"); - } + break; + default: + if (!actions.length || !actions[0].data || actions[0].data === "0x") { + return addAlert( + "Please ensure that the values of the action to execute are correct", + { type: "error" } + ); } - - const proposalMetadataJsonObject = { title, summary }; - const blob = new Blob([JSON.stringify(proposalMetadataJsonObject)], { type: 'application/json' }); - - const ipfsPin = await uploadToIPFS(ipfsClient, blob); - createProposalWrite({ - abi: TokenVotingAbi, - address: PUB_TOKEN_VOTING_PLUGIN_ADDRESS, - functionName: 'createProposal', - args: [toHex(ipfsPin), actions, 0, 0, 0, 0, 0], - }) } - const handleTitleInput = (event: React.ChangeEvent) => { - setTitle(event?.target?.value); - }; - - const showLoading = status === "pending" || isConfirming; - - return ( -
-
-

Create Proposal

-
- -
-
- -
-
- Select proposal action -
-
{changeActionType(ActionType.Signaling)}} - className={`rounded-xl border border-solid border-2 bg-neutral-0 hover:bg-neutral-50 flex flex-col items-center cursor-pointer ${actionType === ActionType.Signaling ? 'border-primary-300' : 'border-neutral-100'}`}> - - Signaling -
-
changeActionType(ActionType.Withdrawal)} - className={`rounded-xl border border-solid border-2 bg-neutral-0 hover:bg-neutral-50 flex flex-col items-center cursor-pointer ${actionType === ActionType.Withdrawal ? 'border-primary-300' : 'border-neutral-100'}`}> - - DAO Payment -
-
changeActionType(ActionType.Custom)} - className={`rounded-xl border border-solid border-2 bg-neutral-0 hover:bg-neutral-50 flex flex-col items-center cursor-pointer ${actionType === ActionType.Custom ? 'border-primary-300' : 'border-neutral-100'}`}> - - Custom action -
-
-
- {actionType === ActionType.Withdrawal && ()} - {actionType === ActionType.Custom && ()} -
-
- - - -
- -
-
- - - -
+ const proposalMetadataJsonObject = { title, summary }; + const blob = new Blob([JSON.stringify(proposalMetadataJsonObject)], { + type: "application/json", + }); + + const ipfsPin = await uploadToIPFS(ipfsClient, blob); + createProposalWrite({ + abi: TokenVotingAbi, + address: PUB_TOKEN_VOTING_PLUGIN_ADDRESS, + functionName: "createProposal", + args: [toHex(ipfsPin), actions, 0, 0, 0, 0, 0], + }); + }; + + const handleTitleInput = (event: React.ChangeEvent) => { + setTitle(event?.target?.value); + }; + + const showLoading = status === "pending" || isConfirming; + + return ( +
+
+

+ Create Proposal +

+
+ +
+
+ +
+
+ + Select proposal action + +
+
{ + changeActionType(ActionType.Signaling); + }} + className={`rounded-xl border border-solid border-2 bg-neutral-0 hover:bg-neutral-50 flex flex-col items-center cursor-pointer ${ + actionType === ActionType.Signaling + ? "border-primary-300" + : "border-neutral-100" + }`} + > + + + Signaling +
-
- ) -} +
changeActionType(ActionType.Withdrawal)} + className={`rounded-xl border border-solid border-2 bg-neutral-0 hover:bg-neutral-50 flex flex-col items-center cursor-pointer ${ + actionType === ActionType.Withdrawal + ? "border-primary-300" + : "border-neutral-100" + }`} + > + + + DAO Payment + +
+
changeActionType(ActionType.Custom)} + className={`rounded-xl border border-solid border-2 bg-neutral-0 hover:bg-neutral-50 flex flex-col items-center cursor-pointer ${ + actionType === ActionType.Custom + ? "border-primary-300" + : "border-neutral-100" + }`} + > + + + Custom action + +
+
+
+ {actionType === ActionType.Withdrawal && ( + + )} + {actionType === ActionType.Custom && ( + + )} +
+
+ + +
+ +
+
+ + + +
+ + + ); +} diff --git a/plugins/tokenVoting/pages/proposal.tsx b/plugins/tokenVoting/pages/proposal.tsx index 1f927327..85bf7ca8 100644 --- a/plugins/tokenVoting/pages/proposal.tsx +++ b/plugins/tokenVoting/pages/proposal.tsx @@ -99,19 +99,32 @@ export default function ProposalDetail({ id: proposalId }: { id: string }) { useEffect(() => { if (status === "idle" || status === "pending") return; else if (status === "error") { - if (error?.message?.startsWith("User rejected the request")) return; - alert("Could not create the proposal"); + if (error?.message?.startsWith("User rejected the request")) { + addAlert("Transaction rejected by the user", { + timeout: 4 * 1000, + }); + } else { + addAlert("Could not create the proposal", { type: "error" }); + } return; } // success if (!votingTxHash) return; else if (isConfirming) { - addAlert("The vote has been submitted", votingTxHash); + addAlert("Vote submitted", { + description: "Waiting for the transaction to be validated", + txHash: votingTxHash, + }); return; } else if (!isConfirmed) return; - // addAlert("The vote has been registered", votingTxHash); + addAlert("Vote registered", { + description: "The transaction has been validated", + type: "success", + txHash: votingTxHash, + }); + reload(); }, [status, votingTxHash, isConfirming, isConfirmed]); diff --git a/utils/types.ts b/utils/types.ts index 3a169a4f..be83054a 100644 --- a/utils/types.ts +++ b/utils/types.ts @@ -2,10 +2,12 @@ export type Action = { to: string; value: bigint; data: string; -} +}; export interface IAlert { - message: string; - txHash: string; id: number; + type: "success" | "info" | "error"; + message: string; + description?: string; + explorerLink?: string; }