From 1aa9989eefc74c8d717fdaca7eb77a5cdb5de920 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Sworze=C5=84?= Date: Fri, 2 Feb 2024 11:48:00 +0100 Subject: [PATCH 1/3] add loading state everywhere is wallet triggering --- .../src/components/atoms/LoadingButton.tsx | 45 ++++++++++------- .../molecules/DashboardActionCard.tsx | 48 +++++++++++-------- .../components/molecules/VoteActionForm.tsx | 14 +++--- .../components/organisms/DashboardCards.tsx | 10 ++-- .../organisms/DelegateTodRepStepOne.tsx | 21 ++++++-- .../organisms/DelegateTodRepStepTwo.tsx | 11 +++-- .../src/hooks/forms/useDelegateTodRepForm.tsx | 2 +- .../src/hooks/forms/useVoteActionForm.tsx | 2 +- 8 files changed, 92 insertions(+), 61 deletions(-) diff --git a/govtool/frontend/src/components/atoms/LoadingButton.tsx b/govtool/frontend/src/components/atoms/LoadingButton.tsx index bd0581f4f..cc89562b8 100644 --- a/govtool/frontend/src/components/atoms/LoadingButton.tsx +++ b/govtool/frontend/src/components/atoms/LoadingButton.tsx @@ -1,24 +1,37 @@ -import { Button, CircularProgress } from '@mui/material'; -import type { ButtonProps } from '@mui/material'; +import { Button, CircularProgress } from "@mui/material"; +import type { ButtonProps, SxProps } from "@mui/material"; -export type ButtonIntent = 'primary' | 'secondary'; -export interface ExtendedButtonProps extends Omit { - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- conflicting event types for form and button event handlers - onClick?: (e?: any) => void; +interface Props extends Omit { + isLoading?: boolean; + size?: "small" | "medium" | "large" | "extraLarge"; + sx?: SxProps; } -interface Props extends ExtendedButtonProps { - isLoading: boolean; -} +export const LoadingButton = ({ + isLoading, + disabled, + children, + size = "large", + sx, + ...rest +}: Props) => { + const buttonHeight = { + extraLarge: 48, + large: 40, + medium: 36, + small: 32, + }[size]; -export const LoadingButton = ({ isLoading, disabled, children, ...rest }: Props) => { return ( - ); -} +}; diff --git a/govtool/frontend/src/components/molecules/DashboardActionCard.tsx b/govtool/frontend/src/components/molecules/DashboardActionCard.tsx index 62708e524..3d3e9fc26 100644 --- a/govtool/frontend/src/components/molecules/DashboardActionCard.tsx +++ b/govtool/frontend/src/components/molecules/DashboardActionCard.tsx @@ -1,54 +1,58 @@ import { Box, ButtonProps, Skeleton } from "@mui/material"; import { FC, ReactNode } from "react"; -import { Button, CopyButton, Typography } from "@atoms"; +import { CopyButton, LoadingButton, Typography } from "@atoms"; import { useScreenDimension } from "@hooks"; import { theme } from "@/theme"; type DashboardActionCardProps = { + cardId?: string; + cardTitle?: string; + dataTestidDelegationStatus?: string; + dataTestidDrepIdBox?: string; + dataTestidFirstButton?: string; + dataTestidSecondButton?: string; description?: ReactNode; firstButtonAction?: () => void; firstButtonDisabled?: boolean; + firstButtonIsLoading?: boolean; firstButtonLabel?: string; firstButtonVariant?: ButtonProps["variant"]; imageHeight?: number; imageURL?: string; imageWidth?: number; + inProgress?: boolean; + isLoading?: boolean; secondButtonAction?: () => void; + secondButtonIsLoading?: boolean; secondButtonLabel?: string; secondButtonVariant?: ButtonProps["variant"]; title?: ReactNode; - cardTitle?: string; - cardId?: string; - inProgress?: boolean; - isLoading?: boolean; - dataTestidFirstButton?: string; - dataTestidSecondButton?: string; - dataTestidDrepIdBox?: string; - dataTestidDelegationStatus?: string; }; export const DashboardActionCard: FC = ({ ...props }) => { const { + cardId, + cardTitle, + dataTestidDrepIdBox, dataTestidFirstButton, dataTestidSecondButton, - dataTestidDrepIdBox, description, firstButtonAction, firstButtonDisabled = false, + firstButtonIsLoading = false, firstButtonLabel, firstButtonVariant = "contained", imageURL, + inProgress, + isLoading = false, secondButtonAction, + secondButtonIsLoading = false, secondButtonLabel, secondButtonVariant = "outlined", title, - cardId, - cardTitle, - inProgress, - isLoading = false, } = props; const { @@ -202,12 +206,13 @@ export const DashboardActionCard: FC = ({ } > {firstButtonLabel ? ( - + ) : null} {secondButtonLabel ? ( - + ) : null} )} diff --git a/govtool/frontend/src/components/molecules/VoteActionForm.tsx b/govtool/frontend/src/components/molecules/VoteActionForm.tsx index 591bf39ec..27a846895 100644 --- a/govtool/frontend/src/components/molecules/VoteActionForm.tsx +++ b/govtool/frontend/src/components/molecules/VoteActionForm.tsx @@ -35,7 +35,7 @@ export const VoteActionForm = ({ isDirty, clearErrors, areFormErrors, - isLoading, + isVoteLoading, } = useVoteActionForm(); useEffect(() => { @@ -80,7 +80,7 @@ export const VoteActionForm = ({ areFormErrors || (!isContext && voteFromEP === vote) } - isLoading={isLoading} + isLoading={isVoteLoading} variant="contained" sx={{ borderRadius: 50, @@ -92,7 +92,7 @@ export const VoteActionForm = ({ Change vote ); - }, [confirmVote, areFormErrors, vote]); + }, [confirmVote, areFormErrors, vote, isVoteLoading]); return ( @@ -253,7 +253,7 @@ export const VoteActionForm = ({ {isMobile ? renderCancelButton : renderChangeVoteButton} ) : ( - + )} ); diff --git a/govtool/frontend/src/components/organisms/DashboardCards.tsx b/govtool/frontend/src/components/organisms/DashboardCards.tsx index e29782299..8c82b8a14 100644 --- a/govtool/frontend/src/components/organisms/DashboardCards.tsx +++ b/govtool/frontend/src/components/organisms/DashboardCards.tsx @@ -28,13 +28,14 @@ export const DashboardCards = () => { useGetAdaHolderCurrentDelegationQuery(stakeKey); const { screenWidth, isMobile } = useScreenDimension(); const { openModal } = useModal(); - const [isLoading, setIsLoading] = useState(false); + const [isRetirementLoading, setIsRetirementLoading] = + useState(false); const { votingPower, powerIsLoading } = useGetAdaHolderVotingPowerQuery(stakeKey); const retireAsDrep = useCallback(async () => { try { - setIsLoading(true); + setIsRetirementLoading(true); const isPendingTx = isPendingTransaction(); if (isPendingTx) return; const certBuilder = await buildDRepRetirementCert(); @@ -59,7 +60,6 @@ export const DashboardCards = () => { } catch (error: any) { const errorMessage = error.info ? error.info : error; - setIsLoading(false); openModal({ type: "statusModal", state: { @@ -71,7 +71,7 @@ export const DashboardCards = () => { }, }); } finally { - setIsLoading(false); + setIsRetirementLoading(false); } }, [buildDRepRetirementCert, buildSignSubmitConwayCertTx]); @@ -328,7 +328,6 @@ export const DashboardCards = () => { } dataTestidDrepIdBox="my-drep-id" firstButtonVariant={dRep?.isRegistered ? "outlined" : "contained"} - firstButtonDisabled={isLoading} secondButtonVariant={ registerTransaction?.transactionHash ? "outlined" @@ -359,6 +358,7 @@ export const DashboardCards = () => { ? retireAsDrep : () => navigateTo(PATHS.registerAsdRep) } + firstButtonIsLoading={isRetirementLoading} firstButtonLabel={ registerTransaction?.transactionHash ? "" diff --git a/govtool/frontend/src/components/organisms/DelegateTodRepStepOne.tsx b/govtool/frontend/src/components/organisms/DelegateTodRepStepOne.tsx index 6febb38ae..95cb0b2f0 100644 --- a/govtool/frontend/src/components/organisms/DelegateTodRepStepOne.tsx +++ b/govtool/frontend/src/components/organisms/DelegateTodRepStepOne.tsx @@ -2,7 +2,7 @@ import { useEffect, useState, useCallback, useMemo } from "react"; import { useNavigate } from "react-router-dom"; import { Box, Grid } from "@mui/material"; -import { ActionRadio, Button, Typography } from "@atoms"; +import { ActionRadio, Button, LoadingButton, Typography } from "@atoms"; import { ICONS, PATHS } from "@consts"; import { useCardano, useModal } from "@context"; import { @@ -31,6 +31,8 @@ export const DelegateTodRepStepOne = ({ setStep }: DelegateProps) => { const { openModal, closeModal } = useModal(); const [areOptions, setAreOptions] = useState(false); const [chosenOption, setChosenOption] = useState(""); + const [isDelegationLoading, setIsDelegationLoading] = + useState(false); const { palette: { boxShadow2 }, } = theme; @@ -86,6 +88,7 @@ export const DelegateTodRepStepOne = ({ setStep }: DelegateProps) => { }, [chosenOption, areOptions]); const delegate = useCallback(async () => { + setIsDelegationLoading(true); try { const certBuilder = await buildVoteDelegationCert(chosenOption); const result = await buildSignSubmitConwayCertTx({ @@ -97,16 +100,19 @@ export const DelegateTodRepStepOne = ({ setStep }: DelegateProps) => { const errorMessage = error.info ? error.info : error; openErrorDelegationModal(errorMessage); + } finally { + setIsDelegationLoading(false); } }, [chosenOption, buildSignSubmitConwayCertTx, buildVoteDelegationCert]); const renderDelegateButton = useMemo(() => { return ( - + ); - }, [chosenOption, dRep?.isRegistered, isMobile, delegate, dRepID]); + }, [ + chosenOption, + delegate, + dRep?.isRegistered, + dRepID, + isDelegationLoading, + isMobile, + ]); const renderCancelButton = useMemo(() => { return ( diff --git a/govtool/frontend/src/components/organisms/DelegateTodRepStepTwo.tsx b/govtool/frontend/src/components/organisms/DelegateTodRepStepTwo.tsx index 6ac689264..03e0cacbf 100644 --- a/govtool/frontend/src/components/organisms/DelegateTodRepStepTwo.tsx +++ b/govtool/frontend/src/components/organisms/DelegateTodRepStepTwo.tsx @@ -1,7 +1,7 @@ import { useMemo } from "react"; import { Box, Link } from "@mui/material"; -import { Button, Input, Typography } from "../atoms"; +import { Button, Input, LoadingButton, Typography } from "../atoms"; import { useScreenDimension, useDelegateTodRepForm } from "@hooks"; import { theme } from "@/theme"; import { openInNewTab } from "@utils"; @@ -17,14 +17,15 @@ export const DelegateTodRepStepTwo = ({ setStep }: DelegateProps) => { palette: { boxShadow2 }, } = theme; - const { control, isDelegateButtonDisabled, delegate } = + const { control, delegate, isDelegateButtonDisabled, isDelegationLoading } = useDelegateTodRepForm(); const renderDelegateButton = useMemo(() => { return ( - + ); - }, [isDelegateButtonDisabled, delegate, isMobile]); + }, [isDelegateButtonDisabled, delegate, isMobile, isDelegationLoading]); const renderBackButton = useMemo(() => { return ( diff --git a/govtool/frontend/src/hooks/forms/useDelegateTodRepForm.tsx b/govtool/frontend/src/hooks/forms/useDelegateTodRepForm.tsx index e4ff1861b..1493d1f34 100644 --- a/govtool/frontend/src/hooks/forms/useDelegateTodRepForm.tsx +++ b/govtool/frontend/src/hooks/forms/useDelegateTodRepForm.tsx @@ -92,6 +92,6 @@ export const useDelegateTodRepForm = () => { isDelegateButtonDisabled, delegate: handleSubmit(delegate), modal, - isLoading, + isDelegationLoading: isLoading, }; }; diff --git a/govtool/frontend/src/hooks/forms/useVoteActionForm.tsx b/govtool/frontend/src/hooks/forms/useVoteActionForm.tsx index 0dc5f768f..bd12705e6 100644 --- a/govtool/frontend/src/hooks/forms/useVoteActionForm.tsx +++ b/govtool/frontend/src/hooks/forms/useVoteActionForm.tsx @@ -124,6 +124,6 @@ export const useVoteActionForm = () => { isDirty, clearErrors, areFormErrors, - isLoading, + isVoteLoading: isLoading, }; }; From 1718fc2aa35e971b1dc1b4251fdf56feb3c02c41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Sworze=C5=84?= Date: Mon, 5 Feb 2024 14:16:33 +0100 Subject: [PATCH 2/3] move render modal from provider to app --- govtool/frontend/src/App.tsx | 20 +++++++++++++++-- .../components/organisms/DashboardCards.tsx | 7 +++++- govtool/frontend/src/context/modal.tsx | 22 +++++-------------- govtool/frontend/src/context/wallet.tsx | 6 +++-- 4 files changed, 33 insertions(+), 22 deletions(-) diff --git a/govtool/frontend/src/App.tsx b/govtool/frontend/src/App.tsx index f781b12de..91dab6356 100644 --- a/govtool/frontend/src/App.tsx +++ b/govtool/frontend/src/App.tsx @@ -1,9 +1,9 @@ import { useCallback, useEffect } from "react"; import { Route, Routes, useNavigate } from "react-router-dom"; -import { ScrollToTop } from "@atoms"; +import { Modal, ScrollToTop } from "@atoms"; import { PATHS } from "@consts"; -import { useCardano } from "@context"; +import { useCardano, useModal } from "@context"; import { DashboardCards, DashboardGovernanceActions, @@ -23,6 +23,7 @@ import { DashboardGovernanceActionsCategory, } from "@pages"; import { + callAll, getItemFromLocalStorage, WALLET_LS_KEY, removeItemFromLocalStorage, @@ -34,6 +35,7 @@ export default function App() { const { enable, setDRep, setIsDrepLoading } = useCardano(); const navigate = useNavigate(); const { data } = useGetDRepInfo(); + const { modal, openModal, modals } = useModal(); useWalletConnectionListener(); @@ -118,6 +120,20 @@ export default function App() { } /> } /> + {modals[modal.type]?.component && ( + + openModal({ type: "none", state: null }) + ) + : undefined + } + > + {modals[modal.type]?.component ?? <>} + + )} ); } diff --git a/govtool/frontend/src/components/organisms/DashboardCards.tsx b/govtool/frontend/src/components/organisms/DashboardCards.tsx index 8c82b8a14..85528c957 100644 --- a/govtool/frontend/src/components/organisms/DashboardCards.tsx +++ b/govtool/frontend/src/components/organisms/DashboardCards.tsx @@ -73,7 +73,12 @@ export const DashboardCards = () => { } finally { setIsRetirementLoading(false); } - }, [buildDRepRetirementCert, buildSignSubmitConwayCertTx]); + }, [ + buildDRepRetirementCert, + buildSignSubmitConwayCertTx, + isPendingTransaction, + openModal, + ]); const delegationDescription = useMemo(() => { const correctAdaRepresentation = ( diff --git a/govtool/frontend/src/context/modal.tsx b/govtool/frontend/src/context/modal.tsx index d8146a167..4d6afdf0a 100644 --- a/govtool/frontend/src/context/modal.tsx +++ b/govtool/frontend/src/context/modal.tsx @@ -1,6 +1,6 @@ import { createContext, useContext, useMemo, useReducer } from "react"; -import { Modal, type MuiModalChildren } from "@atoms"; +import { type MuiModalChildren } from "@atoms"; import { ChooseWalletModal, ExternalLinkModal, @@ -53,7 +53,8 @@ interface ModalState { } interface ModalContext { - modal: ContextModal; + modal: ModalState; + modals: Record; state: T | null; openModal: (modal: Optional, "state">) => void; closeModal: () => void; @@ -74,7 +75,8 @@ function ModalProvider(props: ProviderProps) { const value = useMemo( () => ({ - modal: modals[modal.type], + modals, + modal, state: modal.state, openModal, closeModal: callAll(modals[modal.type]?.onClose, () => @@ -86,20 +88,6 @@ function ModalProvider(props: ProviderProps) { return ( - {modals[modal.type]?.component && ( - - openModal({ type: "none", state: null }) - ) - : undefined - } - > - {modals[modal.type]?.component ?? <>} - - )} {props.children} ); diff --git a/govtool/frontend/src/context/wallet.tsx b/govtool/frontend/src/context/wallet.tsx index 46529d0af..3d15fb593 100644 --- a/govtool/frontend/src/context/wallet.tsx +++ b/govtool/frontend/src/context/wallet.tsx @@ -66,7 +66,7 @@ import { VOTE_TRANSACTION_KEY, checkIsMaintenanceOn, } from "@utils"; -import { getDRepInfo, getEpochParams, getTransactionStatus } from "@services"; +import { getEpochParams, getTransactionStatus } from "@services"; import { setLimitedDelegationInterval, setLimitedRegistrationInterval, @@ -228,8 +228,10 @@ function CardanoProvider(props: Props) { } return false; }, [ - registerTransaction?.transactionHash, + closeModal, delegateTransaction?.transactionHash, + openModal, + registerTransaction?.transactionHash, voteTransaction?.transactionHash, ]); From 1fa6b198f451a0c66b17f9c1694f8a5ac290f8e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Sworze=C5=84?= Date: Mon, 5 Feb 2024 14:31:23 +0100 Subject: [PATCH 3/3] Merge branch 'develop' of https://github.com/IntersectMBO/govtool into fix/75-multiple-instances-of-the-wallet-opening --- govtool/frontend/src/context/index.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/govtool/frontend/src/context/index.tsx b/govtool/frontend/src/context/index.tsx index ecd5e4cff..e8a118ad3 100644 --- a/govtool/frontend/src/context/index.tsx +++ b/govtool/frontend/src/context/index.tsx @@ -8,11 +8,11 @@ interface Props { const ContextProviders = ({ children }: Props) => { return ( - - - {children} - - + + + {children} + + ); };