Skip to content

Commit

Permalink
feat(suite-native): send analytics
Browse files Browse the repository at this point in the history
  • Loading branch information
PeKne committed Sep 27, 2024
1 parent 8adbeaa commit e55fd3c
Show file tree
Hide file tree
Showing 19 changed files with 260 additions and 22 deletions.
6 changes: 6 additions & 0 deletions suite-native/analytics/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,10 @@ export enum EventType {
PassphraseAddHiddenWallet = 'passphrase/add_hidden_wallet',
PassphraseExit = 'passphrase/exit',
CoinEnablingInitState = 'coin-enabling/init_state',
SendAddressFilled = 'send/address_filled',
SendAmountInputSwitched = 'send/amount_input_switched',
SendRecipientCountChanged = 'send/recipient_count_changed',
SendFeeLevelChanged = 'send/fee_level_changed',
SendTransactionDispatched = 'send/transaction_dispatched',
SendFlowExited = 'send/flow_exited',
}
47 changes: 46 additions & 1 deletion suite-native/analytics/src/events.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { FiatCurrencyCode } from '@suite-common/suite-config';
import { UNIT_ABBREVIATION } from '@suite-common/suite-constants';
import { AccountType, NetworkSymbol } from '@suite-common/wallet-config';
import { TokenAddress, TokenSymbol } from '@suite-common/wallet-types';
import { FeeLevelLabel, TokenAddress, TokenSymbol } from '@suite-common/wallet-types';
import { DeviceModelInternal, VersionArray } from '@trezor/connect';

import { EventType } from './constants';
import { AnalyticsSendFlowStep } from './types';

export type SuiteNativeAnalyticsEvent =
| {
Expand Down Expand Up @@ -258,4 +259,48 @@ export type SuiteNativeAnalyticsEvent =
payload: {
enabledNetworks: NetworkSymbol[];
};
}
| {
type: EventType.SendTransactionDispatched;
payload: {
symbol: NetworkSymbol;
outputsCount: number;
selectedFee: FeeLevelLabel;
tokenSymbols?: TokenSymbol[];
tokenAddresses?: TokenAddress[];
hasEthereumData?: boolean;
hasEthereumNonce?: boolean;
hasRippleDestinationTag?: boolean;
hasBitcoinLocktime?: boolean;
};
}
| {
type: EventType.SendAddressFilled;
payload: {
method: 'manual' | 'qr';
};
}
| {
type: EventType.SendFeeLevelChanged;
payload: {
value: FeeLevelLabel;
};
}
| {
type: EventType.SendRecipientCountChanged;
payload: {
count: number;
};
}
| {
type: EventType.SendAmountInputSwitched;
payload: {
changedTo: 'crypto' | 'fiat';
};
}
| {
type: EventType.SendFlowExited;
payload: {
step: AnalyticsSendFlowStep;
};
};
1 change: 1 addition & 0 deletions suite-native/analytics/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './analytics';
export * from './analyticsThunks';
export * from './constants';
export * from './types';
5 changes: 5 additions & 0 deletions suite-native/analytics/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type AnalyticsSendFlowStep =
| 'address_and_amount'
| 'fee_settings'
| 'address_review'
| 'outputs_review';
14 changes: 12 additions & 2 deletions suite-native/forms/src/fields/TextInputField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ export type FieldProps = AllowedTextInputFieldProps &
>;

