diff --git a/packages/suite-desktop/package.json b/packages/suite-desktop/package.json index 8b90287148d..9e53f90c2d5 100644 --- a/packages/suite-desktop/package.json +++ b/packages/suite-desktop/package.json @@ -57,7 +57,8 @@ "protocols": { "name": "Trezor Suite", "schemes": [ - "bitcoin" + "bitcoin", + "aopp" ] }, "publish": { diff --git a/packages/suite/src/components/suite/hocNotification/components/withCoinProtocolScheme.tsx b/packages/suite/src/components/suite/hocNotification/components/withCoinProtocolScheme.tsx index 7274b991812..da656e4b45d 100644 --- a/packages/suite/src/components/suite/hocNotification/components/withCoinProtocolScheme.tsx +++ b/packages/suite/src/components/suite/hocNotification/components/withCoinProtocolScheme.tsx @@ -1,89 +1,63 @@ import React from 'react'; -import { connect } from 'react-redux'; import styled from 'styled-components'; -import { useRouteMatch } from 'react-router-dom'; import * as protocolActions from '@suite-actions/protocolActions'; - -import { useSelector } from '@suite-hooks'; import { Translation } from '@suite-components'; -import { CoinLogo, variables } from '@trezor/components'; +import { CoinLogo } from '@trezor/components'; import { capitalizeFirstLetter } from '@suite-utils/string'; import { PROTOCOL_TO_NETWORK } from '@suite-constants/protocol'; +import withConditionalAction from './withConditionalAction'; import type { NotificationEntry } from '@suite-reducers/notificationReducer'; -import type { Dispatch } from '@suite-types'; import type { ViewProps } from '../definitions'; - -type StrictViewProps = ViewProps & { - notification: Extract; -}; - -const Header = styled.div` - font-weight: ${variables.FONT_WEIGHT.MEDIUM}; - color: ${props => props.theme.TYPE_LIGHT_GREY}; - margin-top: 1px; -`; - -const Body = styled.div` - font-weight: ${variables.FONT_WEIGHT.MEDIUM}; - margin-top: 1px; -`; +import type { Network } from '@wallet-types'; const Row = styled.span` display: flex; `; -const withCoinProtocolScheme = (View: React.ComponentType, props: StrictViewProps) => { - const WrappedView = connect()(({ dispatch }: { dispatch: Dispatch }) => { - const { message, notification } = props; - - const { selectedAccount } = useSelector(state => ({ - selectedAccount: state.wallet.selectedAccount, - })); - - if (typeof message !== 'string') { - message.values = { - header: ( -
- -
- ), - body: ( - - - {notification.amount && `${notification.amount} `} - {capitalizeFirstLetter(notification.scheme)} - - {notification.address} - - ), - }; - } - - const isCorrectCoinSendForm = - useRouteMatch(`${process.env.ASSET_PREFIX || ''}/accounts/send`) && - selectedAccount?.network?.symbol === PROTOCOL_TO_NETWORK[notification.scheme]; - - return ( - dispatch(protocolActions.fillSendForm(true)), - label: 'TOAST_COIN_SCHEME_PROTOCOL_ACTION', - position: 'bottom', - variant: 'primary', - } - : undefined - } - icon={} - onCancel={() => dispatch(protocolActions.resetProtocol())} - /> - ); +const getIcon = (symbol?: Network['symbol']) => symbol && ; + +export const withAoppProtocol = ( + View: React.ComponentType, + notification: Extract, +) => + withConditionalAction(View, { + notification, + header: , + body: notification.message, + icon: getIcon(notification.asset), + actionLabel: 'TOAST_AOPP_FILL_ACTION', + actionCondition: { + path: '/accounts/sign-verify', + network: notification.asset, + }, + onAction: protocolActions.fillAopp(true), + onCancel: protocolActions.resetProtocol(), }); - return ; -}; -export default withCoinProtocolScheme; +export const withCoinProtocol = ( + View: React.ComponentType, + notification: Extract, +) => + withConditionalAction(View, { + notification, + header: , + body: ( + <> + + {notification.amount && `${notification.amount} `} + {capitalizeFirstLetter(notification.scheme)} + + {notification.address} + + ), + actionLabel: 'TOAST_COIN_SCHEME_PROTOCOL_ACTION', + actionCondition: { + path: '/accounts/send', + network: PROTOCOL_TO_NETWORK[notification.scheme], + }, + onAction: protocolActions.fillSendForm(true), + onCancel: protocolActions.resetProtocol(), + icon: getIcon(PROTOCOL_TO_NETWORK[notification.scheme]), + }); diff --git a/packages/suite/src/components/suite/hocNotification/components/withConditionalAction.tsx b/packages/suite/src/components/suite/hocNotification/components/withConditionalAction.tsx new file mode 100644 index 00000000000..a3b4238f79d --- /dev/null +++ b/packages/suite/src/components/suite/hocNotification/components/withConditionalAction.tsx @@ -0,0 +1,94 @@ +import React from 'react'; +import styled from 'styled-components'; +import { connect } from 'react-redux'; +import { useRouteMatch } from 'react-router-dom'; + +import { useSelector } from '@suite-hooks'; +import { variables } from '@trezor/components'; + +import type { NotificationEntry } from '@suite-reducers/notificationReducer'; +import type { Dispatch, Action, ExtendedMessageDescriptor } from '@suite-types'; +import type { Network } from '@wallet-types'; +import type { ViewProps } from '../definitions'; + +const Header = styled.div` + font-weight: ${variables.FONT_WEIGHT.MEDIUM}; + color: ${props => props.theme.TYPE_LIGHT_GREY}; + margin-top: 1px; +`; + +const Body = styled.div` + font-weight: ${variables.FONT_WEIGHT.MEDIUM}; + margin-top: 1px; +`; + +type WithConditionalActionProps = { + notification: NotificationEntry; + header: React.ReactNode; + body: React.ReactNode; + icon?: JSX.Element; + actionLabel: ExtendedMessageDescriptor['id']; + actionCondition: { + path?: string; + network?: Network['symbol']; + }; + onAction: Action; + onCancel: Action; +}; + +const withConditionalAction = ( + View: React.ComponentType, + props: WithConditionalActionProps, +) => { + const WrappedView = connect()(({ dispatch }: { dispatch: Dispatch }) => { + const { + notification, + header, + body, + icon, + actionCondition: { path, network }, + actionLabel, + onAction, + onCancel, + } = props; + + const { selectedAccount } = useSelector(state => ({ + selectedAccount: state.wallet.selectedAccount, + })); + + const actionAllowed = + (!path || useRouteMatch(`${process.env.ASSET_PREFIX || ''}${path}`)) && + (!network || selectedAccount?.network?.symbol === network); + + const action = actionAllowed + ? ({ + onClick: () => dispatch(onAction), + label: actionLabel, + position: 'bottom', + variant: 'primary', + } as const) + : undefined; + + const message = { + id: 'TOAST_COIN_SCHEME_PROTOCOL', + values: { + header:
{header}
, + body: {body}, + }, + } as const; + + return ( + dispatch(onCancel)} + /> + ); + }); + return ; +}; + +export default withConditionalAction; diff --git a/packages/suite/src/components/suite/hocNotification/index.tsx b/packages/suite/src/components/suite/hocNotification/index.tsx index f4bfac9dae2..4c153716788 100644 --- a/packages/suite/src/components/suite/hocNotification/index.tsx +++ b/packages/suite/src/components/suite/hocNotification/index.tsx @@ -4,7 +4,7 @@ import { SUITE } from '@suite-actions/constants'; import { NotificationEntry } from '@suite-reducers/notificationReducer'; import withAction from './components/withAction'; import withTransaction from './components/withTransaction'; -import withCoinProtocolScheme from './components/withCoinProtocolScheme'; +import { withAoppProtocol, withCoinProtocol } from './components/withCoinProtocolScheme'; import { ViewProps } from './definitions'; const simple = (View: React.ComponentType, props: ViewProps) => ( @@ -21,12 +21,9 @@ const simple = (View: React.ComponentType, props: ViewProps) => ( const hocNotification = (notification: NotificationEntry, View: React.ComponentType) => { switch (notification.type) { case 'coin-scheme-protocol': - return withCoinProtocolScheme(View, { - notification, - variant: 'transparent', - // values get filled in `withCoinProtocolScheme` - message: { id: 'TOAST_COIN_SCHEME_PROTOCOL', values: {} }, - }); + return withCoinProtocol(View, notification); + case 'aopp-protocol': + return withAoppProtocol(View, notification); case 'acquire-error': return simple(View, { notification, diff --git a/packages/suite/src/middlewares/suite/protocolMiddleware.ts b/packages/suite/src/middlewares/suite/protocolMiddleware.ts index c5529e65577..5f8989101d6 100644 --- a/packages/suite/src/middlewares/suite/protocolMiddleware.ts +++ b/packages/suite/src/middlewares/suite/protocolMiddleware.ts @@ -4,6 +4,14 @@ import { close } from '@suite-actions/notificationActions'; import { PROTOCOL } from '@suite-actions/constants'; import type { AppState, Action, Dispatch } from '@suite-types'; +import type { ToastPayload } from '@suite-reducers/notificationReducer'; + +// close custom protocol notification of given type +const closeNotifications = (api: MiddlewareAPI, type: ToastPayload['type']) => { + api.getState() + .notifications.filter(notification => notification.type === type && !notification.closed) + .forEach(protocolNotification => api.dispatch(close(protocolNotification.id))); +}; const protocolMiddleware = (api: MiddlewareAPI) => @@ -11,14 +19,15 @@ const protocolMiddleware = (action: Action): Action => { next(action); - if (action.type === PROTOCOL.SAVE_COIN_PROTOCOL) { - // close current custom protocol notification - api.getState() - .notifications.filter( - notification => - notification.type === 'coin-scheme-protocol' && !notification.closed, - ) - .forEach(protocolNotification => api.dispatch(close(protocolNotification.id))); + switch (action.type) { + case PROTOCOL.SAVE_COIN_PROTOCOL: + closeNotifications(api, 'coin-scheme-protocol'); + break; + case PROTOCOL.SAVE_AOPP_PROTOCOL: + closeNotifications(api, 'aopp-protocol'); + break; + default: + break; } return action; diff --git a/packages/suite/src/reducers/suite/notificationReducer.ts b/packages/suite/src/reducers/suite/notificationReducer.ts index 55900885c49..f9d200ddb13 100644 --- a/packages/suite/src/reducers/suite/notificationReducer.ts +++ b/packages/suite/src/reducers/suite/notificationReducer.ts @@ -91,6 +91,11 @@ export type ToastPayload = ( address: string; amount?: number; } + | { + type: 'aopp-protocol'; + message: string; + asset: Network['symbol']; + } ) & Options; diff --git a/packages/suite/src/support/messages.ts b/packages/suite/src/support/messages.ts index a85cbbf22a7..a6e95347f7b 100644 --- a/packages/suite/src/support/messages.ts +++ b/packages/suite/src/support/messages.ts @@ -7007,4 +7007,12 @@ export default defineMessages({ id: 'TR_DO_YOU_REALLY_WANT_TO_SKIP', defaultMessage: 'Do you really want to skip this step?', }, + TOAST_AOPP_FILL_HEADER: { + id: 'TOAST_AOPP_FILL_HEADER', + defaultMessage: 'Go to Sign & Verify form', + }, + TOAST_AOPP_FILL_ACTION: { + id: 'TOAST_AOPP_FILL_ACTION', + defaultMessage: 'Fill form', + }, }); diff --git a/packages/suite/src/support/suite/Protocol.tsx b/packages/suite/src/support/suite/Protocol.tsx index 52aebddfa0c..6a4ef17fcc5 100644 --- a/packages/suite/src/support/suite/Protocol.tsx +++ b/packages/suite/src/support/suite/Protocol.tsx @@ -9,9 +9,10 @@ import * as protocolActions from '@suite-actions/protocolActions'; import { PROTOCOL_SCHEME } from '@suite-constants/protocol'; const Protocol = () => { - const { addToast, saveCoinProtocol } = useActions({ + const { addToast, saveCoinProtocol, saveAoppProtocol } = useActions({ addToast: notificationActions.addToast, saveCoinProtocol: protocolActions.saveCoinProtocol, + saveAoppProtocol: protocolActions.saveAoppProtocol, }); const handleProtocolRequest = useCallback( @@ -30,11 +31,27 @@ const Protocol = () => { }); break; } + case PROTOCOL_SCHEME.AOPP: { + const { asset, msg, callback, format } = protocolInfo; + saveAoppProtocol({ + asset, + message: msg, + callback, + format, + }); + addToast({ + type: 'aopp-protocol', + message: msg, + asset, + autoClose: false, + }); + break; + } default: break; } }, - [addToast, saveCoinProtocol], + [addToast, saveCoinProtocol, saveAoppProtocol], ); const { search } = useLocation();