From e7000002d4e2064977f8cc53281c88d70ee50aba Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Sat, 23 Nov 2024 00:33:12 +0000 Subject: [PATCH 01/29] Add template confirmations to transaction and signature navigation Add Storybook CSS fixes for confirmation templates. Add useConfirmationNavigation hook. Update redesigned and legacy navigation components. Add nav component to template confirmation. Add snap dialog stories. Support template confirmations in useCurrentConfirmation. --- .storybook/index.css | 18 ++ .storybook/preview.js | 1 + ...irm-page-container-navigation.component.js | 77 ++--- .../components/confirm/nav/nav.tsx | 56 +--- .../confirmation/confirmation.js | 277 ++++++++---------- .../stories/snap-dialog.stories.js | 118 ++++++++ .../confirmation/stories/util.js | 10 +- .../confirmations/hooks/syncConfirmPath.ts | 16 +- .../hooks/useConfirmationNavigation.ts | 75 +++++ .../hooks/useCurrentConfirmation.ts | 18 +- ui/pages/confirmations/selectors/confirm.ts | 2 + ui/selectors/approvals.ts | 2 +- 12 files changed, 400 insertions(+), 270 deletions(-) create mode 100644 .storybook/index.css create mode 100644 ui/pages/confirmations/confirmation/stories/snap-dialog.stories.js create mode 100644 ui/pages/confirmations/hooks/useConfirmationNavigation.ts diff --git a/.storybook/index.css b/.storybook/index.css new file mode 100644 index 000000000000..48ebae78fe46 --- /dev/null +++ b/.storybook/index.css @@ -0,0 +1,18 @@ +/* Fixes for any styles that are not compatible with Storybook */ + +.create-snap-account-page, .remove-snap-account-page, .snap-ui-renderer { + width: 100% !important; +} + +.snap-ui-renderer__footer-centered { + position: initial !important; + margin-top: auto !important; +} + +.snap-ui-renderer__container { + padding-bottom: 0 !important; +} + +.snap-ui-renderer__panel { + overflow-y: auto !important; +} diff --git a/.storybook/preview.js b/.storybook/preview.js index 525c364f2072..273afa2468b0 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -21,6 +21,7 @@ import { metamaskStorybookTheme } from './metamask-storybook-theme'; import { DocsContainer } from '@storybook/addon-docs'; import { themes } from '@storybook/theming'; import { AlertMetricsProvider } from '../ui/components/app/alert-system/contexts/alertMetricsContext'; +import './index.css'; // eslint-disable-next-line /* @ts-expect-error: Avoids error from window property not existing */ diff --git a/ui/pages/confirmations/components/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js b/ui/pages/confirmations/components/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js index 2912d8955d52..9b0767375113 100755 --- a/ui/pages/confirmations/components/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js +++ b/ui/pages/confirmations/components/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js @@ -1,67 +1,30 @@ -import React, { useContext } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { useHistory, useParams } from 'react-router-dom'; -import { - unapprovedDecryptMsgsSelector, - unapprovedEncryptionPublicKeyMsgsSelector, - unconfirmedTransactionsListSelector, -} from '../../../../../selectors'; +import React, { useCallback, useContext } from 'react'; +import { useDispatch } from 'react-redux'; +import { useParams } from 'react-router-dom'; import { I18nContext } from '../../../../../contexts/i18n'; -import { - CONFIRM_TRANSACTION_ROUTE, - SIGNATURE_REQUEST_PATH, -} from '../../../../../helpers/constants/routes'; import { clearConfirmTransaction } from '../../../../../ducks/confirm-transaction/confirm-transaction.duck'; import { QueueType } from '../../../../../../shared/constants/metametrics'; import { useQueuedConfirmationsEvent } from '../../../hooks/useQueuedConfirmationEvents'; +import { useConfirmationNavigation } from '../../../hooks/useConfirmationNavigation'; const ConfirmPageContainerNavigation = () => { const t = useContext(I18nContext); const dispatch = useDispatch(); - const history = useHistory(); const { id } = useParams(); + const { count, getIndex, navigateToIndex } = useConfirmationNavigation(); + const currentPosition = getIndex(id); - const unapprovedDecryptMsgs = useSelector(unapprovedDecryptMsgsSelector); - const unapprovedEncryptionPublicKeyMsgs = useSelector( - unapprovedEncryptionPublicKeyMsgsSelector, - ); - const unconfirmedTransactions = - useSelector(unconfirmedTransactionsListSelector) ?? []; - - const enumUnapprovedDecryptMsgsKey = Object.keys(unapprovedDecryptMsgs || {}); - const enumUnapprovedEncryptMsgsKey = Object.keys( - unapprovedEncryptionPublicKeyMsgs || {}, - ); - const enumDecryptAndEncryptMsgs = [ - ...enumUnapprovedDecryptMsgsKey, - ...enumUnapprovedEncryptMsgsKey, - ]; - - const enumUnapprovedTxs = unconfirmedTransactions - .map((tx) => tx.id) - .filter((key) => enumDecryptAndEncryptMsgs.includes(key) === false); - - const currentPosition = enumUnapprovedTxs.indexOf(id); - - const totalTx = enumUnapprovedTxs.length; + const totalTx = count; const positionOfCurrentTx = currentPosition + 1; - const nextTxId = enumUnapprovedTxs[currentPosition + 1]; - const prevTxId = enumUnapprovedTxs[currentPosition - 1]; - const showNavigation = enumUnapprovedTxs.length > 1; - const firstTx = enumUnapprovedTxs[0]; - const lastTx = enumUnapprovedTxs[enumUnapprovedTxs.length - 1]; + const showNavigation = count > 1; - const onNextTx = (txId) => { - if (txId) { + const onNextTx = useCallback( + (index) => { dispatch(clearConfirmTransaction()); - const position = enumUnapprovedTxs.indexOf(txId); - history.push( - unconfirmedTransactions[position]?.msgParams - ? `${CONFIRM_TRANSACTION_ROUTE}/${txId}${SIGNATURE_REQUEST_PATH}` - : `${CONFIRM_TRANSACTION_ROUTE}/${txId}`, - ); - } - }; + navigateToIndex(index); + }, + [dispatch, navigateToIndex], + ); useQueuedConfirmationsEvent(QueueType.NavigationHeader); @@ -76,20 +39,20 @@ const ConfirmPageContainerNavigation = () => { className="confirm-page-container-navigation__container" data-testid="navigation-container" style={{ - visibility: prevTxId ? 'initial' : 'hidden', + visibility: currentPosition > 0 ? 'initial' : 'hidden', }} > @@ -105,20 +68,20 @@ const ConfirmPageContainerNavigation = () => {
diff --git a/ui/pages/confirmations/components/confirm/nav/nav.tsx b/ui/pages/confirmations/components/confirm/nav/nav.tsx index 4a58e1c364dd..0c9f77c5f114 100644 --- a/ui/pages/confirmations/components/confirm/nav/nav.tsx +++ b/ui/pages/confirmations/components/confirm/nav/nav.tsx @@ -1,9 +1,7 @@ import { providerErrors, serializeError } from '@metamask/rpc-errors'; -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { useHistory } from 'react-router-dom'; -import { ApprovalType } from '@metamask/controller-utils'; import { QueueType } from '../../../../../../shared/constants/metametrics'; import { Box, @@ -26,52 +24,26 @@ import { TextColor, TextVariant, } from '../../../../../helpers/constants/design-system'; -import { - CONFIRM_TRANSACTION_ROUTE, - SIGNATURE_REQUEST_PATH, -} from '../../../../../helpers/constants/routes'; import { useI18nContext } from '../../../../../hooks/useI18nContext'; import { pendingConfirmationsSortedSelector } from '../../../../../selectors'; import { rejectPendingApproval } from '../../../../../store/actions'; import { useConfirmContext } from '../../../context/confirm'; import { useQueuedConfirmationsEvent } from '../../../hooks/useQueuedConfirmationEvents'; -import { isCorrectSignatureApprovalType } from '../../../../../../shared/lib/confirmation.utils'; +import { useConfirmationNavigation } from '../../../hooks/useConfirmationNavigation'; const Nav = () => { - const history = useHistory(); const t = useI18nContext(); const dispatch = useDispatch(); - + const { getIndex, navigateToIndex } = useConfirmationNavigation(); const { currentConfirmation } = useConfirmContext(); - const pendingConfirmations = useSelector(pendingConfirmationsSortedSelector); + const position = getIndex(currentConfirmation?.id); - const currentConfirmationPosition = useMemo(() => { - if (pendingConfirmations?.length <= 0 || !currentConfirmation) { - return 0; - } - return pendingConfirmations.findIndex( - ({ id }) => id === currentConfirmation.id, - ); - }, [currentConfirmation, pendingConfirmations]); - - const onNavigateToTransaction = useCallback( - (pos: number) => { - const nextConfirmation = - pendingConfirmations[currentConfirmationPosition + pos]; - // todo: once all signature request pages are ported to new designs - // SIGNATURE_REQUEST_PATH from path below can be removed - // In new routing all confirmations will support - // "/confirm-transaction/" - history.replace( - `${CONFIRM_TRANSACTION_ROUTE}/${nextConfirmation.id}${ - isCorrectSignatureApprovalType(nextConfirmation.type as ApprovalType) - ? SIGNATURE_REQUEST_PATH - : '' - }`, - ); + const onNavigateButtonClick = useCallback( + (change: number) => { + navigateToIndex(position + change); }, - [currentConfirmationPosition, pendingConfirmations], + [position, navigateToIndex], ); const onRejectAll = useCallback(() => { @@ -111,9 +83,9 @@ const Nav = () => { borderRadius={BorderRadius.full} className="confirm_nav__left_btn" color={IconColor.iconAlternative} - disabled={currentConfirmationPosition === 0} + disabled={position === 0} iconName={IconName.ArrowLeft} - onClick={() => onNavigateToTransaction(-1)} + onClick={() => onNavigateButtonClick(-1)} size={ButtonIconSize.Sm} /> { marginInline={2} variant={TextVariant.bodySm} > - {currentConfirmationPosition + 1} of {pendingConfirmations.length} + {position + 1} of {pendingConfirmations.length} { borderRadius={BorderRadius.full} className="confirm_nav__right_btn" color={IconColor.iconAlternative} - disabled={ - currentConfirmationPosition === pendingConfirmations.length - 1 - } + disabled={position === pendingConfirmations.length - 1} iconName={IconName.ArrowRight} - onClick={() => onNavigateToTransaction(1)} + onClick={() => onNavigateButtonClick(1)} size={ButtonIconSize.Sm} /> diff --git a/ui/pages/confirmations/confirmation/confirmation.js b/ui/pages/confirmations/confirmation/confirmation.js index c2d42be40e47..c12dfe71d27f 100644 --- a/ui/pages/confirmations/confirmation/confirmation.js +++ b/ui/pages/confirmations/confirmation/confirmation.js @@ -1,3 +1,4 @@ +/* eslint-disable react/prop-types */ import React, { useCallback, useEffect, @@ -36,7 +37,7 @@ import { } from '../../../selectors'; import { getNetworkConfigurationsByChainId } from '../../../../shared/modules/selectors/networks'; import Callout from '../../../components/ui/callout'; -import { Box, Icon, IconName } from '../../../components/component-library'; +import { Box } from '../../../components/component-library'; import Loading from '../../../components/ui/loading-screen'; import SnapAuthorshipHeader from '../../../components/app/snaps/snap-authorship-header'; import { SnapUIRenderer } from '../../../components/app/snaps/snap-ui-renderer'; @@ -44,10 +45,10 @@ import { SnapUIRenderer } from '../../../components/app/snaps/snap-ui-renderer'; import { SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES } from '../../../../shared/constants/app'; ///: END:ONLY_INCLUDE_IF import { DAY } from '../../../../shared/constants/time'; -import { - BlockSize, - BackgroundColor, -} from '../../../helpers/constants/design-system'; +import { BackgroundColor } from '../../../helpers/constants/design-system'; +import { Nav } from '../components/confirm/nav'; +import { ConfirmContextProvider } from '../context/confirm'; +import { useConfirmationNavigation } from '../hooks/useConfirmationNavigation'; import ConfirmationFooter from './components/confirmation-footer'; import { getTemplateValues, @@ -55,8 +56,7 @@ import { getTemplateState, } from './templates'; -// TODO(rekmarks): This component and all of its sub-components should probably -// be renamed to "Dialog", now that we are using it in that manner. +const SNAP_CUSTOM_UI_DIALOG = Object.values(DIALOG_APPROVAL_TYPES); /** * a very simple reducer using produce from Immer to keep state manipulation @@ -195,6 +195,34 @@ function useTemplateState(pendingConfirmation) { return [templateState]; } +const Header = ({ pendingConfirmation, onCancel }) => { + const { origin } = pendingConfirmation ?? {}; + + const isSnapCustomUIDialog = SNAP_CUSTOM_UI_DIALOG.includes( + pendingConfirmation?.type, + ); + + const hideSnapBranding = useSelector((state) => + getHideSnapBranding(state, origin), + ); + + const { requiresNavigation } = useConfirmationNavigation(); + const requiresSnapHeader = isSnapCustomUIDialog && !hideSnapBranding; + + if (!requiresNavigation && !requiresSnapHeader) { + return null; + } + + return ( + +
+

+ 1 + of + 2 +

+ +
+ + +