export const TextInputField = forwardRef<TextInput, FieldProps>(
({ name, hint, onBlur, defaultValue = '', valueTransformer, ...otherProps }, ref) => {
(
{ name, hint, onBlur, defaultValue = '', valueTransformer, onChangeText, ...otherProps },
ref,
) => {
const field = useField({
name,
defaultValue,
Expand All @@ -45,12 +48,19 @@ export const TextInputField = forwardRef<TextInput, FieldProps>(
}
};

const handleOnChange = (text: string) => {
onChange(text);
if (onChangeText) {
onChangeText(text);
}
};

return (
<InputWrapper error={errorMessage} hint={hint}>
<Input
{...otherProps}
onBlur={handleOnBlur}
onChangeText={onChange}
onChangeText={handleOnChange}
value={value}
hasError={hasError}
ref={ref}
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 @@ -26,6 +26,7 @@
"@suite-common/wallet-utils": "workspace:*",
"@suite-native/accounts": "workspace:*",
"@suite-native/alerts": "workspace:*",
"@suite-native/analytics": "workspace:*",
"@suite-native/atoms": "workspace:*",
"@suite-native/device": "workspace:*",
"@suite-native/device-mutex": "workspace:*",
Expand Down
26 changes: 23 additions & 3 deletions suite-native/module-send/src/components/AddressInput.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,41 @@
import React from 'react';
import { useSelector } from 'react-redux';

import { formInputsMaxLength } from '@suite-common/validators';
import { VStack, Text } from '@suite-native/atoms';
import { TextInputField, useFormContext } from '@suite-native/forms';
import { Translation } from '@suite-native/intl';
import { analytics, EventType } from '@suite-native/analytics';
import { isAddressValid } from '@suite-common/wallet-utils';
import { AccountKey } from '@suite-common/wallet-types';
import { AccountsRootState, selectAccountNetworkSymbol } from '@suite-common/wallet-core';

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

type AddressInputProps = {
index: number;
accountKey: AccountKey;
};
export const AddressInput = ({ index }: AddressInputProps) => {
export const AddressInput = ({ index, accountKey }: AddressInputProps) => {
const addressFieldName = getOutputFieldName(index, 'address');
const { setValue } = useFormContext();

const networkSymbol = useSelector((state: AccountsRootState) =>
selectAccountNetworkSymbol(state, accountKey),
);

const handleScanAddressQRCode = (qrCodeData: string) => {
setValue(addressFieldName, qrCodeData);
setValue(addressFieldName, qrCodeData, { shouldValidate: true });
if (networkSymbol && isAddressValid(qrCodeData, networkSymbol)) {
analytics.report({ type: EventType.SendAddressFilled, payload: { method: 'qr' } });
}
};

const handleChangeValue = (newValue: string) => {
if (networkSymbol && isAddressValid(newValue, networkSymbol)) {
analytics.report({ type: EventType.SendAddressFilled, payload: { method: 'manual' } });
}
};

return (
Expand All @@ -28,6 +47,7 @@ export const AddressInput = ({ index }: AddressInputProps) => {
multiline
name={addressFieldName}
testID={addressFieldName}
onChangeText={handleChangeValue}
maxLength={formInputsMaxLength.address}
accessibilityLabel="address input"
rightIcon={<QrCodeBottomSheetIcon onCodeScanned={handleScanAddressQRCode} />}
Expand Down
6 changes: 6 additions & 0 deletions suite-native/module-send/src/components/AmountInputs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from '@suite-common/wallet-core';
import { Translation } from '@suite-native/intl';
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
import { analytics, EventType } from '@suite-native/analytics';

import { CryptoAmountInput } from './CryptoAmountInput';
import { FiatAmountInput } from './FiatAmountInput';
Expand Down Expand Up @@ -80,6 +81,11 @@ export const AmountInputs = ({ index, accountKey }: AmountInputProps) => {
setTimeout(() => cryptoRef.current?.focus(), ANIMATION_DURATION);
}

analytics.report({
type: EventType.SendAmountInputSwitched,
payload: { changedTo: isCryptoSelected ? 'fiat' : 'crypto' },
});

setIsCryptoSelected(!isCryptoSelected);
};

Expand Down
27 changes: 19 additions & 8 deletions suite-native/module-send/src/components/FeeOption.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useContext } from 'react';
import { Pressable } from 'react-native';
import { useSelector } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import Animated, {
interpolateColor,
useAnimatedStyle,
Expand All @@ -9,7 +9,7 @@ import Animated, {
} from 'react-native-reanimated';

import { getNetworkType, NetworkSymbol } from '@suite-common/wallet-config';
import { GeneralPrecomposedTransactionFinal } from '@suite-common/wallet-types';
import { AccountKey, GeneralPrecomposedTransactionFinal } from '@suite-common/wallet-types';
import { Text, HStack, VStack, Radio, Box } from '@suite-native/atoms';
import { CryptoToFiatAmountFormatter, CryptoAmountFormatter } from '@suite-native/formatters';
import { FormContext } from '@suite-native/forms';
Expand All @@ -22,10 +22,12 @@ import {
import { Color } from '@trezor/theme';
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
import { getFeeUnits } from '@suite-common/wallet-utils';
import { analytics, EventType } from '@suite-native/analytics';

import { SendFeesFormValues } from '../sendFeesFormSchema';
import { NativeSupportedFeeLevel } from '../types';
import { FeeOptionErrorMessage } from './FeeOptionErrorMessage';
import { updateDraftFeeLevelThunk } from '../sendFormThunks';

const feeLabelsMap = {
economy: 'moduleSend.fees.levels.low',
Expand All @@ -45,20 +47,25 @@ const valuesWrapperStyle = prepareNativeStyle(utils => ({
padding: utils.spacings.medium,
}));

type FeeOptionProps = {
feeKey: SendFeesFormValues['feeLevel'];
feeLevel: GeneralPrecomposedTransactionFinal;
networkSymbol: NetworkSymbol;
transactionBytes: number;
accountKey: AccountKey;
};

export const FeeOption = ({
feeKey,
feeLevel,
networkSymbol,
transactionBytes,
}: {
feeKey: SendFeesFormValues['feeLevel'];
feeLevel: GeneralPrecomposedTransactionFinal;
networkSymbol: NetworkSymbol;
transactionBytes: number;
}) => {
accountKey,
}: FeeOptionProps) => {
const { utils } = useNativeStyles();
const { applyStyle } = useNativeStyles();
const { watch, setValue } = useContext(FormContext);
const dispatch = useDispatch();

const feeTimeEstimate = useSelector((state: FeesRootState) =>
selectNetworkFeeLevelTimeEstimate(state, feeKey, networkSymbol),
Expand All @@ -74,6 +81,10 @@ export const FeeOption = ({
setValue('feeLevel', feeKey, {
shouldValidate: true,
});

analytics.report({ type: EventType.SendFeeLevelChanged, payload: { value: feeKey } });

dispatch(updateDraftFeeLevelThunk({ accountKey, feeLevel: feeKey }));
};

const selectedLevel = watch('feeLevel');
Expand Down
17 changes: 11 additions & 6 deletions suite-native/module-send/src/components/FeeOptionsList.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import { D, pipe } from '@mobily/ts-belt';

import { NetworkSymbol } from '@suite-common/wallet-config';
import { GeneralPrecomposedLevels, PrecomposedTransactionFinal } from '@suite-common/wallet-types';
import {
AccountKey,
GeneralPrecomposedLevels,
PrecomposedTransactionFinal,
} from '@suite-common/wallet-types';
import { VStack } from '@suite-native/atoms';

import { FeeOption } from './FeeOption';
import { NativeSupportedFeeLevel } from '../types';

export const FeeOptionsList = ({
feeLevels,
networkSymbol,
}: {
type FeeOptionsListProps = {
feeLevels: GeneralPrecomposedLevels;
networkSymbol: NetworkSymbol;
}) => {
accountKey: AccountKey;
};

export const FeeOptionsList = ({ feeLevels, networkSymbol, accountKey }: FeeOptionsListProps) => {
// Remove custom fee level from the list. It is not supported in the first version of the send flow.
const predefinedFeeLevels = pipe(
feeLevels,
Expand All @@ -31,6 +35,7 @@ export const FeeOptionsList = ({
key={feeKey}
feeKey={feeKey as NativeSupportedFeeLevel}
feeLevel={feeLevel}
accountKey={accountKey}
networkSymbol={networkSymbol}
transactionBytes={transactionBytes}
/>
Expand Down
20 changes: 20 additions & 0 deletions suite-native/module-send/src/components/OutputsReviewFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ import { isFulfilled } from '@reduxjs/toolkit';
import {
AccountsRootState,
selectAccountByKey,
selectSendFormDraftByAccountKey,
selectSendSignedTx,
SendRootState,
} from '@suite-common/wallet-core';
import { AccountKey } from '@suite-common/wallet-types';
import { Button } from '@suite-native/atoms';
import { RootStackRoutes, AppTabsRoutes, RootStackParamList } from '@suite-native/navigation';
import { Translation } from '@suite-native/intl';
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
import { analytics, EventType } from '@suite-native/analytics';

import { SendConfirmOnDeviceImage } from '../components/SendConfirmOnDeviceImage';
import { sendTransactionAndCleanupSendFormThunk } from '../sendFormThunks';
Expand Down Expand Up @@ -67,17 +70,34 @@ export const OutputsReviewFooter = ({ accountKey }: { accountKey: AccountKey })
);
const signedTransaction = useSelector(selectSendSignedTx);

const formValues = useSelector((state: SendRootState) =>
selectSendFormDraftByAccountKey(state, accountKey),
);

{
/* TODO: improve the illustration: https://github.com/trezor/trezor-suite/issues/13965 */
}
if (!signedTransaction || !account) return <SendConfirmOnDeviceImage />;

const handleSendTransaction = async () => {
setIsSendInProgress(true);

const sendResponse = await dispatch(sendTransactionAndCleanupSendFormThunk({ account }));

if (isFulfilled(sendResponse)) {
const { txid } = sendResponse.payload;

if (formValues) {
analytics.report({
type: EventType.SendTransactionDispatched,
payload: {
symbol: account.symbol,
outputsCount: formValues.outputs.length,
selectedFee: formValues.selectedFee ?? 'normal',
},
});
}

navigation.dispatch(
navigateToAccountDetail({ accountKey, txid, closeActionType: 'close' }),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type RecipientInputsProps = {
export const RecipientInputs = ({ index, accountKey }: RecipientInputsProps) => {
return (
<VStack spacing="medium">
<AddressInput index={index} />
<AddressInput index={index} accountKey={accountKey} />
<CardDivider />
<AmountInputs index={index} accountKey={accountKey} />
</VStack>
Expand Down
6 changes: 5 additions & 1 deletion suite-native/module-send/src/components/SendFeesForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,11 @@ export const SendFeesForm = ({ accountKey, feeLevels }: SendFormProps) => {
<Translation id="moduleSend.fees.description.body" />
</Text>
</VStack>
<FeeOptionsList feeLevels={feeLevels} networkSymbol={account.symbol} />
<FeeOptionsList
feeLevels={feeLevels}
networkSymbol={account.symbol}
accountKey={accountKey}
/>
</VStack>
<FeesFooter
isSubmittable={isSubmittable}
Expand Down
Loading

0 comments on commit e55fd3c

Please sign in to comment.