Skip to content

Commit

Permalink
feat: support bns recipients, closes #1840
Browse files Browse the repository at this point in the history
  • Loading branch information
edu-stx committed Nov 10, 2022
1 parent 37f0c10 commit e5ddf57
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 22 deletions.
78 changes: 72 additions & 6 deletions src/app/pages/send-tokens/components/recipient-field.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import { memo } from 'react';
import { memo, useCallback } from 'react';
import { useState } from 'react';
import { FiCopy, FiInfo } from 'react-icons/fi';

import { Input, InputGroup, Stack, StackProps, Text } from '@stacks/ui';
import { Box, Input, InputGroup, Stack, StackProps, Text, useClipboard } from '@stacks/ui';
import { color, truncateMiddle } from '@stacks/ui-utils';
import { SendFormSelectors } from '@tests/page-objects/send-form.selectors';
import { useFormikContext } from 'formik';

import { SendFormValues } from '@shared/models/form.model';

import { useAnalytics } from '@app/common/hooks/analytics/use-analytics';
import { ErrorLabel } from '@app/components/error-label';
import { Tooltip } from '@app/components/tooltip';
import { Caption } from '@app/components/typography';
import { useStacksClient } from '@app/store/common/api-clients.hooks';

interface RecipientField extends StackProps {
error?: string;
Expand All @@ -14,7 +23,21 @@ interface RecipientField extends StackProps {
// TODO: this should use a new "Field" component (with inline label like in figma)
function RecipientFieldBase(props: RecipientField) {
const { error, value, ...rest } = props;
const { handleChange } = useFormikContext();
const { handleChange, values, setFieldValue } = useFormikContext<SendFormValues>();
const client = useStacksClient();
const [resolvedBnsAddress, setResolvedBnsAddres] = useState('');
const { onCopy, hasCopied } = useClipboard(resolvedBnsAddress);
const analytics = useAnalytics();

const copyToClipboard = () => {
void analytics.track('copy_resolved_address_to_clipboard');
onCopy();
};

const onHover = useCallback(
() => analytics.track('view_resolved_recipient_address'),
[analytics]
);

return (
<Stack width="100%" {...rest}>
Expand All @@ -25,22 +48,65 @@ function RecipientFieldBase(props: RecipientField) {
mb="tight"
fontSize={1}
fontWeight="500"
htmlFor="recipient"
htmlFor="recipientAddressOrBnsName"
>
Recipient
</Text>
<Input
display="block"
type="string"
width="100%"
name="recipient"
name="recipientAddressOrBnsName"
value={value}
onChange={handleChange}
placeholder="Enter an address"
onBlur={async () => {
setResolvedBnsAddres('');
try {
const res = await client.namesApi.getNameInfo({ name: value ?? '' });
if (res.address) {
setFieldValue('recipient', res.address);
setResolvedBnsAddres(res.address);
} else {
setFieldValue('recipient', values.recipientAddressOrBnsName);
}
return;
} catch {
// do nothing
}
setFieldValue('recipient', values.recipientAddressOrBnsName);
}}
placeholder="Enter an address or BNS name"
autoComplete="off"
data-testid={SendFormSelectors.InputRecipientField}
/>
</InputGroup>
{Boolean(resolvedBnsAddress) && (
<Stack isInline spacing="tight">
<Caption display="inline">{truncateMiddle(resolvedBnsAddress, 12)}</Caption>
<Tooltip label={resolvedBnsAddress} placement="bottom">
<Stack display="inline-flex">
<Box
_hover={{ cursor: 'pointer' }}
onMouseOver={onHover}
as={FiInfo}
color={color('text-caption')}
size="14px"
/>
</Stack>
</Tooltip>
<Tooltip placement="right" label={hasCopied ? 'Copied!' : 'Copy address'}>
<Stack>
<Box
_hover={{ cursor: 'pointer' }}
onClick={copyToClipboard}
size="12px"
color={color('text-caption')}
as={FiCopy}
/>
</Stack>
</Tooltip>
</Stack>
)}
{error && (
<ErrorLabel data-testid={SendFormSelectors.InputRecipientFieldErrorLabel}>
<Text textStyle="caption">{error}</Text>
Expand Down
2 changes: 1 addition & 1 deletion src/app/pages/send-tokens/components/send-form-inner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export function SendFormInner(props: SendFormInnerProps) {
<Suspense fallback={<></>}>
<AmountField error={errors.amount} value={values.amount || 0} />
</Suspense>
<RecipientField error={errors.recipient} value={values.recipient} />
<RecipientField error={errors.recipient} value={values.recipientAddressOrBnsName} />
{selectedAssetBalance?.asset.hasMemo && <MemoField value={values.memo} error={errors.memo} />}
{selectedAssetBalance?.asset.hasMemo && selectedAssetBalance?.asset.symbol && (
<SendFormMemoWarning symbol={selectedAssetBalance?.asset.symbol} />
Expand Down
48 changes: 33 additions & 15 deletions src/app/pages/send-tokens/hooks/use-send-form-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { useFeeSchema } from '@app/common/validation/use-fee-schema';
import { transactionMemoSchema } from '@app/common/validation/validate-memo';
import { useSelectedAssetBalance } from '@app/pages/send-tokens/hooks/use-selected-asset-balance';
import { useCurrentAccountAvailableStxBalance } from '@app/query/stacks/balance/balance.hooks';
import { useStacksClient } from '@app/store/common/api-clients.hooks';

interface UseSendFormValidationArgs {
selectedAssetId: string;
Expand All @@ -35,6 +36,7 @@ export const useSendFormValidation = ({
const availableStxBalance = useCurrentAccountAvailableStxBalance();
const { isStx, selectedAssetBalance } = useSelectedAssetBalance(selectedAssetId);
const feeSchema = useFeeSchema();
const client = useStacksClient();

// TODO: Can this be removed?
const selectedAssetSchema = useCallback(
Expand Down Expand Up @@ -103,19 +105,34 @@ export const useSendFormValidation = ({
[fungibleTokenSchema, isStx, stxAmountFormSchema]
);

const recipientSchema = useCallback(
() =>
stxAddressSchema(SendFormErrorMessages.InvalidAddress)
.test({
message: SendFormErrorMessages.IncorrectAddressMode,
test: stxAddressNetworkValidatorFactory(currentNetwork),
})
.test({
message: SendFormErrorMessages.SameAddress,
test: stxNotCurrentAddressValidatorFactory(currentAccountStxAddress || ''),
}),
[currentAccountStxAddress, currentNetwork]
);
const addressSchema = stxAddressSchema(SendFormErrorMessages.InvalidAddress)
.test({
message: SendFormErrorMessages.IncorrectAddressMode,
test: stxAddressNetworkValidatorFactory(currentNetwork),
})
.test({
message: SendFormErrorMessages.SameAddress,
test: stxNotCurrentAddressValidatorFactory(currentAccountStxAddress || ''),
});

const recipientAddressOrBnsNameSchema = yup.string().test({
name: 'recipientAddressOrBnsName',
test: async value => {
try {
await addressSchema.validate(value);
return true;
} catch (e) {}
try {
const res = await client.namesApi.getNameInfo({ name: value ?? '' });
if (typeof res.address !== 'string' || res.address.length === 0) return false;
return true;
} catch (e) {
return false;
}
},
});

const recipientSchema = addressSchema;

return useMemo(
() =>
Expand All @@ -124,9 +141,10 @@ export const useSendFormValidation = ({
fee: feeSchema(),
memo: transactionMemoSchema(SendFormErrorMessages.MemoExceedsLimit),
nonce: nonceSchema,
recipient: recipientSchema(),
recipient: recipientSchema,
recipientAddressOrBnsName: recipientAddressOrBnsNameSchema,
selectedAsset: selectedAssetSchema(),
}),
[amountSchema, feeSchema, recipientSchema, selectedAssetSchema]
[recipientAddressOrBnsNameSchema, amountSchema, feeSchema, recipientSchema, selectedAssetSchema]
);
};
1 change: 1 addition & 0 deletions src/app/pages/send-tokens/send-tokens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ function SendTokensFormBase() {
feeType: FeeType[FeeType.Middle],
memo: '',
nonce,
recipientAddressOrBnsName: '',
recipient: '',
};

Expand Down
1 change: 1 addition & 0 deletions src/shared/models/form.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export interface SendFormValues {
fee: number | string;
feeType: string;
recipient: string;
recipientAddressOrBnsName: string;
memo: string;
nonce?: number | string;
}
Expand Down

0 comments on commit e5ddf57

Please sign in to comment.