Skip to content

Commit

Permalink
feat: add alerts for redesigned contract interaction confirmation (#2…
Browse files Browse the repository at this point in the history
…5174)

Migrate the existing transaction alerts for contract interaction transactions to the new alert system.
Update the `useCurrentConfirmation` hook to simplify the logic and use deep equal selectors.
Update the alert system to recursively close all the alert modals when clicking an action button.
  • Loading branch information
matthewwalsh0 authored Jun 21, 2024
1 parent 97edb9a commit 7c87e58
Show file tree
Hide file tree
Showing 50 changed files with 2,192 additions and 354 deletions.
57 changes: 57 additions & 0 deletions app/_locales/en/messages.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions test/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ process.env.NOTIFICATIONS_SERVICE_URL =
'https://mock-test-notifications-api.metamask.io';
process.env.PUSH_NOTIFICATIONS_SERVICE_URL =
'https://mock-test-push-notifications-api.metamask.io';
process.env.ENABLE_CONFIRMATION_REDESIGN = 'true';
process.env.PORTFOLIO_URL = 'https://portfolio.test';
19 changes: 16 additions & 3 deletions test/lib/render-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,24 @@ export function renderWithProvider(component, store, pathname = '/') {
};
}

export function renderHookWithProvider(hook, state, pathname = '/') {
export function renderHookWithProvider(hook, state, pathname = '/', Container) {
const store = state ? configureStore(state) : undefined;
const { history, Wrapper } = createProviderWrapper(store, pathname);

const { history, Wrapper: ProviderWrapper } = createProviderWrapper(
store,
pathname,
);

const wrapper = Container
? ({ children }) => (
<ProviderWrapper>
<Container>{children}</Container>
</ProviderWrapper>
)
: ProviderWrapper;

return {
...renderHook(hook, { wrapper: Wrapper }),
...renderHook(hook, { wrapper }),
history,
};
}
Expand Down
36 changes: 29 additions & 7 deletions ui/components/app/alert-system/alert-modal/alert-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export type AlertModalProps = {
/**
* The function to be executed when the modal needs to be closed.
*/
onClose: () => void;
onClose: (request?: { recursive?: boolean }) => void;
/**
* The owner ID of the relevant alert from the `confirmAlerts` reducer.
*/
Expand Down Expand Up @@ -254,9 +254,24 @@ function AcknowledgeButton({
);
}

function ActionButton({ action }: { action?: { key: string; label: string } }) {
function ActionButton({
action,
onClose,
}: {
action?: { key: string; label: string };
onClose: (request: { recursive?: boolean } | void) => void;
}) {
const { processAction } = useAlertActionHandler();

const handleClick = useCallback(() => {
if (!action) {
return;
}

processAction(action.key);
onClose({ recursive: true });
}, [action, onClose, processAction]);

if (!action) {
return null;
}
Expand All @@ -269,7 +284,7 @@ function ActionButton({ action }: { action?: { key: string; label: string } }) {
variant={ButtonVariant.Primary}
width={BlockSize.Full}
size={ButtonSize.Lg}
onClick={() => processAction(key)}
onClick={handleClick}
>
{label}
</Button>
Expand All @@ -290,9 +305,12 @@ export function AlertModal({
}: AlertModalProps) {
const { isAlertConfirmed, setAlertConfirmed, alerts } = useAlerts(ownerId);

const handleClose = useCallback(() => {
onClose();
}, [onClose]);
const handleClose = useCallback(
(...args) => {
onClose(...args);
},
[onClose],
);

const selectedAlert = alerts.find((alert: Alert) => alert.key === alertKey);

Expand Down Expand Up @@ -358,7 +376,11 @@ export function AlertModal({
/>
{(selectedAlert.actions ?? []).map(
(action: { key: string; label: string }) => (
<ActionButton key={action.key} action={action} />
<ActionButton
key={action.key}
action={action}
onClose={handleClose}
/>
),
)}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ describe('ConfirmAlertModal', () => {
const onCancelMock = jest.fn();
const onSubmitMock = jest.fn();
const alertsMock = [
{
key: DATA_ALERT_KEY_MOCK,
field: DATA_ALERT_KEY_MOCK,
severity: Severity.Danger,
message: DATA_ALERT_MESSAGE_MOCK,
},
{
key: FROM_ALERT_KEY_MOCK,
field: FROM_ALERT_KEY_MOCK,
Expand All @@ -25,12 +31,6 @@ describe('ConfirmAlertModal', () => {
reason: 'Reason 1',
alertDetails: ['Detail 1', 'Detail 2'],
},
{
key: DATA_ALERT_KEY_MOCK,
field: DATA_ALERT_KEY_MOCK,
severity: Severity.Danger,
message: DATA_ALERT_MESSAGE_MOCK,
},
];

const STATE_MOCK = {
Expand All @@ -44,11 +44,11 @@ describe('ConfirmAlertModal', () => {
},
},
};

const mockStore = configureMockStore([])(STATE_MOCK);

const defaultProps: ConfirmAlertModalProps = {
ownerId: OWNER_ID_MOCK,
alertKey: FROM_ALERT_KEY_MOCK,
onClose: onCloseMock,
onCancel: onCancelMock,
onSubmit: onSubmitMock,
Expand All @@ -65,7 +65,7 @@ describe('ConfirmAlertModal', () => {

it('disables submit button when confirm modal is not acknowledged', () => {
const { getByTestId } = renderWithProvider(
<ConfirmAlertModal {...defaultProps} alertKey={DATA_ALERT_KEY_MOCK} />,
<ConfirmAlertModal {...defaultProps} />,
mockStore,
);

Expand All @@ -84,7 +84,7 @@ describe('ConfirmAlertModal', () => {

it('calls onSubmit when the button is clicked', () => {
const { getByTestId } = renderWithProvider(
<ConfirmAlertModal {...defaultProps} alertKey={DATA_ALERT_KEY_MOCK} />,
<ConfirmAlertModal {...defaultProps} />,
mockStore,
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ import { AcknowledgeCheckboxBase } from '../alert-modal/alert-modal';
import { MultipleAlertModal } from '../multiple-alert-modal';

export type ConfirmAlertModalProps = {
/** The unique key representing the specific alert field. */
alertKey: string;
/** Callback function that is called when the cancel button is clicked. */
onCancel: () => void;
/** The function to be executed when the modal needs to be closed. */
Expand Down Expand Up @@ -112,7 +110,6 @@ function ConfirmDetails({
}

export function ConfirmAlertModal({
alertKey,
onCancel,
onClose,
onSubmit,
Expand All @@ -121,49 +118,56 @@ export function ConfirmAlertModal({
const t = useI18nContext();
const { alerts, unconfirmedDangerAlerts } = useAlerts(ownerId);

const selectedAlert = alerts.find((alert) => alert.key === alertKey);

const [confirmCheckbox, setConfirmCheckbox] = useState<boolean>(false);

// if there are multiple alerts, show the multiple alert modal
const [multipleAlertModalVisible, setMultipleAlertModalVisible] =
useState<boolean>(unconfirmedDangerAlerts.length > 1);

const handleCloseMultipleAlertModal = useCallback(() => {
setMultipleAlertModalVisible(false);
}, []);
const handleCloseMultipleAlertModal = useCallback(
(request?: { recursive?: boolean }) => {
setMultipleAlertModalVisible(false);

if (request?.recursive) {
onClose();
}
},
[onClose],
);

const handleOpenMultipleAlertModal = useCallback(() => {
setMultipleAlertModalVisible(true);
}, []);

const handleConfirmCheckbox = useCallback(() => {
setConfirmCheckbox(!confirmCheckbox);
}, [confirmCheckbox, selectedAlert]);

if (!selectedAlert) {
return null;
}
}, [confirmCheckbox]);

if (multipleAlertModalVisible) {
return (
<MultipleAlertModal
alertKey={alertKey}
ownerId={ownerId}
onFinalAcknowledgeClick={handleCloseMultipleAlertModal}
onClose={handleCloseMultipleAlertModal}
/>
);
}

const selectedAlert = alerts[0];

if (!selectedAlert) {
return null;
}

return (
<AlertModal
ownerId={ownerId}
onAcknowledgeClick={onClose}
alertKey={alertKey}
alertKey={selectedAlert.key}
onClose={onClose}
customTitle={t('confirmAlertModalTitle')}
customDetails={
selectedAlert?.provider === SecurityProvider.Blockaid ? (
selectedAlert.provider === SecurityProvider.Blockaid ? (
SecurityProvider.Blockaid
) : (
<ConfirmDetails onAlertLinkClick={handleOpenMultipleAlertModal} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ import { Alert } from '../../../../ducks/confirm-alerts/confirm-alerts';

export type MultipleAlertModalProps = {
/** The key of the initial alert to display. */
alertKey: string;
alertKey?: string;
/** The function to be executed when the button in the alert modal is clicked. */
onFinalAcknowledgeClick: () => void;
/** The function to be executed when the modal needs to be closed. */
onClose: () => void;
onClose: (request?: { recursive?: boolean }) => void;
/** The unique identifier of the entity that owns the alert. */
ownerId: string;
};
Expand Down Expand Up @@ -148,8 +148,12 @@ export function MultipleAlertModal({
}: MultipleAlertModalProps) {
const { isAlertConfirmed, alerts } = useAlerts(ownerId);

const initialAlertIndex = alerts.findIndex(
(alert: Alert) => alert.key === alertKey,
);

const [selectedIndex, setSelectedIndex] = useState(
alerts.findIndex((alert: Alert) => alert.key === alertKey),
initialAlertIndex === -1 ? 0 : initialAlertIndex,
);

const selectedAlert = alerts[selectedIndex];
Expand Down
15 changes: 9 additions & 6 deletions ui/components/app/confirm/info/row/alert-row/alert-row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ export const ConfirmInfoAlertRow = ({

const [alertModalVisible, setAlertModalVisible] = useState<boolean>(false);

const handleCloseModal = () => {
const handleModalClose = () => {
setAlertModalVisible(false);
};

const handleOpenModal = () => {
const handleInlineAlertClick = () => {
setAlertModalVisible(true);
};

Expand All @@ -66,18 +66,21 @@ export const ConfirmInfoAlertRow = ({

const inlineAlert = hasFieldAlert ? (
<Box marginLeft={1}>
<InlineAlert onClick={handleOpenModal} severity={selectedAlertSeverity} />
<InlineAlert
onClick={handleInlineAlertClick}
severity={selectedAlertSeverity}
/>
</Box>
) : null;

return (
<>
{alertModalVisible && (
<MultipleAlertModal
alertKey={alertKey}
alertKey={fieldAlerts[0].key}
ownerId={ownerId}
onFinalAcknowledgeClick={handleCloseModal}
onClose={handleCloseModal}
onFinalAcknowledgeClick={handleModalClose}
onClose={handleModalClose}
/>
)}
<ConfirmInfoRow {...confirmInfoRowProps} labelChildren={inlineAlert} />
Expand Down
Loading

0 comments on commit 7c87e58

Please sign in to comment.