From d8b9ad1a63cd26ab68714a7a83a0e09c132c3569 Mon Sep 17 00:00:00 2001 From: iamacook Date: Tue, 27 Jun 2023 15:41:33 +0200 Subject: [PATCH 1/6] feat: show alert before closing transaction flow --- src/components/safe-apps/AppFrame/index.tsx | 1 - .../safe-messages/SignMsgButton/index.tsx | 2 +- .../common/TxFlowExitWarning/index.tsx | 46 ++++++++++++ .../flows/SignMessage/SignMessage.test.tsx | 63 ++--------------- .../tx-flow/flows/SignMessage/SignMessage.tsx | 70 ++----------------- src/components/tx-flow/index.tsx | 42 +++++++---- 6 files changed, 90 insertions(+), 134 deletions(-) create mode 100644 src/components/tx-flow/common/TxFlowExitWarning/index.tsx diff --git a/src/components/safe-apps/AppFrame/index.tsx b/src/components/safe-apps/AppFrame/index.tsx index c30d60e9c8..a16eba2f65 100644 --- a/src/components/safe-apps/AppFrame/index.tsx +++ b/src/components/safe-apps/AppFrame/index.tsx @@ -130,7 +130,6 @@ const AppFrame = ({ appUrl, allowedFeaturesList }: AppFrameProps): ReactElement if (signOffChain) { setTxFlow( setTxFlow(undefined)} logoUri={safeAppFromManifest?.iconUrl || ''} name={safeAppFromManifest?.name || ''} message={message} diff --git a/src/components/safe-messages/SignMsgButton/index.tsx b/src/components/safe-messages/SignMsgButton/index.tsx index d2e183e2f2..9f5339ef4a 100644 --- a/src/components/safe-messages/SignMsgButton/index.tsx +++ b/src/components/safe-messages/SignMsgButton/index.tsx @@ -20,7 +20,7 @@ const SignMsgButton = ({ msg, compact = false }: { msg: SafeMessage; compact?: b const onClick = (e: SyntheticEvent) => { e.stopPropagation() - setTxFlow( setTxFlow(undefined)} {...msg} />) + setTxFlow() } const isDisabled = !isSignable || isPending diff --git a/src/components/tx-flow/common/TxFlowExitWarning/index.tsx b/src/components/tx-flow/common/TxFlowExitWarning/index.tsx new file mode 100644 index 0000000000..3572900c96 --- /dev/null +++ b/src/components/tx-flow/common/TxFlowExitWarning/index.tsx @@ -0,0 +1,46 @@ +import { DialogContent, DialogContentText, DialogActions, Button, SvgIcon } from '@mui/material' +import type { ReactElement } from 'react' + +import ModalDialog from '@/components/common/ModalDialog' +import AlertIcon from '@/public/images/notifications/alert.svg' + +export const TxFlowExitWarning = ({ + open, + onCancel, + onClose, +}: { + open: boolean + onCancel: () => void + onClose: () => void +}): ReactElement => { + return ( + + {' '} + Are you sure? + + } + > + + + If you leave this page, the current progress will be lost. + + + + + + + + ) +} diff --git a/src/components/tx-flow/flows/SignMessage/SignMessage.test.tsx b/src/components/tx-flow/flows/SignMessage/SignMessage.test.tsx index a6579cb8bc..e11fb881c2 100644 --- a/src/components/tx-flow/flows/SignMessage/SignMessage.test.tsx +++ b/src/components/tx-flow/flows/SignMessage/SignMessage.test.tsx @@ -99,7 +99,6 @@ describe('SignMessage', () => { logoUri="www.fake.com/test.png" name="Test App" message={hexlify(toUtf8Bytes(EXAMPLE_MESSAGE))} - onClose={jest.fn} />, ) @@ -108,13 +107,7 @@ describe('SignMessage', () => { it('displays the SafeMessage message', () => { const { getByText } = render( - , + , ) expect(getByText('0xaa05af77f274774b8bdc7b61d98bc40da523dc2821fdea555f4d6aa413199bcc')).toBeInTheDocument() @@ -122,13 +115,7 @@ describe('SignMessage', () => { it('generates the SafeMessage hash if not provided', () => { const { getByText } = render( - , + , ) expect(getByText('0x73d0948ac608c5d00a6dd26dd396cce79b459307ea365f5a5bd5d3119c2d9708')).toBeInTheDocument() @@ -176,13 +163,7 @@ describe('SignMessage', () => { it('renders the message', () => { const { getByText } = render( - , + , ) Object.keys(EXAMPLE_MESSAGE.message).forEach((key) => { @@ -194,13 +175,7 @@ describe('SignMessage', () => { it('displays the SafeMessage message', () => { const { getByText } = render( - , + , ) expect(getByText('0xd5ffe9f6faa9cc9294673fb161b1c7b3e0c98241e90a38fc6c451941f577fb19')).toBeInTheDocument() @@ -208,13 +183,7 @@ describe('SignMessage', () => { it('generates the SafeMessage hash if not provided', () => { const { getByText } = render( - , + , ) expect(getByText('0x10c926c4f417e445de3fddc7ad8c864f81b9c81881b88eba646015de10d21613')).toBeInTheDocument() @@ -233,7 +202,6 @@ describe('SignMessage', () => { name="Test App" message="Hello world!" requestId="123" - onClose={jest.fn} safeAppId={25} />, ) @@ -300,13 +268,7 @@ describe('SignMessage', () => { jest.spyOn(useSafeMessages, 'useSafeMessage').mockReturnValue(msg) const { getByText } = render( - , + , ) await act(async () => { @@ -348,7 +310,6 @@ describe('SignMessage', () => { name="Test App" message="Hello world!" requestId="123" - onClose={jest.fn} safeAppId={25} />, ) @@ -370,7 +331,6 @@ describe('SignMessage', () => { name="Test App" message="Hello world!" requestId="123" - onClose={jest.fn} safeAppId={25} />, ) @@ -396,7 +356,6 @@ describe('SignMessage', () => { name="Test App" message="Hello world!" requestId="123" - onClose={jest.fn} safeAppId={25} />, ) @@ -445,13 +404,7 @@ describe('SignMessage', () => { jest.spyOn(useSafeMessages, 'useSafeMessage').mockReturnValue(msg) const { getByText } = render( - , + , ) await waitFor(() => { @@ -484,7 +437,6 @@ describe('SignMessage', () => { name="Test App" message="Hello world!" requestId="123" - onClose={jest.fn} safeAppId={25} />, ) @@ -526,7 +478,6 @@ describe('SignMessage', () => { name="Test App" message="Hello world!" requestId="123" - onClose={jest.fn} safeAppId={25} />, ) diff --git a/src/components/tx-flow/flows/SignMessage/SignMessage.tsx b/src/components/tx-flow/flows/SignMessage/SignMessage.tsx index b9f9f29991..464146c6df 100644 --- a/src/components/tx-flow/flows/SignMessage/SignMessage.tsx +++ b/src/components/tx-flow/flows/SignMessage/SignMessage.tsx @@ -1,19 +1,6 @@ -import { - Grid, - DialogActions, - Button, - Box, - Typography, - DialogContent, - SvgIcon, - Dialog, - DialogTitle, - DialogContentText, - CardContent, - CardActions, -} from '@mui/material' +import { Grid, Button, Box, Typography, SvgIcon, CardContent, CardActions } from '@mui/material' import { useTheme } from '@mui/material/styles' -import { useCallback, useState } from 'react' +import { useContext } from 'react' import { SafeMessageListItemType, SafeMessageStatus } from '@safe-global/safe-gateway-typescript-sdk' import type { ReactElement } from 'react' import type { SafeMessage } from '@safe-global/safe-gateway-typescript-sdk' @@ -27,7 +14,7 @@ import ErrorMessage from '@/components/tx/ErrorMessage' import useWallet from '@/hooks/wallets/useWallet' import { useSafeMessage } from '@/hooks/messages/useSafeMessages' import useOnboard, { switchWallet } from '@/hooks/wallets/useOnboard' - +import { TxModalContext } from '@/components/tx-flow' import CopyButton from '@/components/common/CopyButton' import { WrongChainWarning } from '@/components/tx/WrongChainWarning' import MsgSigners from '@/components/safe-messages/MsgSigners' @@ -129,39 +116,7 @@ const AlreadySignedByOwnerMessage = ({ hasSigned }: { hasSigned: boolean }) => { ) } -const ConfirmationDialog = ({ - open, - onCancel, - onClose, -}: { - open: boolean - onCancel: () => void - onClose: () => void -}) => ( - - Cancel message signing request - - - If you close this modal, the signing request will be aborted. - - - - - - - -) - -type BaseProps = { - onClose: () => void -} & Pick +type BaseProps = Pick // Custom Safe Apps do not have a `safeAppId` export type ProposeProps = BaseProps & { @@ -175,9 +130,9 @@ export type ConfirmProps = BaseProps & { requestId?: RequestId } -const SignMessage = ({ onClose, message, safeAppId, requestId }: ProposeProps | ConfirmProps): ReactElement => { +const SignMessage = ({ message, safeAppId, requestId }: ProposeProps | ConfirmProps): ReactElement => { // Hooks & variables - const [showCloseTooltip, setShowCloseTooltip] = useState(false) + const { onClose } = useContext(TxModalContext) const { palette } = useTheme() const { safe } = useSafeInfo() const isOwner = useIsSafeOwner() @@ -203,15 +158,6 @@ const SignMessage = ({ onClose, message, safeAppId, requestId }: ProposeProps | onClose, ) - const handleClose = useCallback(() => { - if (requestId && (!ongoingMessage || ongoingMessage.status === SafeMessageStatus.NEEDS_CONFIRMATION)) { - // If we are in a Safe app modal we want to keep the modal open - setShowCloseTooltip(true) - } else { - onClose() - } - }, [onClose, ongoingMessage, requestId]) - return ( <> @@ -255,15 +201,11 @@ const SignMessage = ({ onClose, message, safeAppId, requestId }: ProposeProps | - {/* TODO: Remove this Cancel button once we can figure out how to move the logic outside */} - - - setShowCloseTooltip(false)} onClose={onClose} /> ) diff --git a/src/components/tx-flow/index.tsx b/src/components/tx-flow/index.tsx index 875286e0b1..504d63fcfc 100644 --- a/src/components/tx-flow/index.tsx +++ b/src/components/tx-flow/index.tsx @@ -1,6 +1,7 @@ import { createContext, type ReactElement, type ReactNode, useState, useEffect, useCallback } from 'react' import TxModalDialog from '@/components/common/TxModalDialog' import { useRouter } from 'next/router' +import { TxFlowExitWarning } from '@/components/tx-flow/common/TxFlowExitWarning' const noop = () => {} @@ -18,16 +19,26 @@ export const TxModalContext = createContext({ export const TxModalProvider = ({ children }: { children: ReactNode }): ReactElement => { const [txFlow, setFlow] = useState(undefined) + const [showWarning, setShowWarning] = useState(false) const [onClose, setOnClose] = useState(noop) const router = useRouter() - const handleModalClose = useCallback(() => { + const handleShowWarning = useCallback(() => { + setShowWarning(true) + }, [setShowWarning]) + + const handleCloseWarning = useCallback(() => { + setShowWarning(false) + }, [setShowWarning]) + + const handleExitFlow = useCallback(() => { setOnClose((prevOnClose) => { prevOnClose?.() return noop }) + handleCloseWarning() setFlow(undefined) - }, [setFlow, setOnClose]) + }, [handleCloseWarning, setFlow, setOnClose]) const setTxFlow = useCallback( (txFlow: TxModalContextType['txFlow'], onClose?: () => void) => { @@ -37,19 +48,26 @@ export const TxModalProvider = ({ children }: { children: ReactNode }): ReactEle [setFlow, setOnClose], ) - // Close the modal if user navigates + // Show the modal if user navigates useEffect(() => { - router.events.on('routeChangeComplete', handleModalClose) - return () => router.events.off('routeChangeComplete', handleModalClose) - }, [router, handleModalClose]) + if (!txFlow) { + return + } + router.events.on('routeChangeStart', handleShowWarning) + return () => router.events.off('routeChangeStart', handleShowWarning) + }, [txFlow, router, handleShowWarning]) return ( - - {children} + <> + + {children} + + + {txFlow} + + - - {txFlow} - - + + ) } From d0f8544367251803c78e208c313f5d6f7464e0c5 Mon Sep 17 00:00:00 2001 From: iamacook Date: Tue, 27 Jun 2023 15:45:44 +0200 Subject: [PATCH 2/6] fix: dark mode font colour --- src/components/tx-flow/common/TxFlowExitWarning/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/tx-flow/common/TxFlowExitWarning/index.tsx b/src/components/tx-flow/common/TxFlowExitWarning/index.tsx index 3572900c96..d17cc78071 100644 --- a/src/components/tx-flow/common/TxFlowExitWarning/index.tsx +++ b/src/components/tx-flow/common/TxFlowExitWarning/index.tsx @@ -3,6 +3,7 @@ import type { ReactElement } from 'react' import ModalDialog from '@/components/common/ModalDialog' import AlertIcon from '@/public/images/notifications/alert.svg' +import { useDarkMode } from '@/hooks/useDarkMode' export const TxFlowExitWarning = ({ open, @@ -13,6 +14,7 @@ export const TxFlowExitWarning = ({ onCancel: () => void onClose: () => void }): ReactElement => { + const isDarkMode = useDarkMode() return ( - + If you leave this page, the current progress will be lost. From cb4d625435f2aee9645127a2ab3aa9c0fd9e62b0 Mon Sep 17 00:00:00 2001 From: iamacook Date: Tue, 27 Jun 2023 15:47:28 +0200 Subject: [PATCH 3/6] fix: handle back button --- src/components/tx-flow/index.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/tx-flow/index.tsx b/src/components/tx-flow/index.tsx index 504d63fcfc..0fd5fae233 100644 --- a/src/components/tx-flow/index.tsx +++ b/src/components/tx-flow/index.tsx @@ -53,8 +53,13 @@ export const TxModalProvider = ({ children }: { children: ReactNode }): ReactEle if (!txFlow) { return } - router.events.on('routeChangeStart', handleShowWarning) - return () => router.events.off('routeChangeStart', handleShowWarning) + + router.events.on('beforeHistoryChange', handleShowWarning) // Back button + router.events.on('routeChangeStart', handleShowWarning) // Navigation + return () => { + router.events.off('beforeHistoryChange', handleShowWarning) + router.events.off('routeChangeStart', handleShowWarning) + } }, [txFlow, router, handleShowWarning]) return ( From 61273ede9ecc4700647e20134770121a5fc8cd79 Mon Sep 17 00:00:00 2001 From: iamacook Date: Tue, 27 Jun 2023 16:57:07 +0200 Subject: [PATCH 4/6] fix: change text --- src/components/tx-flow/common/TxFlowExitWarning/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/tx-flow/common/TxFlowExitWarning/index.tsx b/src/components/tx-flow/common/TxFlowExitWarning/index.tsx index d17cc78071..6e4febbd2e 100644 --- a/src/components/tx-flow/common/TxFlowExitWarning/index.tsx +++ b/src/components/tx-flow/common/TxFlowExitWarning/index.tsx @@ -34,7 +34,7 @@ export const TxFlowExitWarning = ({ > - If you leave this page, the current progress will be lost. + Leaving this page will result in the loss of all progress. From 90127fbbde7becdaf23856025f73c53a9495fa08 Mon Sep 17 00:00:00 2001 From: iamacook Date: Tue, 27 Jun 2023 17:20:14 +0200 Subject: [PATCH 5/6] fix: change text --- src/components/tx-flow/common/TxFlowExitWarning/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/tx-flow/common/TxFlowExitWarning/index.tsx b/src/components/tx-flow/common/TxFlowExitWarning/index.tsx index 6e4febbd2e..de4c7ed002 100644 --- a/src/components/tx-flow/common/TxFlowExitWarning/index.tsx +++ b/src/components/tx-flow/common/TxFlowExitWarning/index.tsx @@ -34,7 +34,7 @@ export const TxFlowExitWarning = ({ > - Leaving this page will result in the loss of all progress. + Closing this window will discard your current progress. From fc79877e5827823acf3ba9a236c112b0df62394e Mon Sep 17 00:00:00 2001 From: iamacook Date: Wed, 28 Jun 2023 09:38:25 +0200 Subject: [PATCH 6/6] fix: unmount on final sig + close warning --- .../tx-flow/flows/SignMessage/SignMessage.tsx | 4 ++-- src/components/tx-flow/index.tsx | 15 ++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/components/tx-flow/flows/SignMessage/SignMessage.tsx b/src/components/tx-flow/flows/SignMessage/SignMessage.tsx index 464146c6df..4df39d5b2a 100644 --- a/src/components/tx-flow/flows/SignMessage/SignMessage.tsx +++ b/src/components/tx-flow/flows/SignMessage/SignMessage.tsx @@ -132,7 +132,7 @@ export type ConfirmProps = BaseProps & { const SignMessage = ({ message, safeAppId, requestId }: ProposeProps | ConfirmProps): ReactElement => { // Hooks & variables - const { onClose } = useContext(TxModalContext) + const { setTxFlow } = useContext(TxModalContext) const { palette } = useTheme() const { safe } = useSafeInfo() const isOwner = useIsSafeOwner() @@ -155,7 +155,7 @@ const SignMessage = ({ message, safeAppId, requestId }: ProposeProps | ConfirmPr safeMessageHash, requestId, safeAppId, - onClose, + () => setTxFlow(undefined), ) return ( diff --git a/src/components/tx-flow/index.tsx b/src/components/tx-flow/index.tsx index 0fd5fae233..f513c070ca 100644 --- a/src/components/tx-flow/index.tsx +++ b/src/components/tx-flow/index.tsx @@ -8,19 +8,17 @@ const noop = () => {} type TxModalContextType = { txFlow: ReactNode | undefined setTxFlow: (txFlow: TxModalContextType['txFlow'], onClose?: () => void) => void - onClose: () => void } export const TxModalContext = createContext({ txFlow: undefined, setTxFlow: noop, - onClose: noop, }) export const TxModalProvider = ({ children }: { children: ReactNode }): ReactElement => { const [txFlow, setFlow] = useState(undefined) const [showWarning, setShowWarning] = useState(false) - const [onClose, setOnClose] = useState(noop) + const [, setOnClose] = useState[1]>(noop) const router = useRouter() const handleShowWarning = useCallback(() => { @@ -31,7 +29,7 @@ export const TxModalProvider = ({ children }: { children: ReactNode }): ReactEle setShowWarning(false) }, [setShowWarning]) - const handleExitFlow = useCallback(() => { + const handleModalClose = useCallback(() => { setOnClose((prevOnClose) => { prevOnClose?.() return noop @@ -42,10 +40,13 @@ export const TxModalProvider = ({ children }: { children: ReactNode }): ReactEle const setTxFlow = useCallback( (txFlow: TxModalContextType['txFlow'], onClose?: () => void) => { + if (!txFlow) { + handleCloseWarning() + } setFlow(txFlow) setOnClose(() => onClose ?? noop) }, - [setFlow, setOnClose], + [setFlow, setOnClose, handleCloseWarning], ) // Show the modal if user navigates @@ -64,7 +65,7 @@ export const TxModalProvider = ({ children }: { children: ReactNode }): ReactEle return ( <> - + {children} @@ -72,7 +73,7 @@ export const TxModalProvider = ({ children }: { children: ReactNode }): ReactEle - + ) }