Skip to content

Commit

Permalink
feat(suite): #4512 add AOPP autofill notification
Browse files Browse the repository at this point in the history
  • Loading branch information
marekrjpolak committed Dec 14, 2021
1 parent 288a23a commit 92f7737
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 90 deletions.
3 changes: 2 additions & 1 deletion packages/suite-desktop/package.json
Expand Up @@ -57,7 +57,8 @@
"protocols": {
"name": "Trezor Suite",
"schemes": [
"bitcoin"
"bitcoin",
"aopp"
]
},
"publish": {
Expand Down
@@ -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<NotificationEntry, { type: 'coin-scheme-protocol' }>;
};

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<ViewProps>, 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: (
<Header>
<Translation id="TOAST_COIN_SCHEME_PROTOCOL_HEADER" />
</Header>
),
body: (
<Body>
<Row>
{notification.amount && `${notification.amount} `}
{capitalizeFirstLetter(notification.scheme)}
</Row>
<Row>{notification.address}</Row>
</Body>
),
};
}

const isCorrectCoinSendForm =
useRouteMatch(`${process.env.ASSET_PREFIX || ''}/accounts/send`) &&
selectedAccount?.network?.symbol === PROTOCOL_TO_NETWORK[notification.scheme];

return (
<View
{...props}
action={
isCorrectCoinSendForm
? {
onClick: () => dispatch(protocolActions.fillSendForm(true)),
label: 'TOAST_COIN_SCHEME_PROTOCOL_ACTION',
position: 'bottom',
variant: 'primary',
}
: undefined
}
icon={<CoinLogo symbol={PROTOCOL_TO_NETWORK[notification.scheme] as any} size={20} />}
onCancel={() => dispatch(protocolActions.resetProtocol())}
/>
);
const getIcon = (symbol?: Network['symbol']) => symbol && <CoinLogo symbol={symbol} size={20} />;

export const withAoppProtocol = (
View: React.ComponentType<ViewProps>,
notification: Extract<NotificationEntry, { type: 'aopp-protocol' }>,
) =>
withConditionalAction(View, {
notification,
header: <Translation id="TOAST_AOPP_FILL_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 <WrappedView key={props.notification.id} />;
};

export default withCoinProtocolScheme;
export const withCoinProtocol = (
View: React.ComponentType<ViewProps>,
notification: Extract<NotificationEntry, { type: 'coin-scheme-protocol' }>,
) =>
withConditionalAction(View, {
notification,
header: <Translation id="TOAST_COIN_SCHEME_PROTOCOL_HEADER" />,
body: (
<>
<Row>
{notification.amount && `${notification.amount} `}
{capitalizeFirstLetter(notification.scheme)}
</Row>
<Row>{notification.address}</Row>
</>
),
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]),
});
@@ -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<ViewProps>,
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>{header}</Header>,
body: <Body>{body}</Body>,
},
} as const;

return (
<View
variant="transparent"
notification={notification}
message={message}
icon={icon}
action={action}
onCancel={() => dispatch(onCancel)}
/>
);
});
return <WrappedView key={props.notification.id} />;
};

export default withConditionalAction;
11 changes: 4 additions & 7 deletions packages/suite/src/components/suite/hocNotification/index.tsx
Expand Up @@ -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<ViewProps>, props: ViewProps) => (
Expand All @@ -21,12 +21,9 @@ const simple = (View: React.ComponentType<ViewProps>, props: ViewProps) => (
const hocNotification = (notification: NotificationEntry, View: React.ComponentType<ViewProps>) => {
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,
Expand Down
25 changes: 17 additions & 8 deletions packages/suite/src/middlewares/suite/protocolMiddleware.ts
Expand Up @@ -4,21 +4,30 @@ 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<Dispatch, AppState>, type: ToastPayload['type']) => {
api.getState()
.notifications.filter(notification => notification.type === type && !notification.closed)
.forEach(protocolNotification => api.dispatch(close(protocolNotification.id)));
};

const protocolMiddleware =
(api: MiddlewareAPI<Dispatch, AppState>) =>
(next: Dispatch) =>
(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;
Expand Down
5 changes: 5 additions & 0 deletions packages/suite/src/reducers/suite/notificationReducer.ts
Expand Up @@ -91,6 +91,11 @@ export type ToastPayload = (
address: string;
amount?: number;
}
| {
type: 'aopp-protocol';
message: string;
asset: Network['symbol'];
}
) &
Options;

Expand Down
8 changes: 8 additions & 0 deletions packages/suite/src/support/messages.ts
Expand Up @@ -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',
},
});
21 changes: 19 additions & 2 deletions packages/suite/src/support/suite/Protocol.tsx
Expand Up @@ -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(
Expand All @@ -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();
Expand Down

0 comments on commit 92f7737

Please sign in to comment.