Skip to content

Commit

Permalink
feat(suite-native): ethereum send info messages
Browse files Browse the repository at this point in the history
  • Loading branch information
PeKne committed Nov 1, 2024
1 parent ec6f90c commit d380b96
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 9 deletions.
16 changes: 16 additions & 0 deletions suite-native/intl/src/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -938,6 +938,22 @@ export const en = {
title: 'Send from',
},
outputs: {
correctNetworkMessage:
'Make sure that you’re sending to an address on\u00A0{networkName} network. <link>Learn more</link>',
tokenOfNetworkSheet: {
title: 'You’re about to Send {tokenSymbol} that runs on {networkName} network.',
body: {
self: {
subtitle: 'Sending to yourself?',
text: 'Make sure your exchange or wallet supports this token on {networkName} network.',
},
outside: {
subtitle: 'Sending to someone else?',
text: 'Check with them if they’re alright with receiving this token on {networkName} network.',
},
},
warning: 'Sending to a wrong network might result in loss of funds.',
},
recipients: {
title: 'Amount & recipients',
addressLabel: 'Recipient address',
Expand Down
23 changes: 14 additions & 9 deletions suite-native/link/src/components/Link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Animated, {
import { RequireExactlyOne } from 'type-fest';

import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
import { Color } from '@trezor/theme';
import { Color, TypographyStyle } from '@trezor/theme';

import { useOpenLink } from '../useOpenLink';

Expand All @@ -21,18 +21,22 @@ type LinkProps = RequireExactlyOne<
isUnderlined?: boolean;
textColor?: Color;
textPressedColor?: Color;
textVariant?: TypographyStyle;
},
'href' | 'onPress'
>;

const textStyle = prepareNativeStyle<{ isUnderlined: boolean }>((_, { isUnderlined }) => ({
extend: {
condition: isUnderlined,
style: {
textDecorationLine: 'underline',
const textStyle = prepareNativeStyle<{ isUnderlined: boolean; textVariant: TypographyStyle }>(
(utils, { isUnderlined, textVariant }) => ({
...utils.typography[textVariant],
extend: {
condition: isUnderlined,
style: {
textDecorationLine: 'underline',
},
},
},
}));
}),
);

const ANIMATION_DURATION = 100;
const IS_NOT_PRESSED_VALUE = 0;
Expand All @@ -44,6 +48,7 @@ export const Link = ({
isUnderlined = false,
textColor = 'textPrimaryDefault',
textPressedColor = 'textPrimaryPressed',
textVariant = 'body',
onPress,
}: LinkProps) => {
const { utils, applyStyle } = useNativeStyles();
Expand Down Expand Up @@ -80,7 +85,7 @@ export const Link = ({
onPressIn={handlePressIn}
onPress={handlePress}
onPressOut={handlePressOut}
style={[applyStyle(textStyle, { isUnderlined }), animatedTextColorStyle]}
style={[applyStyle(textStyle, { isUnderlined, textVariant }), animatedTextColorStyle]}
suppressHighlighting
>
{label}
Expand Down
1 change: 1 addition & 0 deletions suite-native/module-send/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@suite-native/helpers": "workspace:*",
"@suite-native/icons": "workspace:*",
"@suite-native/intl": "workspace:*",
"@suite-native/link": "workspace:*",
"@suite-native/navigation": "workspace:*",
"@suite-native/qr-code": "workspace:*",
"@suite-native/settings": "workspace:*",
Expand Down
3 changes: 3 additions & 0 deletions suite-native/module-send/src/components/AddressInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { isDebugEnv } from '@suite-native/config';
import { QrCodeBottomSheetIcon } from './QrCodeBottomSheetIcon';
import { getOutputFieldName } from '../utils';
import { SendOutputsFormValues } from '../sendOutputsFormSchema';
import { useTokenOfNetworkAlert } from '../hooks/useTokenOfNetworkAlert';

type AddressInputProps = {
index: number;
Expand All @@ -36,6 +37,8 @@ export const AddressInput = ({ index, accountKey }: AddressInputProps) => {
selectFreshAccountAddress(state, accountKey),
);

useTokenOfNetworkAlert({ inputIndex: index });

const handleScanAddressQRCode = (qrCodeData: string) => {
setValue(addressFieldName, qrCodeData, { shouldValidate: true });
if (networkSymbol && isAddressValid(qrCodeData, networkSymbol)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { CryptoIcon } from '@suite-native/icons';
import { networks, NetworkSymbol } from '@suite-common/wallet-config';
import { Card, HStack, Text } from '@suite-native/atoms';
import { Translation } from '@suite-native/intl';
import { Link } from '@suite-native/link';
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
import { isCoinWithTokens } from '@suite-native/tokens';

const cardStyle = prepareNativeStyle(utils => ({
backgroundColor: utils.colors.backgroundTertiaryDefaultOnElevation0,
borderColor: utils.colors.borderElevation0,
borderWidth: utils.borders.widths.small,
paddingVertical: utils.spacings.sp12,

...utils.boxShadows.none,
}));

type CorrectNetworkMessageCardProps = {
networkSymbol: NetworkSymbol;
};

const LINK_URL = 'https://trezor.io/learn/a/how-to-choose-the-right-network';

export const CorrectNetworkMessageCard = ({ networkSymbol }: CorrectNetworkMessageCardProps) => {
const { applyStyle } = useNativeStyles();

if (!isCoinWithTokens(networkSymbol)) return null;

const networkName = networks[networkSymbol].name;

return (
<Card style={applyStyle(cardStyle)}>
<HStack spacing="sp12">
<CryptoIcon symbol={networkSymbol} size={20} />
<Text variant="hint">
<Translation
id="moduleSend.outputs.correctNetworkMessage"
values={{
networkName,
link: linkChunk => (
<Link
href={LINK_URL}
label={linkChunk}
isUnderlined
textVariant="hint"
textColor="textDefault"
/>
),
}}
/>
</Text>
</HStack>
</Card>
);
};
7 changes: 7 additions & 0 deletions suite-native/module-send/src/components/SendOutputFields.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { useFieldArray } from 'react-hook-form';
import { useSelector } from 'react-redux';

import { Card, Text, VStack } from '@suite-native/atoms';
import { useFormContext } from '@suite-native/forms';
import { AccountKey } from '@suite-common/wallet-types';
import { Translation } from '@suite-native/intl';
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
import { AccountsRootState, selectAccountNetworkSymbol } from '@suite-common/wallet-core';

import { RecipientInputs } from './RecipientInputs';
import { SendOutputsFormValues } from '../sendOutputsFormSchema';
import { CorrectNetworkMessageCard } from './CorrectNetworkMessageCard';

type SendOutputFieldsProps = {
accountKey: AccountKey;
Expand All @@ -21,13 +24,17 @@ const cardStyle = prepareNativeStyle(utils => ({
export const SendOutputFields = ({ accountKey }: SendOutputFieldsProps) => {
const { applyStyle } = useNativeStyles();
const { control } = useFormContext<SendOutputsFormValues>();
const networkSymbol = useSelector((state: AccountsRootState) =>
selectAccountNetworkSymbol(state, accountKey),
);
const outputsFieldArray = useFieldArray({ control, name: 'outputs' });

return (
<VStack spacing="sp16">
<Text variant="titleSmall">
<Translation id="moduleSend.outputs.recipients.title" />
</Text>
{networkSymbol && <CorrectNetworkMessageCard networkSymbol={networkSymbol} />}
<Card style={applyStyle(cardStyle)}>
{outputsFieldArray.fields.map((output, index) => (
<RecipientInputs key={output.id} index={index} accountKey={accountKey} />
Expand Down
156 changes: 156 additions & 0 deletions suite-native/module-send/src/hooks/useTokenOfNetworkAlert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { ReactNode, useEffect, useRef } from 'react';
import { useSelector } from 'react-redux';

import { useRoute, RouteProp } from '@react-navigation/native';

import { getNetwork } from '@suite-common/wallet-config';
import { Box, VStack, Text, AlertBox } from '@suite-native/atoms';
import { SendStackParamList, SendStackRoutes } from '@suite-native/navigation';
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
import { Translation } from '@suite-native/intl';
import { selectAccountTokenSymbol, TokensRootState } from '@suite-native/tokens';
import { CryptoIcon } from '@suite-native/icons';
import { useAlert } from '@suite-native/alerts';
import { useFormContext } from '@suite-native/forms';
import { isAddressValid } from '@suite-common/wallet-utils';
import { AccountsRootState, selectAccountNetworkSymbol } from '@suite-common/wallet-core';
import { AccountKey, TokenAddress } from '@suite-common/wallet-types';

import { getOutputFieldName } from '../utils';

type UseTokenOfNetworkAlertArgs = {
inputIndex: number;
};

const iconWrapperStyle = prepareNativeStyle(() => ({
overflow: 'visible',
height: 90,
width: 90,
}));

const networkIconWrapperStyle = prepareNativeStyle(utils => ({
position: 'absolute',
backgroundColor: utils.colors.backgroundSurfaceElevation1,
padding: 3,
borderRadius: utils.borders.radii.round,
right: 0,
bottom: 0,
overflow: 'visible',
}));

type ParagraphProps = {
header: ReactNode;
body: ReactNode;
};

const Paragraph = ({ header, body }: ParagraphProps) => (
<VStack spacing="sp4">
<Text variant="highlight">{header}</Text>
<Text color="textSubdued">{body}</Text>
</VStack>
);

const TokenOfNetworkAlertBody = ({
accountKey,
tokenContract,
}: {
accountKey: AccountKey;
tokenContract?: TokenAddress;
}) => {
const { applyStyle } = useNativeStyles();
const tokenSymbol = useSelector((state: TokensRootState) =>
selectAccountTokenSymbol(state, accountKey, tokenContract),
);
const networkSymbol = useSelector((state: AccountsRootState) =>
selectAccountNetworkSymbol(state, accountKey),
);

if (!tokenContract || !networkSymbol) return null;

const networkName = getNetwork(networkSymbol).name;

return (
<VStack spacing="sp24">
<Box style={applyStyle(iconWrapperStyle)}>
<CryptoIcon symbol={tokenContract} size={80} />
<Box style={applyStyle(networkIconWrapperStyle)}>
<CryptoIcon symbol={networkSymbol} size={32} />
</Box>
</Box>
<Text variant="titleSmall">
<Translation
id="moduleSend.outputs.tokenOfNetworkSheet.title"
values={{ tokenSymbol, networkName }}
/>
</Text>
<Paragraph
header={
<Translation id="moduleSend.outputs.tokenOfNetworkSheet.body.self.subtitle" />
}
body={
<Translation
id="moduleSend.outputs.tokenOfNetworkSheet.body.self.text"
values={{ networkName }}
/>
}
/>
<Paragraph
header={
<Translation id="moduleSend.outputs.tokenOfNetworkSheet.body.outside.subtitle" />
}
body={
<Translation
id="moduleSend.outputs.tokenOfNetworkSheet.body.outside.text"
values={{ networkName }}
/>
}
/>
<AlertBox
title={
<Text variant="callout" color="textDefault">
<Translation id="moduleSend.outputs.tokenOfNetworkSheet.warning" />
</Text>
}
variant="warning"
borderRadius="r12"
/>
</VStack>
);
};

export const useTokenOfNetworkAlert = ({ inputIndex }: UseTokenOfNetworkAlertArgs) => {
const wasAlertShown = useRef(false);
const { showAlert } = useAlert();
const {
params: { tokenContract, accountKey },
} = useRoute<RouteProp<SendStackParamList, SendStackRoutes.SendOutputs>>();

const tokenSymbol = useSelector((state: TokensRootState) =>
selectAccountTokenSymbol(state, accountKey, tokenContract),
);
const networkSymbol = useSelector((state: AccountsRootState) =>
selectAccountNetworkSymbol(state, accountKey),
);

const { watch } = useFormContext();

const addressValue = watch(getOutputFieldName(inputIndex, 'address'));

const isFilledValidAddress =
addressValue && networkSymbol && isAddressValid(addressValue, networkSymbol);

useEffect(() => {
if (tokenContract && isFilledValidAddress && !wasAlertShown.current) {
showAlert({
appendix: (
<TokenOfNetworkAlertBody
accountKey={accountKey}
tokenContract={tokenContract}
/>
),
primaryButtonTitle: <Translation id="generic.buttons.gotIt" />,
});
wasAlertShown.current = true;
}
}, [isFilledValidAddress, showAlert, tokenContract, tokenSymbol, accountKey]);
};
1 change: 1 addition & 0 deletions suite-native/module-send/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
{ "path": "../helpers" },
{ "path": "../icons" },
{ "path": "../intl" },
{ "path": "../link" },
{ "path": "../navigation" },
{ "path": "../qr-code" },
{ "path": "../settings" },
Expand Down
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10235,6 +10235,7 @@ __metadata:
"@suite-native/helpers": "workspace:*"
"@suite-native/icons": "workspace:*"
"@suite-native/intl": "workspace:*"
"@suite-native/link": "workspace:*"
"@suite-native/navigation": "workspace:*"
"@suite-native/qr-code": "workspace:*"
"@suite-native/settings": "workspace:*"
Expand Down

0 comments on commit d380b96

Please sign in to comment.