diff --git a/app/actions/alert/index.js b/app/actions/alert/index.js deleted file mode 100644 index 42afeee9c5f..00000000000 --- a/app/actions/alert/index.js +++ /dev/null @@ -1,15 +0,0 @@ -export function dismissAlert() { - return { - type: 'HIDE_ALERT', - }; -} - -export function showAlert({ isVisible, autodismiss, content, data }) { - return { - type: 'SHOW_ALERT', - isVisible, - autodismiss, - content, - data, - }; -} diff --git a/app/components/Nav/Main/index.js b/app/components/Nav/Main/index.js index 49a02bef5d9..4282296f6c3 100644 --- a/app/components/Nav/Main/index.js +++ b/app/components/Nav/Main/index.js @@ -16,7 +16,6 @@ import { import NetInfo from '@react-native-community/netinfo'; import PropTypes from 'prop-types'; import { connect, useSelector } from 'react-redux'; -import GlobalAlert from '../../UI/GlobalAlert'; import NotificationManager from '../../../core/NotificationManager'; import Engine from '../../../core/Engine'; import AppConstants from '../../../core/AppConstants'; @@ -401,7 +400,6 @@ const Main = (props) => { ) : ( renderLoader() )} - diff --git a/app/components/Nav/Main/index.test.tsx b/app/components/Nav/Main/index.test.tsx index e2f71a48558..a5f622cfff2 100644 --- a/app/components/Nav/Main/index.test.tsx +++ b/app/components/Nav/Main/index.test.tsx @@ -32,11 +32,6 @@ jest.mock('./MainNavigator', () => { }; }); -jest.mock( - '../../UI/GlobalAlert', - () => () => mockReact.createElement('GlobalAlertMock'), -); - jest.mock( '../../UI/FadeOutOverlay', () => () => mockReact.createElement('FadeOutOverlayMock'), diff --git a/app/components/UI/AccountOverview/index.js b/app/components/UI/AccountOverview/index.js index 7262f48ec29..351ac4c9c13 100644 --- a/app/components/UI/AccountOverview/index.js +++ b/app/components/UI/AccountOverview/index.js @@ -12,8 +12,8 @@ import { connect } from 'react-redux'; import { strings } from '../../../../locales/i18n'; import { AccountOverviewSelectorsIDs } from './AccountOverview.testIds'; import { WalletViewSelectorsIDs } from '../../Views/Wallet/WalletView.testIds'; -import { showAlert } from '../../../actions/alert'; import { protectWalletModalVisible } from '../../../actions/user'; +import { toast, ToastSeverity } from '@metamask/design-system-react-native'; import Routes from '../../../constants/navigation/Routes'; import ClipboardManager from '../../../core/ClipboardManager'; import { fontStyles } from '../../../styles/common'; @@ -162,10 +162,6 @@ class AccountOverview extends PureComponent { * Object that represents the selected account */ account: PropTypes.object, - /** - /* Triggers global alert - */ - showAlert: PropTypes.func, /** * Used to get child ref */ @@ -278,11 +274,9 @@ class AccountOverview extends PureComponent { copyAccountToClipboard = async () => { const { selectedAddress } = this.props; await ClipboardManager.setString(selectedAddress); - this.props.showAlert({ - isVisible: true, - autodismiss: 1500, - content: 'clipboard-alert', - data: { msg: strings('account_details.account_copied_to_clipboard') }, + toast({ + description: strings('account_details.account_copied_to_clipboard'), + severity: ToastSeverity.Success, }); setTimeout(() => this.props.protectWalletModalVisible(), 2000); @@ -458,7 +452,6 @@ const mapStateToProps = (state) => ({ }); const mapDispatchToProps = (dispatch) => ({ - showAlert: (config) => dispatch(showAlert(config)), protectWalletModalVisible: () => dispatch(protectWalletModalVisible()), }); diff --git a/app/components/UI/GlobalAlert/index.js b/app/components/UI/GlobalAlert/index.js deleted file mode 100644 index 0541552bdda..00000000000 --- a/app/components/UI/GlobalAlert/index.js +++ /dev/null @@ -1,156 +0,0 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; -import Modal from 'react-native-modal'; -import { StyleSheet, View, Text } from 'react-native'; -import { dismissAlert } from '../../../actions/alert'; -import { connect } from 'react-redux'; -import { fontStyles } from '../../../styles/common'; -import Icon from 'react-native-vector-icons/FontAwesome'; -import ElevatedView from 'react-native-elevated-view'; -import { ThemeContext, mockTheme } from '../../../util/theme'; - -const createStyles = (colors) => - StyleSheet.create({ - modal: { - margin: 0, - width: '100%', - }, - copyAlert: (width) => ({ - width: width || 180, - backgroundColor: colors.overlay.alternative, - padding: 20, - paddingTop: 30, - alignSelf: 'center', - alignItems: 'center', - justifyContent: 'center', - borderRadius: 8, - }), - copyAlertIcon: { - marginBottom: 20, - }, - copyAlertText: { - textAlign: 'center', - color: colors.overlay.inverse, - fontSize: 16, - ...fontStyles.normal, - }, - }); - -/** - * Wrapper component for a global alert - * connected to redux - */ -class GlobalAlert extends PureComponent { - static propTypes = { - /** - * Boolean that determines if the modal should be shown - */ - isVisible: PropTypes.bool.isRequired, - /** - * Number that determines when it should be autodismissed (in miliseconds) - */ - autodismiss: PropTypes.number, - /** - * Children component(s) - */ - content: PropTypes.any, - /** - * Object with data required to render the content - */ - data: PropTypes.object, - /** - * function that dismisses de modal - */ - dismissAlert: PropTypes.func, - }; - - onClose = () => { - this.props.dismissAlert(); - }; - - componentDidUpdate(prevProps) { - if ( - this.props.autodismiss && - !isNaN(this.props.autodismiss) && - !prevProps.isVisible && - this.props.isVisible - ) { - setTimeout(() => { - this.props.dismissAlert(); - }, this.props.autodismiss); - } - } - - getComponent(content) { - switch (content) { - case 'clipboard-alert': - return this.renderClipboardAlert(); - default: - return ; - } - } - - getStyles = () => { - const colors = this.context.colors || mockTheme.colors; - return createStyles(colors); - }; - - renderClipboardAlert = () => { - const colors = this.context.colors || mockTheme.colors; - const styles = this.getStyles(colors); - - return ( - - - - - - {this.props.data && this.props.data.msg} - - - ); - }; - - render = () => { - const { content, isVisible } = this.props; - const colors = this.context.colors || mockTheme.colors; - const styles = this.getStyles(colors); - - return ( - - {this.getComponent(content)} - - ); - }; -} - -const mapStateToProps = (state) => ({ - isVisible: state.alert.isVisible, - autodismiss: state.alert.autodismiss, - content: state.alert.content, - data: state.alert.data, -}); - -const mapDispatchToProps = (dispatch) => ({ - dismissAlert: () => dispatch(dismissAlert()), -}); - -GlobalAlert.contextType = ThemeContext; - -export default connect(mapStateToProps, mapDispatchToProps)(GlobalAlert); diff --git a/app/components/UI/Transactions/index.js b/app/components/UI/Transactions/index.js index fa0e03f60b9..67b2cceddc2 100644 --- a/app/components/UI/Transactions/index.js +++ b/app/components/UI/Transactions/index.js @@ -14,7 +14,6 @@ import { import { connect } from 'react-redux'; import { ActivitiesViewSelectorsIDs } from '../../Views/ActivityView/ActivitiesView.testIds'; import { strings } from '../../../../locales/i18n'; -import { showAlert } from '../../../actions/alert'; import ExtendedKeyringTypes from '../../../constants/keyringTypes'; import { NO_RPC_BLOCK_EXPLORER, RPC } from '../../../constants/network'; import Engine from '../../../core/Engine'; @@ -1053,10 +1052,6 @@ const mapStateToProps = (state) => ({ Transactions.contextType = ThemeContext; -const mapDispatchToProps = (dispatch) => ({ - showAlert: (config) => dispatch(showAlert(config)), -}); - export { Transactions as UnconnectedTransactions }; const TransactionsWithHardwareWallet = (props) => { @@ -1065,7 +1060,6 @@ const TransactionsWithHardwareWallet = (props) => { return ; }; -export default connect( - mapStateToProps, - mapDispatchToProps, -)(withQRHardwareAwareness(TransactionsWithHardwareWallet)); +export default connect(mapStateToProps)( + withQRHardwareAwareness(TransactionsWithHardwareWallet), +); diff --git a/app/components/UI/Transactions/index.test.tsx b/app/components/UI/Transactions/index.test.tsx index 74bdd2ec3c2..9b6d73d5053 100644 --- a/app/components/UI/Transactions/index.test.tsx +++ b/app/components/UI/Transactions/index.test.tsx @@ -290,7 +290,6 @@ const defaultTestProps = { collectibleContracts: [], tokens: {}, navigation: mockNavigation, - showAlert: jest.fn(), gasFeeEstimates: { medium: { suggestedMaxFeePerGas: '20' }, }, diff --git a/app/components/Views/MultichainAccounts/AddressList/AddressList.tsx b/app/components/Views/MultichainAccounts/AddressList/AddressList.tsx index 9436cf97bc0..b0f78074af0 100644 --- a/app/components/Views/MultichainAccounts/AddressList/AddressList.tsx +++ b/app/components/Views/MultichainAccounts/AddressList/AddressList.tsx @@ -10,7 +10,7 @@ import { selectInternalAccountListSpreadByScopesByGroupId, selectInternalAccountsByGroupId, } from '../../../../selectors/multichainAccounts/accounts'; -import { IconName, Toaster, toast } from '@metamask/design-system-react-native'; +import { IconName, toast } from '@metamask/design-system-react-native'; import MultichainAddressRow, { MULTICHAIN_ADDRESS_ROW_QR_BUTTON_TEST_ID, } from '../../../../component-library/components-temp/MultichainAccounts/MultichainAddressRow'; @@ -156,7 +156,6 @@ export const AddressList = () => { renderItem={renderAddressItem} onLoad={onLoad} /> - ); }; diff --git a/app/components/Views/MultichainAccounts/MultichainAccountConnect/MultichainAccountConnect.tsx b/app/components/Views/MultichainAccounts/MultichainAccountConnect/MultichainAccountConnect.tsx index 809a8c11548..136f46f4ddc 100644 --- a/app/components/Views/MultichainAccounts/MultichainAccountConnect/MultichainAccountConnect.tsx +++ b/app/components/Views/MultichainAccounts/MultichainAccountConnect/MultichainAccountConnect.tsx @@ -17,7 +17,6 @@ import { AvatarFavicon, AvatarFaviconSize, Box, - Toaster, toast, } from '@metamask/design-system-react-native'; import { USER_INTENT } from '../../../../constants/permissions.ts'; @@ -1002,7 +1001,6 @@ const MultichainAccountConnect = (props: AccountConnectProps) => { )} {renderPhishingModal()} - ); }; diff --git a/app/components/Views/MultichainAccounts/PrivateKeyList/PrivateKeyList.tsx b/app/components/Views/MultichainAccounts/PrivateKeyList/PrivateKeyList.tsx index 1a262cd4c6e..4c8a0579d8d 100644 --- a/app/components/Views/MultichainAccounts/PrivateKeyList/PrivateKeyList.tsx +++ b/app/components/Views/MultichainAccounts/PrivateKeyList/PrivateKeyList.tsx @@ -25,7 +25,6 @@ import { Button, ButtonVariant, ButtonSize, - Toaster, toast, } from '@metamask/design-system-react-native'; import Engine from '../../../../core/Engine'; @@ -326,7 +325,6 @@ export const PrivateKeyList = () => { /> {reveal ? renderPrivateKeyList() : renderPassword()} - ); }; diff --git a/app/components/Views/NftDetails/NftDetails.tsx b/app/components/Views/NftDetails/NftDetails.tsx index b14eedf8553..d54b8876bb0 100644 --- a/app/components/Views/NftDetails/NftDetails.tsx +++ b/app/components/Views/NftDetails/NftDetails.tsx @@ -19,6 +19,8 @@ import { ButtonVariant, HeaderStandard, IconName as DSIconName, + toast, + ToastSeverity, } from '@metamask/design-system-react-native'; import NftDetailsBox from './NftDetailsBox'; import NftDetailsInformationRow from './NftDetailsInformationRow'; @@ -28,8 +30,7 @@ import Icon, { IconSize, } from '../../../component-library/components/Icons/Icon'; import ClipboardManager from '../../../core/ClipboardManager'; -import { useDispatch, useSelector } from 'react-redux'; -import { showAlert } from '../../../actions/alert'; +import { useSelector } from 'react-redux'; import { strings } from '../../../../locales/i18n'; import { selectChainId, @@ -61,7 +62,6 @@ const NftDetails = () => { const navigation = useNavigation(); const { collectible, source } = useParams(); const chainId = useSelector(selectChainId); - const dispatch = useDispatch(); const currentCurrency = useSelector(selectCurrentCurrency); const ticker = useSelector(selectEvmTicker); const { trackEvent, createEventBuilder } = useAnalytics(); @@ -135,14 +135,10 @@ const NftDetails = () => { return; } await ClipboardManager.setString(address); - dispatch( - showAlert({ - isVisible: true, - autodismiss: 1500, - content: 'clipboard-alert', - data: { msg: strings('detected_tokens.address_copied_to_clipboard') }, - }), - ); + toast({ + description: strings('detected_tokens.address_copied_to_clipboard'), + severity: ToastSeverity.Success, + }); }; const blockExplorerTokenLink = () => diff --git a/app/components/Views/Notifications/Details/index.test.tsx b/app/components/Views/Notifications/Details/index.test.tsx index 9c06705918a..200468f4f18 100644 --- a/app/components/Views/Notifications/Details/index.test.tsx +++ b/app/components/Views/Notifications/Details/index.test.tsx @@ -34,10 +34,6 @@ const mockInitialState = { }, }; -jest.mock('../../../../actions/alert', () => ({ - showAlert: jest.fn(), -})); - jest.mock('@react-navigation/native'); describe('NotificationsDetails', () => { const mockStore = configureMockStore(); diff --git a/app/components/Views/Root/index.tsx b/app/components/Views/Root/index.tsx index 0f087ffac6a..023a8548070 100644 --- a/app/components/Views/Root/index.tsx +++ b/app/components/Views/Root/index.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useRef, useState } from 'react'; -import { StyleSheet } from 'react-native'; +import { StyleSheet, View } from 'react-native'; import { Provider as ReduxProvider } from 'react-redux'; import { PersistGate } from 'redux-persist/lib/integration/react'; import { store, persistor } from '../../../store'; @@ -28,6 +28,7 @@ import { QueryClientProvider } from '@tanstack/react-query'; import reactQueryService from '../../../core/ReactQueryService'; import { HardwareWalletProvider } from '../../../core/HardwareWallet'; import { UIMessengerProvider } from '../../../contexts/ui-messenger'; +import { Toaster } from '@metamask/design-system-react-native'; import { createUIMessenger, UIMessenger, @@ -37,6 +38,12 @@ const styles = StyleSheet.create({ gestureRoot: { flex: 1, }, + appRoot: { + flex: 1, + }, + toasterOverlay: { + ...StyleSheet.absoluteFillObject, + }, }); /** @@ -111,20 +118,28 @@ const Root = ({ foxCode }: RootProps) => { - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/app/components/Views/TransactionsView/index.js b/app/components/Views/TransactionsView/index.js index 01321865c94..4a87b756e3a 100644 --- a/app/components/Views/TransactionsView/index.js +++ b/app/components/Views/TransactionsView/index.js @@ -4,7 +4,6 @@ import PropTypes from 'prop-types'; import { connect, useSelector } from 'react-redux'; import { KnownCaipNamespace } from '@metamask/utils'; import { useNavigation } from '@react-navigation/native'; -import { showAlert } from '../../../actions/alert'; import Transactions from '../../UI/Transactions'; import { TX_UNAPPROVED, @@ -305,8 +304,4 @@ const mapStateToProps = (state) => { }; }; -const mapDispatchToProps = (dispatch) => ({ - showAlert: (config) => dispatch(showAlert(config)), -}); - -export default connect(mapStateToProps, mapDispatchToProps)(TransactionsView); +export default connect(mapStateToProps)(TransactionsView); diff --git a/app/reducers/alert/index.js b/app/reducers/alert/index.js deleted file mode 100644 index 7afc3d3f142..00000000000 --- a/app/reducers/alert/index.js +++ /dev/null @@ -1,28 +0,0 @@ -const initialState = { - isVisible: false, - autodismiss: null, - content: null, - data: null, -}; - -const alertReducer = (state = initialState, action) => { - switch (action.type) { - case 'SHOW_ALERT': - return { - ...state, - isVisible: true, - autodismiss: action.autodismiss, - content: action.content, - data: action.data, - }; - case 'HIDE_ALERT': - return { - ...state, - isVisible: false, - autodismiss: null, - }; - default: - return state; - } -}; -export default alertReducer; diff --git a/app/reducers/index.ts b/app/reducers/index.ts index 31a76f3145c..cf6dee0aa12 100644 --- a/app/reducers/index.ts +++ b/app/reducers/index.ts @@ -4,7 +4,6 @@ import engineReducer from '../core/redux/slices/engine'; import privacyReducer from './privacy'; import modalsReducer from './modals'; import settingsReducer from './settings'; -import alertReducer from './alert'; import securityAlertsReducer, { SecurityAlertsState } from './security-alerts'; import legalNoticesReducer, { LegalNoticesState } from './legalNotices'; import userReducer, { UserState } from './user'; @@ -86,9 +85,6 @@ export interface RootState { // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any settings: any; - // TODO: Replace "any" with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - alert: any; securityAlerts: SecurityAlertsState; user: UserState; // TODO: Replace "any" with type @@ -149,7 +145,6 @@ const baseReducers = { browser: browserReducer, modals: modalsReducer, settings: settingsReducer, - alert: alertReducer, securityAlerts: securityAlertsReducer, user: userReducer, onboarding: onboardingReducer, diff --git a/app/store/persistConfig/index.ts b/app/store/persistConfig/index.ts index ad816519c68..0bb4824f5d2 100644 --- a/app/store/persistConfig/index.ts +++ b/app/store/persistConfig/index.ts @@ -208,7 +208,6 @@ const persistConfig = { 'rpcEvents', 'accounts', 'confirmationMetrics', - 'alert', 'engine', 'qrKeyringScanner', 'securityAlerts', diff --git a/app/store/persistConfig/persistConfig.test.ts b/app/store/persistConfig/persistConfig.test.ts index a35b4914083..aa119e7c2f9 100644 --- a/app/store/persistConfig/persistConfig.test.ts +++ b/app/store/persistConfig/persistConfig.test.ts @@ -135,7 +135,6 @@ describe('persistConfig', () => { 'rpcEvents', 'accounts', 'confirmationMetrics', - 'alert', 'engine', 'qrKeyringScanner', 'securityAlerts', diff --git a/app/util/networks/switchNetwork.test.ts b/app/util/networks/switchNetwork.test.ts index 865d256645c..f2209844464 100644 --- a/app/util/networks/switchNetwork.test.ts +++ b/app/util/networks/switchNetwork.test.ts @@ -1,9 +1,18 @@ import switchNetwork from './switchNetwork'; -import { showAlert } from '../../actions/alert'; +import { toast, ToastSeverity } from '@metamask/design-system-react-native'; import { strings } from '../../../locales/i18n'; import { handleNetworkSwitch } from './handleNetworkSwitch'; import { store } from '../../store'; +jest.mock('@metamask/design-system-react-native', () => { + const actual = jest.requireActual('@metamask/design-system-react-native'); + + return { + ...actual, + toast: Object.assign(jest.fn(), { dismiss: jest.fn() }), + }; +}); + jest.mock('../../store', () => ({ store: { dispatch: jest.fn(), @@ -15,10 +24,6 @@ jest.mock('./handleNetworkSwitch', () => ({ handleNetworkSwitch: jest.fn(), })); -jest.mock('../../actions/alert', () => ({ - showAlert: jest.fn(), -})); - describe('switchNetwork', () => { const mockHandleNetworkSwitch = handleNetworkSwitch as jest.MockedFunction< typeof handleNetworkSwitch @@ -31,19 +36,15 @@ describe('switchNetwork', () => { mockHandleNetworkSwitch.mockReturnValue('Ethereum Mainnet'); }); - it('should dispatch an alert for a valid switchToChainId', () => { + it('should show a warning toast for a valid switchToChainId', () => { const switchToChainId = '1'; // Assuming '1' is a valid chain ID switchNetwork({ switchToChainId }); - expect(store.dispatch).toHaveBeenCalledWith( - showAlert({ - isVisible: true, - autodismiss: 5000, - content: 'clipboard-alert', - data: { msg: strings('send.warn_network_change') + 'Ethereum Mainnet' }, - }), - ); + expect(toast).toHaveBeenCalledWith({ + description: strings('send.warn_network_change') + 'Ethereum Mainnet', + severity: ToastSeverity.Warning, + }); }); it('should throw an error for an invalid switchToChainId', () => { @@ -55,12 +56,12 @@ describe('switchNetwork', () => { ); }); - it('should not dispatch an alert when switchNetwork returns undefined', () => { + it('should not show a toast when switchNetwork returns undefined', () => { const switchToChainId = '1'; // Assuming '1' is a valid chain ID mockHandleNetworkSwitch.mockReturnValue(undefined); switchNetwork({ switchToChainId }); - expect(store.dispatch).not.toHaveBeenCalled(); + expect(toast).not.toHaveBeenCalled(); }); }); diff --git a/app/util/networks/switchNetwork.ts b/app/util/networks/switchNetwork.ts index 95019e250aa..cfc0483c31b 100644 --- a/app/util/networks/switchNetwork.ts +++ b/app/util/networks/switchNetwork.ts @@ -1,5 +1,5 @@ +import { toast, ToastSeverity } from '@metamask/design-system-react-native'; import { strings } from '../../../locales/i18n'; -import { showAlert } from '../../actions/alert'; import { handleNetworkSwitch } from './handleNetworkSwitch'; import DevLogger from '../../core/SDKConnect/utils/DevLogger'; @@ -27,14 +27,10 @@ function switchNetwork({ throw new Error(`Unable to find network with chain id ${newChainId}`); } - store.dispatch( - showAlert({ - isVisible: true, - autodismiss: 5000, - content: 'clipboard-alert', - data: { msg: strings('send.warn_network_change') + networkName }, - }), - ); + toast({ + description: strings('send.warn_network_change') + networkName, + severity: ToastSeverity.Warning, + }); } else { DevLogger.log( 'Invalid Type: switchToChainId must be a string or number but was ' + diff --git a/app/util/test/initial-root-state.ts b/app/util/test/initial-root-state.ts index 0cd9067a1b8..9e2c8c03d7e 100644 --- a/app/util/test/initial-root-state.ts +++ b/app/util/test/initial-root-state.ts @@ -39,7 +39,6 @@ const initialRootState: RootState = { browser: undefined, modals: undefined, settings: undefined, - alert: undefined, securityAlerts: { alerts: {}, },