refactor: btc send form

This commit is contained in:
fbwoolf
2023-03-21 14:57:55 -05:00
committed by Fara Woolf
parent 64a04a42b1
commit f28aabfdc7
39 changed files with 291 additions and 445 deletions

View File

@@ -1,13 +1,8 @@
import * as yup from 'yup';
import { NetworkConfiguration } from '@shared/constants';
import { FormErrorMessages } from '@app/common/error-messages';
import { fetchBtcNameOwner } from '@app/query/stacks/bns/bns.utils';
import { StacksClient } from '@app/query/stacks/stacks-client';
import {
btcAddressValidator,
notCurrentAddressValidator,
stxAddressNetworkValidator,
stxAddressValidator,
@@ -21,23 +16,3 @@ export function stxRecipientValidator(
.concat(stxAddressNetworkValidator(currentNetwork))
.concat(notCurrentAddressValidator(currentAddress || ''));
}
export function btcRecipientAddressOrBnsNameValidator({ client }: { client: StacksClient }) {
return yup.string().test({
name: 'btcRecipientOrBnsName',
message: FormErrorMessages.InvalidAddress,
test: async value => {
try {
await btcAddressValidator().validate(value);
return true;
} catch (e) {}
try {
const btcAddress = await fetchBtcNameOwner(client, value ?? '');
await btcAddressValidator().validate(btcAddress);
return true;
} catch (error) {
return false;
}
},
});
}

View File

@@ -50,7 +50,7 @@ export function FeeEstimateSelectLayout(props: FeeEstimateSelectLayoutProps) {
position="absolute"
ref={ref}
style={styles}
top="-100px"
top="-35px"
zIndex={9999}
>
{children}

View File

@@ -10,14 +10,25 @@ interface CollectibleAssetProps {
}
export function CollectibleAsset({ icon, name, symbol }: CollectibleAssetProps) {
return (
<SpaceBetween py="base" px="base" width="100%">
<Flex alignItems="center">
{icon}
<Text ml="tight" mr="extra-tight">
{name}
</Text>
{symbol && <Text>({symbol.toUpperCase()})</Text>}
</Flex>
</SpaceBetween>
<Flex
alignItems="center"
border="1px solid #DCDDE2"
borderRadius="10px"
minHeight="64px"
mb="base"
mt="loose"
px="base"
width="100%"
>
<SpaceBetween>
<Flex alignItems="center">
{icon}
<Text ml="tight" mr="extra-tight">
{name}
</Text>
{symbol && <Text>({symbol.toUpperCase()})</Text>}
</Flex>
</SpaceBetween>
</Flex>
);
}

View File

@@ -1,7 +1,7 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Box, Button } from '@stacks/ui';
import { Box, Button, Flex } from '@stacks/ui';
import BigNumber from 'bignumber.js';
import { Form, Formik } from 'formik';
@@ -16,8 +16,6 @@ import { OrdinalIcon } from '@app/components/icons/ordinal-icon';
import { getNumberOfInscriptionOnUtxo } from '@app/query/bitcoin/ordinals/utils';
import { BtcSizeFeeEstimator } from '../../../common/transactions/bitcoin/fees/btc-size-fee-estimator';
import { FormErrors } from '../send-crypto-asset-form/components/form-errors';
import { FormFieldsLayout } from '../send-crypto-asset-form/components/form-fields.layout';
import { RecipientField } from '../send-crypto-asset-form/components/recipient-field';
import { CollectibleAsset } from './components/collectible-asset';
import { CollectiblePreviewCard } from './components/collectible-preview-card';
@@ -107,12 +105,15 @@ export function SendInscriptionForm() {
<Box px="extra-loose">
<CollectiblePreviewCard inscription={inscription} mt="extra-loose" />
<Box mt={['base', 'extra-loose', '100px']}>
<FormFieldsLayout>
<Flex flexDirection="column" mt="loose" width="100%">
<CollectibleAsset icon={<OrdinalIcon />} name="Ordinal inscription" />
<RecipientField name={recipeintFieldName} placeholder="Address" />
</FormFieldsLayout>
<RecipientField
name={recipeintFieldName}
label="To"
placeholder="Enter recipient address"
/>
</Flex>
</Box>
<FormErrors />
{currentError && (
<ErrorLabel textAlign="left" mb="base-loose">
{currentError}
@@ -120,6 +121,7 @@ export function SendInscriptionForm() {
)}
<Button
mb="extra-loose"
mt="tight"
type="submit"
borderRadius="10px"
height="48px"

View File

@@ -1,5 +1,6 @@
import * as yup from 'yup';
import { FormErrorMessages } from '@app/common/error-messages';
import {
btcAddressNetworkValidator,
btcAddressValidator,
@@ -14,7 +15,7 @@ export function useOrdinalInscriptionFormValidationSchema() {
return yup.object({
[recipeintFieldName]: yup
.string()
.required('Please provide a recipient')
.required(FormErrorMessages.AddressRequired)
.concat(btcAddressValidator())
.concat(btcAddressNetworkValidator(currentNetwork.chain.bitcoin.network))
.concat(btcTaprootAddressValidator()),

View File

@@ -1,14 +0,0 @@
import { Money } from '@shared/models/money.model';
import { convertAmountToBaseUnit } from '@app/common/money/calculate-money';
import { Caption } from '@app/components/typography';
export function AvailableBalance(props: { availableBalance: Money }) {
const { availableBalance } = props;
return (
<Caption>
Balance: {convertAmountToBaseUnit(availableBalance).toFormat()}{' '}
{availableBalance.symbol.toUpperCase()}
</Caption>
);
}

View File

@@ -1,60 +0,0 @@
// TODO: Remove with send form design refactor
import { useEffect, useState } from 'react';
import AnimateHeight from 'react-animate-height';
import { Box, Flex } from '@stacks/ui';
import { SendCryptoAssetSelectors } from '@tests/selectors/send.selectors';
import { FormikContextType, useFormikContext } from 'formik';
import { ErrorLabel } from '@app/components/error-label';
function omitAmountErrorsAsDisplayedElsewhere([key]: [string, unknown]) {
return key !== 'amount';
}
function shouldDisplayErrors(form: FormikContextType<unknown>) {
return (
Object.entries(form.touched)
.filter(omitAmountErrorsAsDisplayedElsewhere)
.map(([_key, value]) => value)
.includes(true) && Object.keys(form.errors).length
);
}
const closedHeight = 24;
const openHeight = 56;
export function FormErrors() {
const [showHide, setShowHide] = useState(closedHeight);
const form = useFormikContext();
useEffect(() => {
if (shouldDisplayErrors(form)) {
setShowHide(openHeight);
return;
}
setShowHide(closedHeight);
}, [form]);
const [firstError] =
Object.entries(form.errors).filter(omitAmountErrorsAsDisplayedElsewhere) ?? [];
const [field, message] = firstError ?? [];
const isFirstErrorFieldTouched = (form.touched as any)[field];
return message && isFirstErrorFieldTouched && shouldDisplayErrors(form) ? (
<AnimateHeight duration={400} easing="ease-out" height={showHide}>
<Flex height={openHeight + 'px'}>
<ErrorLabel
alignSelf="center"
data-testid={SendCryptoAssetSelectors.FormFieldInputErrorLabel}
>
{firstError?.[1]}
</ErrorLabel>
</Flex>
</AnimateHeight>
) : (
<Box height={closedHeight + 'px'}></Box>
);
}

View File

@@ -1,20 +0,0 @@
import { Flex } from '@stacks/ui';
interface FormFieldsProps {
children: React.ReactNode;
}
export function FormFieldsLayout({ children }: FormFieldsProps) {
return (
<Flex
alignItems="center"
flexDirection="column"
maxHeight={['calc(100vh - 116px)', 'calc(85vh - 116px)']}
overflowY="scroll"
pb={['92px', 'unset']}
pt={['base', '48px']}
px="loose"
>
{children}
</Flex>
);
}

View File

@@ -1,5 +1,3 @@
import { Outlet } from 'react-router-dom';
import { Box, color } from '@stacks/ui';
import { Money } from '@shared/models/money.model';
@@ -8,19 +6,18 @@ import { formatMoney } from '@app/common/money/format-money';
import { whenPageMode } from '@app/common/utils';
import { SpaceBetween } from '@app/components/layout/space-between';
import { Caption } from '@app/components/typography';
import { HighFeeDrawer } from '@app/features/high-fee-drawer/high-fee-drawer';
import { PreviewButton } from './preview-button';
export function Footer(props: { balance: Money; url: string }) {
const { balance, url } = props;
export function FormFooter(props: { balance: Money }) {
const { balance } = props;
return (
<Box
bg={color('bg')}
borderTop="1px solid #DCDDE2"
bottom="0px"
height={['128px', '116px']}
height={['106px', '116px']}
position={whenPageMode({
full: 'unset',
popup: 'absolute',
@@ -35,8 +32,6 @@ export function Footer(props: { balance: Money; url: string }) {
<Caption>{formatMoney(balance)}</Caption>
</SpaceBetween>
</Box>
<HighFeeDrawer learnMoreUrl={url} />
<Outlet />
</Box>
);
}

View File

@@ -4,6 +4,7 @@ import { TextInputField } from './text-input-field';
interface RecipientFieldProps {
isDisabled?: boolean;
label?: string;
labelAction?: string;
name: string;
onBlur?(): void;
@@ -13,6 +14,7 @@ interface RecipientFieldProps {
}
export function RecipientField({
isDisabled,
label,
labelAction,
name,
onBlur,
@@ -24,6 +26,7 @@ export function RecipientField({
<TextInputField
dataTestId={SendCryptoAssetSelectors.RecipientFieldInput}
isDisabled={isDisabled}
label={label}
labelAction={labelAction}
minHeight="76px"
name={name}

View File

@@ -1,17 +1,17 @@
import { useCallback } from 'react';
import { FiCopy } from 'react-icons/fi';
import { Box, Stack, color, useClipboard } from '@stacks/ui';
import { Box, Text, color, useClipboard } from '@stacks/ui';
import { SendCryptoAssetSelectors } from '@tests/selectors/send.selectors';
import { useAnalytics } from '@app/common/hooks/analytics/use-analytics';
import { AddressDisplayer } from '@app/components/address-displayer/address-displayer';
import { SpaceBetween } from '@app/components/layout/space-between';
import { Tooltip } from '@app/components/tooltip';
interface BnsAddressDisplayerProps {
interface RecipientAddressDisplayerProps {
address: string;
}
export function RecipientBnsAddressDisplayer({ address }: BnsAddressDisplayerProps) {
export function RecipientAddressDisplayer({ address }: RecipientAddressDisplayerProps) {
const analytics = useAnalytics();
const { onCopy, hasCopied } = useClipboard(address);
@@ -21,16 +21,14 @@ export function RecipientBnsAddressDisplayer({ address }: BnsAddressDisplayerPro
}, [analytics, onCopy]);
return (
<Stack alignItems="center" isInline mb="base">
<Box
<SpaceBetween mb="base" width="100%">
<Text
color={color('text-caption')}
data-testid={SendCryptoAssetSelectors.RecipientBnsAddressLabel}
display="flex"
flexWrap="wrap"
fontSize={1}
justifyContent="flex-start"
fontSize={0}
>
<AddressDisplayer address={address} />
</Box>
{address}
</Text>
<Tooltip hideOnClick={false} label={hasCopied ? 'Copied!' : 'Copy address'} placement="right">
<Box
_hover={{ cursor: 'pointer' }}
@@ -38,12 +36,11 @@ export function RecipientBnsAddressDisplayer({ address }: BnsAddressDisplayerPro
color={color('text-caption')}
data-testid={SendCryptoAssetSelectors.RecipientBnsAddressCopyToClipboard}
onClick={copyToClipboard}
size="16px"
type="button"
>
<FiCopy />
<FiCopy size="16px" />
</Box>
</Tooltip>
</Stack>
</SpaceBetween>
);
}

View File

@@ -0,0 +1,47 @@
import { useCallback, useState } from 'react';
import { useFormikContext } from 'formik';
import { logger } from '@shared/logger';
import { BitcoinSendFormValues, StacksSendFormValues } from '@shared/models/form.model';
import { FormErrorMessages } from '@app/common/error-messages';
import { StacksClient } from '@app/query/stacks/stacks-client';
import { useStacksClientUnanchored } from '@app/store/common/api-clients.hooks';
import { useCurrentNetworkState } from '@app/store/networks/networks.hooks';
// Handles validating the BNS name lookup
export function useRecipientBnsName() {
const { setFieldError, setFieldValue, values } = useFormikContext<
BitcoinSendFormValues | StacksSendFormValues
>();
const [bnsAddress, setBnsAddress] = useState('');
const currentNetwork = useCurrentNetworkState();
const client = useStacksClientUnanchored();
const getBnsAddressAndValidate = useCallback(
async (
fetchFn: (client: StacksClient, name: string, isTestnet?: boolean) => Promise<string | null>
) => {
setBnsAddress('');
if (!values.recipientBnsName) return;
try {
const owner = await fetchFn(client, values.recipientBnsName, currentNetwork.isTestnet);
if (owner) {
setBnsAddress(owner);
setFieldError('recipient', undefined);
setFieldValue('recipient', owner);
} else {
setFieldError('recipientBnsName', FormErrorMessages.BnsAddressNotFound);
}
} catch (e) {
setFieldError('recipientBnsName', FormErrorMessages.BnsAddressNotFound);
logger.error('Error fetching bns address', e);
}
},
[client, currentNetwork.isTestnet, setFieldError, setFieldValue, values.recipientBnsName]
);
return { bnsAddress, getBnsAddressAndValidate, setBnsAddress };
}

View File

@@ -3,19 +3,20 @@ import { useNavigate } from 'react-router-dom';
import { useFormikContext } from 'formik';
import { StacksSendFormValues } from '@shared/models/form.model';
import { BitcoinSendFormValues, StacksSendFormValues } from '@shared/models/form.model';
import { RouteUrls } from '@shared/route-urls';
import { RecipientFieldType } from '@app/pages/send/send-crypto-asset-form/components/recipient-select/recipient-select';
import { useStacksRecipientBnsName } from './use-stacks-recipient-bns-name';
import { useRecipientBnsName } from './use-recipient-bns-name';
export function useStacksRecipientField() {
const { setFieldError, setFieldTouched, setFieldValue } =
useFormikContext<StacksSendFormValues>();
export function useRecipientSelectFields() {
const { setFieldError, setFieldTouched, setFieldValue } = useFormikContext<
BitcoinSendFormValues | StacksSendFormValues
>();
const [selectedRecipientField, setSelectedRecipientField] = useState(RecipientFieldType.Address);
const [isSelectVisible, setIsSelectVisible] = useState(false);
const { setBnsAddress } = useStacksRecipientBnsName();
const { setBnsAddress } = useRecipientBnsName();
const navigate = useNavigate();
const onClickLabelAction = useCallback(() => {

View File

@@ -2,27 +2,32 @@ import { useEffect, useState } from 'react';
import { useFormikContext } from 'formik';
import { StacksSendFormValues } from '@shared/models/form.model';
import { BitcoinSendFormValues, StacksSendFormValues } from '@shared/models/form.model';
import { RecipientField } from '@app/pages/send/send-crypto-asset-form/components/recipient-field';
import { StacksClient } from '@app/query/stacks/stacks-client';
import { useStacksRecipientBnsName } from '../hooks/use-stacks-recipient-bns-name';
import { RecipientBnsAddressDisplayer } from './recipient-bns-address-displayer';
import { RecipientAddressDisplayer } from './components/recipient-address-displayer';
import { useRecipientBnsName } from './hooks/use-recipient-bns-name';
interface RecipientFieldBnsNameProps {
fetchFn(client: StacksClient, name: string, isTestnet?: boolean): Promise<string | null>;
isSelectVisible: boolean;
onClickLabelAction(): void;
selectedRecipientField: number;
topInputOverlay: JSX.Element;
}
export function RecipientFieldBnsName({
fetchFn,
isSelectVisible,
onClickLabelAction,
topInputOverlay,
}: RecipientFieldBnsNameProps) {
const [showBnsAddress, setShowBnsAddress] = useState(false);
const { errors, setFieldError, values } = useFormikContext<StacksSendFormValues>();
const { bnsAddress, getBnsAddressAndValidate } = useStacksRecipientBnsName();
const { errors, setFieldError, values } = useFormikContext<
BitcoinSendFormValues | StacksSendFormValues
>();
const { bnsAddress, getBnsAddressAndValidate } = useRecipientBnsName();
// Apply the recipient field validation to the bns name field
// here so we don't need to validate the bns name on blur.
@@ -46,12 +51,12 @@ export function RecipientFieldBnsName({
isDisabled={isSelectVisible}
labelAction="Select account"
name="recipientBnsName"
onBlur={getBnsAddressAndValidate}
onBlur={() => getBnsAddressAndValidate(fetchFn)}
onClickLabelAction={onClickLabelAction}
placeholder="Enter recipient BNS name"
topInputOverlay={topInputOverlay}
/>
{showBnsAddress ? <RecipientBnsAddressDisplayer address={bnsAddress} /> : null}
{showBnsAddress ? <RecipientAddressDisplayer address={bnsAddress} /> : null}
</>
);
}

View File

@@ -3,6 +3,7 @@ import { FiChevronDown } from 'react-icons/fi';
import { Box, Text, color } from '@stacks/ui';
const labels = ['Address', 'BNS Name'];
const testLabels = ['address', 'bns-name'];
interface RecipientSelectItemProps {
index: number;
@@ -18,6 +19,7 @@ export function RecipientSelectItem(props: RecipientSelectItemProps) {
alignItems="center"
as="button"
bg={color('bg')}
data-testid={`recipient-select-field-${testLabels[index]}`}
display="flex"
height="32px"
mb="0px !important"
@@ -26,10 +28,16 @@ export function RecipientSelectItem(props: RecipientSelectItemProps) {
pl={isVisible ? 'tight' : 'unset'}
type="button"
>
<Text color={color('text-caption')} fontSize={1} fontWeight={500} ml="2px" mr="tight">
<Text
color={isVisible ? color('text-body') : color('accent')}
fontSize={1}
fontWeight={isVisible ? 400 : 500}
ml="2px"
mr="tight"
>
{labels[index]}
</Text>
{isVisible ? <></> : <FiChevronDown />}
{isVisible ? <></> : <FiChevronDown color={color('accent')} />}
</Box>
);
}

View File

@@ -1,5 +1,5 @@
import { RecipientSelectItem } from './recipient-select-item';
import { RecipientSelectLayout } from './recipient-select.layout';
import { RecipientSelectItem } from './components/recipient-select-item';
import { RecipientSelectLayout } from './components/recipient-select.layout';
export enum RecipientFieldType {
Address,

View File

@@ -1,13 +1,23 @@
import { Box } from '@stacks/ui';
import { Flex } from '@stacks/ui';
import { SendCryptoAssetSelectors } from '@tests/selectors/send.selectors';
interface SendCryptoAssetFormLayoutProps {
children: JSX.Element;
children: React.ReactNode;
}
export function SendCryptoAssetFormLayout({ children }: SendCryptoAssetFormLayoutProps) {
return (
<Box data-testid={SendCryptoAssetSelectors.SendForm} pb="base" width="100%">
<Flex
alignItems="center"
data-testId={SendCryptoAssetSelectors.SendForm}
flexDirection="column"
maxHeight={['calc(100vh - 116px)', 'calc(85vh - 116px)']}
overflowY="scroll"
pb={['120px', '48px']}
pt={['base', '48px']}
px="loose"
width="100%"
>
{children}
</Box>
</Flex>
);
}

View File

@@ -88,6 +88,7 @@ export function TextInputField({
as="button"
color={color('accent')}
fontSize={1}
fontWeight={500}
onClick={onClickLabelAction}
type="button"
zIndex={999}

View File

@@ -0,0 +1,52 @@
import { RecipientFieldType } from '@app/pages/send/send-crypto-asset-form/components/recipient-select/recipient-select';
import { fetchBtcNameOwner } from '@app/query/stacks/bns/bns.utils';
import { useRecipientSelectFields } from '../../../components/recipient-select-fields/hooks/use-recipient-select-fields';
import { RecipientFieldAddress } from '../../../components/recipient-select-fields/recipient-field-address';
import { RecipientFieldBnsName } from '../../../components/recipient-select-fields/recipient-field-bns-name';
import { RecipientSelectOverlay } from '../../../components/recipient-select/components/recipient-select-overlay';
export function BitcoinRecipientField() {
const {
isSelectVisible,
onClickLabelAction,
onSelectRecipientFieldType,
onSetIsSelectVisible,
selectedRecipientField,
} = useRecipientSelectFields();
const topInputOverlay = (
<RecipientSelectOverlay
isSelectVisible={isSelectVisible}
onSelectRecipientFieldType={onSelectRecipientFieldType}
onSetIsSelectVisible={onSetIsSelectVisible}
selectedRecipientField={selectedRecipientField}
/>
);
const recipientFieldAddress = (
<RecipientFieldAddress
isSelectVisible={isSelectVisible}
onClickLabelAction={onClickLabelAction}
selectedRecipientField={selectedRecipientField}
topInputOverlay={topInputOverlay}
/>
);
switch (selectedRecipientField) {
case RecipientFieldType.Address:
return recipientFieldAddress;
case RecipientFieldType.BnsName:
return (
<RecipientFieldBnsName
fetchFn={fetchBtcNameOwner}
isSelectVisible={isSelectVisible}
onClickLabelAction={onClickLabelAction}
selectedRecipientField={selectedRecipientField}
topInputOverlay={topInputOverlay}
/>
);
default:
return recipientFieldAddress;
}
}

View File

@@ -5,7 +5,7 @@ import { WarningLabel } from '@app/components/warning-label';
export function TestnetBtcMessage() {
return (
<WarningLabel mt="base-loose" width="100%">
<WarningLabel mb="base">
This is a Bitcoin testnet transaction. Funds have no value.{' '}
<ExternalLink
href="https://coinfaucet.eu/en/btc-testnet"

View File

@@ -1,9 +1,10 @@
import { RecipientFieldType } from '@app/pages/send/send-crypto-asset-form/components/recipient-select/recipient-select';
import { fetchNameOwner } from '@app/query/stacks/bns/bns.utils';
import { RecipientFieldAddress } from './components/recipient-field-address';
import { RecipientFieldBnsName } from './components/recipient-field-bns-name';
import { RecipientSelectOverlay } from './components/recipient-select-overlay';
import { useStacksRecipientField } from './hooks/use-stacks-recipient-field';
import { useRecipientSelectFields } from '../../../components/recipient-select-fields/hooks/use-recipient-select-fields';
import { RecipientFieldAddress } from '../../../components/recipient-select-fields/recipient-field-address';
import { RecipientFieldBnsName } from '../../../components/recipient-select-fields/recipient-field-bns-name';
import { RecipientSelectOverlay } from '../../../components/recipient-select/components/recipient-select-overlay';
export function StacksRecipientField() {
const {
@@ -12,7 +13,7 @@ export function StacksRecipientField() {
onSelectRecipientFieldType,
onSetIsSelectVisible,
selectedRecipientField,
} = useStacksRecipientField();
} = useRecipientSelectFields();
const topInputOverlay = (
<RecipientSelectOverlay
@@ -38,6 +39,7 @@ export function StacksRecipientField() {
case RecipientFieldType.BnsName:
return (
<RecipientFieldBnsName
fetchFn={fetchNameOwner}
isSelectVisible={isSelectVisible}
onClickLabelAction={onClickLabelAction}
selectedRecipientField={selectedRecipientField}

View File

@@ -1,61 +0,0 @@
// TODO: Remove with old recipient field in btc form
import { useCallback } from 'react';
import { FiCopy, FiInfo } from 'react-icons/fi';
import { Box, Stack, Text, Tooltip, color, useClipboard } from '@stacks/ui';
import { truncateMiddle } from '@stacks/ui-utils';
import { SendCryptoAssetSelectors } from '@tests/selectors/send.selectors';
import { useAnalytics } from '@app/common/hooks/analytics/use-analytics';
export function RecipientFieldBnsAddress(props: { bnsAddress: string }) {
const { bnsAddress } = props;
const analytics = useAnalytics();
const { onCopy, hasCopied } = useClipboard(bnsAddress);
const copyToClipboard = useCallback(() => {
void analytics.track('copy_resolved_address_to_clipboard');
onCopy();
}, [analytics, onCopy]);
const onHover = useCallback(
() => analytics.track('view_resolved_recipient_address'),
[analytics]
);
return (
<Stack isInline spacing="tight" zIndex={999}>
<Text
color={color('text-caption')}
data-testid={SendCryptoAssetSelectors.RecipientBnsAddressLabel}
fontSize={0}
>
{truncateMiddle(bnsAddress, 4)}
</Text>
<Tooltip label={bnsAddress} maxWidth="none" placement="bottom">
<Stack>
<Box
_hover={{ cursor: 'pointer' }}
as={FiInfo}
color={color('text-caption')}
data-testid={SendCryptoAssetSelectors.RecipientBnsAddressInfoIcon}
onMouseOver={onHover}
size="12px"
/>
</Stack>
</Tooltip>
<Tooltip label={hasCopied ? 'Copied!' : 'Copy address'} placement="right">
<Stack>
<Box
_hover={{ cursor: 'pointer' }}
as={FiCopy}
color={color('text-caption')}
data-testid={SendCryptoAssetSelectors.RecipientBnsAddressCopyToClipboard}
onClick={copyToClipboard}
size="12px"
/>
</Stack>
</Tooltip>
</Stack>
);
}

View File

@@ -1,40 +0,0 @@
import { useCallback, useState } from 'react';
import { useFormikContext } from 'formik';
import { logger } from '@shared/logger';
import { StacksSendFormValues } from '@shared/models/form.model';
import { FormErrorMessages } from '@app/common/error-messages';
import { fetchNameOwner } from '@app/query/stacks/bns/bns.utils';
import { useStacksClientUnanchored } from '@app/store/common/api-clients.hooks';
import { useCurrentNetworkState } from '@app/store/networks/networks.hooks';
// Handles validating the BNS name lookup
export function useStacksRecipientBnsName() {
const { setFieldError, setFieldValue, values } = useFormikContext<StacksSendFormValues>();
const [bnsAddress, setBnsAddress] = useState('');
const currentNetwork = useCurrentNetworkState();
const client = useStacksClientUnanchored();
const getBnsAddressAndValidate = useCallback(async () => {
setBnsAddress('');
if (!values.recipientBnsName) return;
try {
const owner = await fetchNameOwner(client, values.recipientBnsName, currentNetwork.isTestnet);
if (owner) {
setBnsAddress(owner);
setFieldError('recipient', undefined);
setFieldValue('recipient', owner);
} else {
setFieldError('recipientBnsName', FormErrorMessages.BnsAddressNotFound);
}
} catch (e) {
setFieldError('recipientBnsName', FormErrorMessages.BnsAddressNotFound);
logger.error('Error fetching bns address', e);
}
}, [client, currentNetwork.isTestnet, setFieldError, setFieldValue, values.recipientBnsName]);
return { bnsAddress, getBnsAddressAndValidate, setBnsAddress };
}

View File

@@ -12,19 +12,15 @@ import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/marke
import { useCurrentBtcNativeSegwitAccountAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
import { AmountField } from '../../components/amount-field';
import { AvailableBalance } from '../../components/available-balance';
import { FormErrors } from '../../components/form-errors';
import { FormFieldsLayout } from '../../components/form-fields.layout';
import { PreviewButton } from '../../components/preview-button';
import { FormFooter } from '../../components/form-footer';
import { SelectedAssetField } from '../../components/selected-asset-field';
import { SendCryptoAssetFormLayout } from '../../components/send-crypto-asset-form.layout';
import { SendFiatValue } from '../../components/send-fiat-value';
import { SendMaxButton } from '../../components/send-max-button';
import { useCalculateMaxBitcoinSpend } from '../../family/bitcoin/hooks/use-calculate-max-spend';
import { BitcoinRecipientField } from '../../family/bitcoin/components/bitcoin-recipient-field';
import { TestnetBtcMessage } from '../../family/bitcoin/components/testnet-btc-message';
import { useSendFormRouteState } from '../../hooks/use-send-form-route-state';
import { createDefaultInitialFormValues, defaultSendFormFormikProps } from '../../send-form.utils';
import { BtcRecipientField } from './components/btc-recipient-field';
import { TestnetBtcMessage } from './components/testnet-btc-message';
import { useBtcSendForm } from './use-btc-send-form';
export function BtcSendForm() {
@@ -34,17 +30,21 @@ export function BtcSendForm() {
const currentAccountBtcAddress = useCurrentBtcNativeSegwitAccountAddressIndexZero();
const btcBalance = useNativeSegwitBalance(currentAccountBtcAddress);
const calcMaxSpend = useCalculateMaxBitcoinSpend();
const { validationSchema, currentNetwork, formRef, previewTransaction, onFormStateChange } =
useBtcSendForm();
const {
calcMaxSpend,
currentNetwork,
formRef,
onFormStateChange,
previewTransaction,
validationSchema,
} = useBtcSendForm();
return (
<SendCryptoAssetFormLayout>
<Box width="100%" pb="base">
<Formik
initialValues={createDefaultInitialFormValues({
...routeState,
recipientAddressOrBnsName: '',
recipientBnsName: '',
})}
onSubmit={previewTransaction}
validationSchema={validationSchema}
@@ -55,36 +55,33 @@ export function BtcSendForm() {
onFormStateChange(props.values);
return (
<Form>
<AmountField
balance={btcBalance.balance}
switchableAmount={<SendFiatValue marketData={btcMarketData} assetSymbol={'BTC'} />}
bottomInputOverlay={
<SendMaxButton
balance={btcBalance.balance}
sendMaxBalance={
calcMaxSpend(props.values.recipient)?.spendableBitcoin.toString() ?? '0'
}
/>
}
autoComplete="off"
/>
<FormFieldsLayout>
<SendCryptoAssetFormLayout>
<AmountField
balance={btcBalance.balance}
switchableAmount={
<SendFiatValue marketData={btcMarketData} assetSymbol={'BTC'} />
}
bottomInputOverlay={
<SendMaxButton
balance={btcBalance.balance}
sendMaxBalance={
calcMaxSpend(props.values.recipient)?.spendableBitcoin.toString() ?? '0'
}
/>
}
autoComplete="off"
/>
<SelectedAssetField icon={<BtcIcon />} name={btcBalance.asset.name} symbol="BTC" />
{/* TODO: Implement new recipient field here */}
<BtcRecipientField />
</FormFieldsLayout>
{currentNetwork.chain.bitcoin.network === 'testnet' && <TestnetBtcMessage />}
<FormErrors />
<PreviewButton />
<Box my="base">
<AvailableBalance availableBalance={btcBalance.balance} />
</Box>
<BitcoinRecipientField />
{currentNetwork.chain.bitcoin.network === 'testnet' && <TestnetBtcMessage />}
</SendCryptoAssetFormLayout>
<FormFooter balance={btcBalance.balance} />
<HighFeeDrawer learnMoreUrl={HIGH_FEE_WARNING_LEARN_MORE_URL_BTC} />
<Outlet />
</Form>
);
}}
</Formik>
</SendCryptoAssetFormLayout>
</Box>
);
}

View File

@@ -1,64 +0,0 @@
import { useCallback, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useField } from 'formik';
import { RouteUrls } from '@shared/route-urls';
import { fetchBtcNameOwner } from '@app/query/stacks/bns/bns.utils';
import { useStacksClientUnanchored } from '@app/store/common/api-clients.hooks';
import { RecipientField } from '../../../components/recipient-field';
import { RecipientFieldBnsAddress } from '../../../family/stacks/components/stacks-recipient-field/components/recipient-bns-address';
export function BtcRecipientField() {
const client = useStacksClientUnanchored();
const [recipientAddressOrBnsNameField] = useField('recipientAddressOrBnsName');
const [, _, recipientFieldHelpers] = useField('recipient');
const navigate = useNavigate();
const [bnsAddress, setBnsAddress] = useState('');
const [lastValidatedInput, setLastValidatedInput] = useState('');
const getBtcAddressFromBns = useCallback(async () => {
// Skip if this input was already handled
if (lastValidatedInput === recipientAddressOrBnsNameField.value) return;
setBnsAddress('');
setLastValidatedInput(recipientAddressOrBnsNameField.value);
try {
const btcFromBns = await fetchBtcNameOwner(client, recipientAddressOrBnsNameField.value);
if (btcFromBns) {
recipientFieldHelpers.setValue(btcFromBns);
setBnsAddress(btcFromBns);
} else {
recipientFieldHelpers.setValue(recipientAddressOrBnsNameField.value);
}
} catch (error) {
recipientFieldHelpers.setValue(recipientAddressOrBnsNameField.value);
}
}, [
client,
recipientAddressOrBnsNameField,
recipientFieldHelpers,
lastValidatedInput,
setLastValidatedInput,
]);
const onClickLabel = () => {
setBnsAddress('');
navigate(RouteUrls.SendCryptoAssetFormRecipientAccounts);
};
return (
<RecipientField
labelAction="Choose account"
name="recipientAddressOrBnsName"
onBlur={getBtcAddressFromBns}
onClickLabelAction={onClickLabel}
placeholder="Address"
topInputOverlay={
!!bnsAddress ? <RecipientFieldBnsAddress bnsAddress={bnsAddress} /> : undefined
}
/>
);
}

View File

@@ -21,20 +21,17 @@ import {
btcMinimumSpendValidator,
} from '@app/common/validation/forms/amount-validators';
import { btcAmountPrecisionValidator } from '@app/common/validation/forms/currency-validators';
import { btcRecipientAddressOrBnsNameValidator } from '@app/common/validation/forms/recipient-validators';
import { useUpdatePersistedSendFormValues } from '@app/features/popup-send-form-restoration/use-update-persisted-send-form-values';
import { useNativeSegwitBalance } from '@app/query/bitcoin/balance/bitcoin-balances.query';
import { useCurrentBtcNativeSegwitAccountAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
import { useStacksClientUnanchored } from '@app/store/common/api-clients.hooks';
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
import { useCalculateMaxBitcoinSpend } from '../../family/bitcoin/hooks/use-calculate-max-spend';
import { useGenerateSignedBitcoinTx } from '../../family/bitcoin/hooks/use-generate-bitcoin-tx';
import { useSendFormNavigate } from '../../hooks/use-send-form-navigate';
import { useGenerateSignedBitcoinTx } from './use-generate-bitcoin-tx';
export function useBtcSendForm() {
const formRef = useRef<FormikProps<BitcoinSendFormValues>>(null);
const currentNetwork = useCurrentNetwork();
const currentAccountBtcAddress = useCurrentBtcNativeSegwitAccountAddressIndexZero();
const btcCryptoCurrencyAssetBalance = useNativeSegwitBalance(currentAccountBtcAddress);
@@ -44,14 +41,12 @@ export function useBtcSendForm() {
const generateTx = useGenerateSignedBitcoinTx();
const calcMaxSpend = useCalculateMaxBitcoinSpend();
const { onFormStateChange } = useUpdatePersistedSendFormValues();
const client = useStacksClientUnanchored();
return {
formRef,
onFormStateChange,
calcMaxSpend,
currentNetwork,
formRef,
onFormStateChange,
validationSchema: yup.object({
amount: yup
@@ -68,10 +63,6 @@ export function useBtcSendForm() {
})
)
.concat(btcMinimumSpendValidator()),
// TODO: Implement new recipient field here
recipientAddressOrBnsName: btcRecipientAddressOrBnsNameValidator({
client,
}),
recipient: yup
.string()
.concat(btcAddressValidator())

View File

@@ -1,5 +1,6 @@
import { Navigate, useNavigate } from 'react-router-dom';
import { Navigate, Outlet, useNavigate } from 'react-router-dom';
import { Box } from '@stacks/ui';
import { Form, Formik } from 'formik';
import { HIGH_FEE_WARNING_LEARN_MORE_URL_STX } from '@shared/constants';
@@ -10,16 +11,16 @@ import { StxAvatar } from '@app/components/crypto-assets/stacks/components/stx-a
import { EditNonceButton } from '@app/components/edit-nonce-button';
import { FeesRow } from '@app/components/fees-row/fees-row';
import { NonceSetter } from '@app/components/nonce-setter';
import { HighFeeDrawer } from '@app/features/high-fee-drawer/high-fee-drawer';
import { useUpdatePersistedSendFormValues } from '@app/features/popup-send-form-restoration/use-update-persisted-send-form-values';
import { AmountField } from '../../components/amount-field';
import { Footer } from '../../components/footer';
import { FormFieldsLayout } from '../../components/form-fields.layout';
import { FormFooter } from '../../components/form-footer';
import { MemoField } from '../../components/memo-field';
import { SelectedAssetField } from '../../components/selected-asset-field';
import { SendCryptoAssetFormLayout } from '../../components/send-crypto-asset-form.layout';
import { SendMaxButton } from '../../components/send-max-button';
import { StacksRecipientField } from '../../family/stacks/components/stacks-recipient-field/stacks-recipient-field';
import { StacksRecipientField } from '../../family/stacks/components/stacks-recipient-field';
import { defaultSendFormFormikProps } from '../../send-form.utils';
import { useSip10SendForm } from './use-sip10-send-form';
@@ -42,7 +43,7 @@ export function StacksSip10FungibleTokenSendForm({}) {
}
return (
<SendCryptoAssetFormLayout>
<Box width="100%" pb="base">
<Formik
initialValues={initialValues}
onSubmit={async (values, formikHelpers) => await previewTransaction(values, formikHelpers)}
@@ -54,7 +55,7 @@ export function StacksSip10FungibleTokenSendForm({}) {
return (
<NonceSetter>
<Form>
<FormFieldsLayout>
<SendCryptoAssetFormLayout>
<AmountField
balance={availableTokenBalance}
bottomInputOverlay={
@@ -70,17 +71,18 @@ export function StacksSip10FungibleTokenSendForm({}) {
<FeesRow fees={stacksFtFees} isSponsored={false} mt="base" />
<EditNonceButton
alignSelf="flex-end"
mb="extra-loose"
mt="base"
onEditNonce={() => navigate(RouteUrls.EditNonce)}
/>
</FormFieldsLayout>
<Footer balance={availableTokenBalance} url={HIGH_FEE_WARNING_LEARN_MORE_URL_STX} />
</SendCryptoAssetFormLayout>
<FormFooter balance={availableTokenBalance} />
<HighFeeDrawer learnMoreUrl={HIGH_FEE_WARNING_LEARN_MORE_URL_STX} />
<Outlet />
</Form>
</NonceSetter>
);
}}
</Formik>
</SendCryptoAssetFormLayout>
</Box>
);
}

View File

@@ -31,10 +31,10 @@ import {
useGenerateFtTokenTransferUnsignedTx,
} from '@app/store/transactions/token-transfer.hooks';
import { useStacksFtRouteState } from '../../family/stacks/hooks/use-stacks-ft-params';
import { useSendFormNavigate } from '../../hooks/use-send-form-navigate';
import { useSendFormRouteState } from '../../hooks/use-send-form-route-state';
import { createDefaultInitialFormValues } from '../../send-form.utils';
import { useStacksFtRouteState } from './use-stacks-ft-params';
export function useSip10SendForm() {
const [contractId, setContractId] = useState('');

View File

@@ -1,5 +1,6 @@
import { useNavigate } from 'react-router-dom';
import { Outlet, useNavigate } from 'react-router-dom';
import { Box } from '@stacks/ui';
import { Form, Formik } from 'formik';
import { HIGH_FEE_WARNING_LEARN_MORE_URL_STX } from '@shared/constants';
@@ -9,17 +10,17 @@ import { StxAvatar } from '@app/components/crypto-assets/stacks/components/stx-a
import { EditNonceButton } from '@app/components/edit-nonce-button';
import { FeesRow } from '@app/components/fees-row/fees-row';
import { NonceSetter } from '@app/components/nonce-setter';
import { HighFeeDrawer } from '@app/features/high-fee-drawer/high-fee-drawer';
import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks';
import { AmountField } from '../../components/amount-field';
import { Footer } from '../../components/footer';
import { FormFieldsLayout } from '../../components/form-fields.layout';
import { FormFooter } from '../../components/form-footer';
import { MemoField } from '../../components/memo-field';
import { SelectedAssetField } from '../../components/selected-asset-field';
import { SendCryptoAssetFormLayout } from '../../components/send-crypto-asset-form.layout';
import { SendFiatValue } from '../../components/send-fiat-value';
import { SendMaxButton } from '../../components/send-max-button';
import { StacksRecipientField } from '../../family/stacks/components/stacks-recipient-field/stacks-recipient-field';
import { StacksRecipientField } from '../../family/stacks/components/stacks-recipient-field';
import { defaultSendFormFormikProps } from '../../send-form.utils';
import { useStxSendForm } from './use-stx-send-form';
@@ -38,7 +39,7 @@ export function StxSendForm() {
} = useStxSendForm();
return (
<SendCryptoAssetFormLayout>
<Box width="100%" pb="base">
<Formik
initialValues={initialValues}
onSubmit={previewTransaction}
@@ -50,7 +51,7 @@ export function StxSendForm() {
return (
<NonceSetter>
<Form>
<FormFieldsLayout>
<SendCryptoAssetFormLayout>
<AmountField
balance={availableStxBalance}
switchableAmount={
@@ -70,17 +71,18 @@ export function StxSendForm() {
<FeesRow fees={stxFees} isSponsored={false} mt="tight" />
<EditNonceButton
alignSelf="flex-end"
mb="extra-loose"
mt="base"
onEditNonce={() => navigate(RouteUrls.EditNonce)}
/>
</FormFieldsLayout>
<Footer balance={availableStxBalance} url={HIGH_FEE_WARNING_LEARN_MORE_URL_STX} />
</SendCryptoAssetFormLayout>
<FormFooter balance={availableStxBalance} />
<HighFeeDrawer learnMoreUrl={HIGH_FEE_WARNING_LEARN_MORE_URL_STX} />
<Outlet />
</Form>
</NonceSetter>
);
}}
</Formik>
</SendCryptoAssetFormLayout>
</Box>
);
}

View File

@@ -5,6 +5,7 @@ export interface BitcoinSendFormValues {
feeType: string;
memo: string;
recipient: string;
recipientBnsName: string;
symbol: string;
}

View File

@@ -1,8 +1,6 @@
import { Locator, Page } from '@playwright/test';
import { CryptoAssetSelectors } from '@tests/selectors/crypto-asset.selectors';
import { SendCryptoAssetSelectors } from '@tests/selectors/send.selectors';
import { SharedComponentsSelectors } from '@tests/selectors/shared-component.selectors';
import { createTestSelector } from '@tests/utils';
import { RouteUrls } from '@shared/route-urls';
@@ -16,13 +14,13 @@ export class SendPage {
readonly memoInput: Locator;
readonly previewSendTxButton: Locator;
readonly recipientChooseAccountButton: Locator;
readonly recipientSelectFieldAddress: Locator;
readonly recipientSelectFieldBnsName: Locator;
readonly recipientInput: Locator;
readonly recipientBnsAddressLabel: Locator;
readonly recipientBnsAddressInfoIcon: Locator;
readonly sendMaxButton: Locator;
readonly feesRow: Locator;
readonly memoRow: Locator;
readonly feesSelector: string = createTestSelector(SharedComponentsSelectors.FeeRow);
constructor(page: Page) {
this.page = page;
@@ -40,13 +38,16 @@ export class SendPage {
this.recipientChooseAccountButton = page.getByTestId(
SendCryptoAssetSelectors.RecipientChooseAccountButton
);
this.recipientSelectFieldAddress = this.page.getByTestId(
SendCryptoAssetSelectors.RecipientSelectFieldAddress
);
this.recipientSelectFieldBnsName = this.page.getByTestId(
SendCryptoAssetSelectors.RecipientSelectFieldBnsName
);
this.recipientInput = this.page.getByTestId(SendCryptoAssetSelectors.RecipientFieldInput);
this.recipientBnsAddressLabel = this.page.getByTestId(
SendCryptoAssetSelectors.RecipientBnsAddressLabel
);
this.recipientBnsAddressInfoIcon = page.getByTestId(
SendCryptoAssetSelectors.RecipientBnsAddressInfoIcon
);
this.feesRow = page.getByTestId(SendCryptoAssetSelectors.ConfirmationDetailsFee);
this.memoRow = page.getByTestId(SendCryptoAssetSelectors.ConfirmationDetailsMemo);
@@ -70,8 +71,4 @@ export class SendPage {
await this.page.waitForURL('**' + `${RouteUrls.SendCryptoAsset}/stx`);
await this.page.getByTestId(SendCryptoAssetSelectors.SendForm).waitFor();
}
async waitForFeesSelector() {
await this.page.waitForSelector(this.feesSelector, { timeout: 30000 });
}
}

View File

@@ -13,9 +13,10 @@ export enum SendCryptoAssetSelectors {
MemoFieldInput = 'memo-field-input',
PreviewSendTxBtn = 'preview-send-tx-btn',
RecipientChooseAccountButton = 'recipient-choose-account-button',
RecipientSelectFieldAddress = 'recipient-select-field-address',
RecipientSelectFieldBnsName = 'recipient-select-field-bns-name',
RecipientFieldInput = 'recipient-field-input',
RecipientBnsAddressLabel = 'recipient-bns-address-label',
RecipientBnsAddressInfoIcon = 'recipient-bns-address-info-icon',
RecipientBnsAddressCopyToClipboard = 'recipient-bns-address-copy-to-clipboard',
SendForm = 'send-form',
SendMaxBtn = 'send-max-btn',

View File

@@ -7,7 +7,6 @@ import { FormErrorMessages } from '@app/common/error-messages';
import { test } from '../../fixtures/fixtures';
test.describe('send btc', () => {
// TODO: Don't run these if we disable bitcoin sending?
test.beforeEach(async ({ extensionId, globalPage, homePage, onboardingPage, sendPage }) => {
await globalPage.setupAndUseApiCalls(extensionId);
await onboardingPage.signInExistingUser();

View File

@@ -31,26 +31,29 @@ test.describe('send stx', () => {
test('that recipient address matches bns name', async ({ page, sendPage }) => {
await sendPage.amountInput.fill('.0001');
await sendPage.recipientSelectFieldAddress.click();
await sendPage.recipientSelectFieldBnsName.click();
await sendPage.recipientInput.fill(TEST_BNS_NAME);
await sendPage.recipientInput.blur();
await sendPage.recipientBnsAddressLabel.waitFor();
await sendPage.recipientBnsAddressInfoIcon.hover();
const bnsResolvedAddress = await page.getByText(TEST_BNS_RESOLVED_ADDRESS).innerText();
test.expect(bnsResolvedAddress).toBeTruthy();
});
test('that fee row defaults to middle fee estimation', async ({ page }) => {
await page.getByTestId(SharedComponentsSelectors.FeeToBePaidLabel).scrollIntoViewIfNeeded();
const feeToBePaid = await page
.getByTestId(SharedComponentsSelectors.FeeToBePaidLabel)
.innerText();
const fee = Number(feeToBePaid.split(' ')[0]);
// Using min/max fee caps
const isMiddleFee = fee >= 0.003 && fee < 0.75;
const isMiddleFee = fee >= 0.003 && fee <= 0.75;
test.expect(isMiddleFee).toBeTruthy();
});
test('that low fee estimate can be selected', async ({ page }) => {
await page.getByTestId(SharedComponentsSelectors.FeeToBePaidLabel).scrollIntoViewIfNeeded();
await page.getByTestId(SharedComponentsSelectors.MiddleFeeEstimateItem).click();
await page.getByTestId(SharedComponentsSelectors.LowFeeEstimateItem).click();
const feeToBePaid = await page
@@ -58,7 +61,7 @@ test.describe('send stx', () => {
.innerText();
const fee = Number(feeToBePaid.split(' ')[0]);
// Using min/max fee caps
const isLowFee = fee >= 0.0025 && fee < 0.5;
const isLowFee = fee >= 0.0025 && fee <= 0.5;
test.expect(isLowFee).toBeTruthy();
});
});
@@ -117,21 +120,22 @@ test.describe('send stx', () => {
});
test.describe('send form preview', () => {
test('that it shows preview of tx details to be confirmed', async ({ sendPage }) => {
test('that it shows preview of tx details to be confirmed', async ({ page, sendPage }) => {
await sendPage.amountInput.fill('0.000001');
await sendPage.recipientInput.fill(TEST_ACCOUNT_2_STX_ADDRESS);
await sendPage.waitForFeesSelector();
await page.getByTestId(SharedComponentsSelectors.FeeToBePaidLabel).scrollIntoViewIfNeeded();
await sendPage.previewSendTxButton.click();
const details = await sendPage.confirmationDetails.allInnerTexts();
test.expect(details).toBeTruthy();
});
test('that it shows preview of tx details after validation error is resolved', async ({
page,
sendPage,
}) => {
await sendPage.amountInput.fill('0.0000001');
await sendPage.recipientInput.fill(TEST_ACCOUNT_2_STX_ADDRESS);
await sendPage.waitForFeesSelector();
await page.getByTestId(SharedComponentsSelectors.FeeToBePaidLabel).scrollIntoViewIfNeeded();
await sendPage.previewSendTxButton.click();
const errorMsg = await sendPage.amountInputErrorLabel.innerText();
test.expect(errorMsg).toEqual(FormErrorMessages.MustBePositive);
@@ -143,6 +147,7 @@ test.describe('send stx', () => {
});
test('that asset value, recipient, memo and fees on preview match input', async ({
page,
sendPage,
}) => {
const amount = '0.000001';
@@ -151,7 +156,7 @@ test.describe('send stx', () => {
await sendPage.amountInput.fill(amount);
await sendPage.recipientInput.fill(TEST_ACCOUNT_2_STX_ADDRESS);
await sendPage.memoInput.fill(memo);
await sendPage.waitForFeesSelector();
await page.getByTestId(SharedComponentsSelectors.FeeToBePaidLabel).scrollIntoViewIfNeeded();
const fees = await sendPage.page
.getByTestId(SharedComponentsSelectors.FeeToBePaidLabel)
.innerText();
@@ -177,13 +182,13 @@ test.describe('send stx', () => {
test.expect(confirmationMemo).toEqual(memo);
});
test('that empty memo on preview matches default empty value', async ({ sendPage }) => {
test('that empty memo on preview matches default empty value', async ({ page, sendPage }) => {
const amount = '0.000001';
const emptyMemoPreviewValue = 'No memo';
await sendPage.amountInput.fill(amount);
await sendPage.recipientInput.fill(TEST_ACCOUNT_2_STX_ADDRESS);
await sendPage.waitForFeesSelector();
await page.getByTestId(SharedComponentsSelectors.FeeToBePaidLabel).scrollIntoViewIfNeeded();
await sendPage.previewSendTxButton.click();
const confirmationMemo = await sendPage.memoRow