Skip to content

Commit

Permalink
Feature/updated transfer UI (#876)
Browse files Browse the repository at this point in the history
* refactor: use updated tab component

* refactor: duplicated form titles

* refactor: remove redundant hook calls

* refactor: prefer title case

* wip: XCM transfer form UI

* wip: updated form UI

* wip: account selector placeholder component

* wip: account selector modal

* wip: modal open and close actions

* wip: update modal type

* wip: get accounts

* wip: add identicon and rename component for consistency

* wip: account input component

* fix: remove redundant icons prop

* feat: implement with SelectTrigger

* wip: styling and account selection value

* wip: handle setting account data

* refactor: better naming

* wip: address list styling

* refactor: rename defaultAccount

* wip: chain selector placeholder component

* wip: duplicate account component and rename

* chore: delete redundant legacy component

* wip: logic for fetching and rendering chain ids

* wip: chain item styling

* wip: selected chain styling

* chore: add comment

* refactor: pass through native token to icon component

* feature: add chain icon component

* chore: add comment

* chore: correct file name casing

* refactor: improve folder structure

* wip: form layout styling

* chore: add arrow icon

* chore: add logos and correct svg titles

* chore: remove redundant svg prop

* chore: rename arrow icon

* chore: consistent use of styled components

* refactor: remove padding from modal body

* wip: formik integration work

* wip: extend useXCMBridge to return available chains and utility methods

* chore: move Chain and Chains types to types directory

* feat: layout and form implementation

* feat: add schema

* feat: final

* wip: refactor useXCMBridge hook

* refactor: add endpoints type

* refactor: wrap methods in useCallback

* refactor: fix bug in hook method

* chore: bump bridge version

* wip: set originating and destination chain values

* refactor: set from chain value on field change

* wip: set originating chain value

* refactor: mergeProps to set field value

* refactor: handle setting origin/destination chain values

* wip: get tokens method

* wip: first iteration of balances function

* wip: handle tokens array

* wip: set token value

* wip: get token balances

* wip: return token and balances in single method

* wip: mapped tokens

* refactor: handle default chain values

* refactor: better organised function order

* wip: handle change events

* wip: handle setting tokens

* wip: handle fetching tokens and balances

* wip: convert input configs

* wip: handle token change

* wip: get token USD price

* Trigger Build

* chore: remove unused import

* chore: correct eslintignore syntax

* wip: handle breaking changes

* wip: disable token input when select items value is 1

* chore: set first token item as variable

* wip: handle setting and changing values

* chire: add loading spinner

* refactor: add loading state

* refactor: filter destination chains

* chore: remove console log

* chore: bump XCM bridge version

* chore: update config

* refactor: configure validation

* chore: revert change to useForm hook

* wip: form validation

* wip: working form validation

* wip: undefined validation parameters

* refactor: return dest fee estimate from bridge hook

* feature: show fees and fee estimates

* chore: conditional operators

* refactor: handle ticker change correctly

* wip: sendTransaction method

* Revert "wip: sendTransaction method"

This reverts commit 3ade26d.

* fix: USD amounts

* wip: send transactions

* refactor: bump bridge and use getNativeToken method

* chore: bump bridge

* refactor: move submit logic to useMutation hook

* fix: type mismatches

* refactor: white space/comments

* refactor: add transaction fee validation

* chore: typo

* chore: remove console log

* refactor: remove duplicated monetary conversion

* refactor: remove duplicate code

* Revert "refactor: remove duplicate code"

This reverts commit bd29f8c.

* Revert "refactor: remove duplicated monetary conversion"

This reverts commit 5fd3d64.

* refactor: use monetaryAmount when constructing transaction

* refactor: remove duplicated code for fetching tokens

* refactor: default XCM origin

* Revert "refactor: remove duplicated code for fetching tokens"

This reverts commit 8f31ee8.

* chore: remove comment

* chore: fix errors

* fix: set default value to empty string to prevent React error

* refactor: removed unwanted force validation parameters

* refactor: remove redundant method

* refactor: add method return type

* refactor: add method return type

* refactor: correct type error

* refactor: fix destFee type error

* refactor: remove fees validation and revert destFee return value

* chore: remove console log

* refactor: remove redundant method

* refactor: disable validation on change

* chore: remove commented out code

* wip: use select component for chain selector

* fix: handle chain select functions

* refactor: type chain id as ChainName

* Revert "refactor: type chain id as ChainName"

This reverts commit d05e012.

* chore: remove unused component files

* refactor: remove duplicated transaction logic

* fix: make to/from field types more specific

* fix: revert yup.custom changes and cast validation

* fix: set correct destination chain

* refator: handle token data

* refactor: add use callback

* fix: correct rendering logic

* fix: update dependencies

* chore: delete unused styles

* chore: fix merge issue with transfer form

* fix: change validation handling

* Revert "fix: change validation handling"

This reverts commit c0cb306.

* refactor: only display transfer amount if amount has been entered

* chore: config changes

* chore: add missing icons

* chore: Hydra chain icon

* fix: add error text to CTA

* Tom/xcm fixes (#1213)

* refactor: specify endpoints and remove unnecessary logic

* fix: save file before committing

* fix: disable refetch

* chore: update endpoints

* chore: remove log

* chore: rename file

* chore: add additional acala/karura endpoints

---------

Co-authored-by: Rui Simão <[email protected]>
  • Loading branch information
tomjeatt and danielsimao authored May 22, 2023
1 parent 182fba6 commit ab6a554
Show file tree
Hide file tree
Showing 42 changed files with 1,478 additions and 759 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
"@craco/craco": "^6.1.1",
"@headlessui/react": "^1.1.1",
"@heroicons/react": "^2.0.0",
"@interlay/bridge": "^0.2.4",
"@interlay/bridge": "^0.3.9",
"@interlay/interbtc-api": "2.2.2",
"@interlay/monetary-js": "0.7.2",
"@polkadot/api": "9.14.2",
"@polkadot/extension-dapp": "0.44.1",
"@polkadot/react-identicon": "^2.11.1",
"@polkadot/ui-keyring": "^2.9.7",
"@reach/tooltip": "^0.16.0",
"@react-aria/accordion": "^3.0.0-alpha.14",
Expand Down
13 changes: 13 additions & 0 deletions src/assets/icons/ArrowRightCircle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { forwardRef } from 'react';

import { Icon, IconProps } from '@/component-library/Icon';

const ArrowRightCircle = forwardRef<SVGSVGElement, IconProps>((props, ref) => (
<Icon ref={ref} xmlns='http://www.w3.org/2000/svg' stroke='none' viewBox='0 0 24 24' fill='currentColor' {...props}>
<path d='M2.4 12C2.4 6.708 6.708 2.4 12 2.4C17.292 2.4 21.6 6.708 21.6 12C21.6 17.292 17.292 21.6 12 21.6C6.708 21.6 2.4 17.292 2.4 12ZM-7.52913e-07 12C-3.37305e-07 18.624 5.376 24 12 24C18.624 24 24 18.624 24 12C24 5.376 18.624 -1.52082e-06 12 -1.31911e-06C5.376 -1.11739e-06 -1.16852e-06 5.376 -7.52913e-07 12ZM12 10.8L7.2 10.8L7.2 13.2L12 13.2L12 16.8L16.8 12L12 7.2L12 10.8Z' />
</Icon>
));

ArrowRightCircle.displayName = 'ArrowRightCircle';

export { ArrowRightCircle };
1 change: 1 addition & 0 deletions src/assets/icons/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { ArrowRight } from './ArrowRight';
export { ArrowRightCircle } from './ArrowRightCircle';
export { ArrowsUpDown } from './ArrowsUpDown';
export { ArrowTopRightOnSquare } from './ArrowTopRightOnSquare';
export { ChevronDown } from './ChevronDown';
Expand Down
1 change: 1 addition & 0 deletions src/assets/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,7 @@
},
"forms": {
"please_enter_your_field": "Please enter your {{field}}",
"please_select_your_field": "Please select your {{field}}",
"please_enter_the_amount_to": "Please enter the amount to {{field}}",
"amount_must_be_at_least": "Amount to {{action}} must be at least {{amount}} {{token}}",
"amount_must_be_at_most": "Amount to {{action}} must be at most {{amount}}",
Expand Down
1 change: 1 addition & 0 deletions src/component-library/Label/Label.style.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const StyledLabel = styled.label`
font-size: ${theme.text.xs};
color: ${theme.colors.textTertiary};
padding: ${theme.spacing.spacing1} 0;
align-self: flex-start;
`;

export { StyledLabel };
2 changes: 1 addition & 1 deletion src/component-library/Select/SelectTrigger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Sizes } from '../utils/prop-types';
import { StyledChevronDown, StyledTrigger, StyledTriggerValue } from './Select.style';

type Props = {
as: any;
as?: any;
size?: Sizes;
isOpen?: boolean;
hasError?: boolean;
Expand Down
2 changes: 2 additions & 0 deletions src/component-library/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export type { ModalBodyProps, ModalDividerProps, ModalFooterProps, ModalHeaderPr
export { Modal, ModalBody, ModalDivider, ModalFooter, ModalHeader } from './Modal';
export type { NumberInputProps } from './NumberInput';
export { NumberInput } from './NumberInput';
export type { SelectProps } from './Select';
export { Item, Select } from './Select';
export type { StackProps } from './Stack';
export { Stack } from './Stack';
export type { SwitchProps } from './Switch';
Expand Down
10 changes: 7 additions & 3 deletions src/component-library/theme/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -510,15 +510,19 @@ const theme = {
size: {
small: {
padding: 'var(--spacing-1)',
text: 'var(--text-s)'
text: 'var(--text-s)',
// TODO: to be determined
maxHeight: 'calc(var(--spacing-6) - 1px)'
},
medium: {
padding: 'var(--spacing-2)',
text: 'var(--text-base)'
text: 'var(--text-base)',
maxHeight: 'calc(var(--spacing-10) - 1px)'
},
large: {
padding: 'var(--spacing-5) var(--spacing-2)',
text: 'var(--text-lg)'
text: 'var(--text-lg)',
maxHeight: 'calc(var(--spacing-16) - 1px)'
}
}
}
Expand Down
34 changes: 34 additions & 0 deletions src/components/AccountSelect/AccountLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import Identicon from '@polkadot/react-identicon';

import { FlexProps } from '@/component-library/Flex';

import { StyledAccountLabelAddress, StyledAccountLabelName, StyledAccountLabelWrapper } from './AccountSelect.style';

type Props = {
isSelected?: boolean;
address: string;
name?: string;
};

type InheritAttrs = Omit<FlexProps, keyof Props | 'children'>;

type AccountLabelProps = Props & InheritAttrs;

const AccountLabel = ({ isSelected, address, name, ...props }: AccountLabelProps): JSX.Element => (
<StyledAccountLabelWrapper alignItems='center' gap='spacing2' flex='1' {...props}>
<Identicon size={24} value={address} theme='polkadot' />
<StyledAccountLabelWrapper direction='column'>
{name && (
<StyledAccountLabelName size='s' $isSelected={!!isSelected}>
{name}
</StyledAccountLabelName>
)}
<StyledAccountLabelAddress size='xs' color='tertiary'>
{address}
</StyledAccountLabelAddress>
</StyledAccountLabelWrapper>
</StyledAccountLabelWrapper>
);

export { AccountLabel };
export type { AccountLabelProps };
58 changes: 58 additions & 0 deletions src/components/AccountSelect/AccountList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { InjectedAccountWithMeta } from '@polkadot/extension-inject/types';

import { ListItem, ListProps } from '@/component-library/List';

import { AccountLabel } from './AccountLabel';
import { StyledList } from './AccountSelect.style';

type Props = {
items: InjectedAccountWithMeta[];
selectedAccount?: string;
onSelectionChange?: (account: string) => void;
};

type InheritAttrs = Omit<ListProps, keyof Props | 'children'>;

type AccountListProps = Props & InheritAttrs;

const AccountList = ({ items, selectedAccount, onSelectionChange, ...props }: AccountListProps): JSX.Element => {
const handleSelectionChange: ListProps['onSelectionChange'] = (key) => {
const [selectedKey] = [...key];

if (!selectedKey) return;

onSelectionChange?.(selectedKey as string);
};

return (
<StyledList
aria-label='select account'
variant='secondary'
selectionMode='single'
onSelectionChange={handleSelectionChange}
selectedKeys={selectedAccount ? [selectedAccount] : undefined}
{...props}
>
{items.map((item) => {
const accountText = item.address;

const isSelected = selectedAccount === accountText;

return (
<ListItem
key={accountText}
textValue={accountText}
alignItems='center'
justifyContent='space-between'
gap='spacing2'
>
<AccountLabel isSelected={isSelected} address={item.address} name={item.meta.name} />
</ListItem>
);
})}
</StyledList>
);
};

export { AccountList };
export type { AccountListProps };
34 changes: 34 additions & 0 deletions src/components/AccountSelect/AccountListModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { InjectedAccountWithMeta } from '@polkadot/extension-inject/types';

import { Modal, ModalBody, ModalHeader, ModalProps } from '@/component-library/Modal';

import { AccountList } from './AccountList';

type Props = {
accounts: InjectedAccountWithMeta[];
onSelectionChange?: (account: string) => void;
selectedAccount?: string;
};

type InheritAttrs = Omit<ModalProps, keyof Props | 'children'>;

type AccountListModalProps = Props & InheritAttrs;

const AccountListModal = ({
selectedAccount,
accounts,
onSelectionChange,
...props
}: AccountListModalProps): JSX.Element => (
<Modal hasMaxHeight {...props}>
<ModalHeader size='lg' weight='medium' color='secondary'>
Select Account
</ModalHeader>
<ModalBody overflow='hidden' noPadding>
<AccountList items={accounts} selectedAccount={selectedAccount} onSelectionChange={onSelectionChange} />
</ModalBody>
</Modal>
);

export { AccountListModal };
export type { AccountListModalProps };
68 changes: 68 additions & 0 deletions src/components/AccountSelect/AccountSelect.style.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import styled from 'styled-components';

import { ChevronDown } from '@/assets/icons';
import { Flex } from '@/component-library/Flex';
import { List } from '@/component-library/List';
import { Span } from '@/component-library/Text';
import { theme } from '@/component-library/theme';

type StyledClickableProps = {
$isClickable: boolean;
};

type StyledListItemSelectedLabelProps = {
$isSelected: boolean;
};

const StyledAccount = styled.span`
font-size: ${theme.text.s};
color: ${theme.colors.textPrimary};
overflow: hidden;
text-overflow: ellipsis;
`;

const StyledAccountSelect = styled(Flex)<StyledClickableProps>`
background-color: ${theme.tokenInput.endAdornment.bg};
border-radius: ${theme.rounded.md};
font-size: ${theme.text.xl2};
padding: ${theme.spacing.spacing3};
cursor: ${({ $isClickable }) => $isClickable && 'pointer'};
height: 3rem;
width: auto;
overflow: hidden;
`;

const StyledChevronDown = styled(ChevronDown)`
margin-left: ${theme.spacing.spacing1};
`;

const StyledAccountLabelAddress = styled(Span)`
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
`;

const StyledAccountLabelName = styled(StyledAccountLabelAddress)<StyledListItemSelectedLabelProps>`
color: ${({ $isSelected }) =>
$isSelected ? theme.tokenInput.list.item.selected.text : theme.tokenInput.list.item.default.text};
`;

const StyledList = styled(List)`
overflow: auto;
padding: 0 ${theme.modal.body.paddingX} ${theme.modal.body.paddingY} ${theme.modal.body.paddingX};
`;

const StyledAccountLabelWrapper = styled(Flex)`
flex-grow: 1;
overflow: hidden;
`;

export {
StyledAccount,
StyledAccountLabelAddress,
StyledAccountLabelName,
StyledAccountLabelWrapper,
StyledAccountSelect,
StyledChevronDown,
StyledList
};
99 changes: 99 additions & 0 deletions src/components/AccountSelect/AccountSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { InjectedAccountWithMeta } from '@polkadot/extension-inject/types';
import { useLabel } from '@react-aria/label';
import { chain, mergeProps } from '@react-aria/utils';
import { VisuallyHidden } from '@react-aria/visually-hidden';
import { forwardRef, InputHTMLAttributes, ReactNode, useEffect, useState } from 'react';

import { Flex, Label } from '@/component-library';
import { SelectTrigger } from '@/component-library/Select';
import { useDOMRef } from '@/component-library/utils/dom';
import { triggerChangeEvent } from '@/component-library/utils/input';

import { AccountLabel } from './AccountLabel';
import { AccountListModal } from './AccountListModal';

const getAccount = (accountValue?: string, accounts?: InjectedAccountWithMeta[]) =>
accounts?.find((account) => account.address === accountValue);

type Props = {
value: string;
defaultValue?: string;
icons?: string[];
isDisabled?: boolean;
label?: ReactNode;
accounts?: InjectedAccountWithMeta[];
};

type NativeAttrs = Omit<InputHTMLAttributes<HTMLInputElement> & { ref?: any }, keyof Props>;

type AccountSelectProps = Props & NativeAttrs;

const AccountSelect = forwardRef<HTMLInputElement, AccountSelectProps>(
({ value: valueProp, defaultValue = '', accounts, disabled, label, className, ...props }, ref): JSX.Element => {
const inputRef = useDOMRef(ref);

const [isOpen, setOpen] = useState(false);
const [value, setValue] = useState(defaultValue);

const { fieldProps, labelProps } = useLabel({ ...props, label });

useEffect(() => {
if (valueProp === undefined) return;

setValue(valueProp);
}, [valueProp]);

const handleAccount = (account: string) => {
triggerChangeEvent(inputRef, account);
setValue(account);
};

const handleClose = () => setOpen(false);

const isDisabled = !accounts?.length || disabled;

const selectedAccount = getAccount(value, accounts);

return (
<>
<Flex direction='column' className={className}>
{label && <Label {...labelProps}>{label}</Label>}
<SelectTrigger
size='large'
onPress={() => setOpen(true)}
disabled={isDisabled}
{...mergeProps(fieldProps, {
// MEMO: when the button is blurred, a focus and blur is executed on the input
// so that validation gets triggered.
onBlur: () => {
if (!isOpen) {
inputRef.current?.focus();
inputRef.current?.blur();
}
}
})}
>
{selectedAccount && <AccountLabel address={selectedAccount.address} name={selectedAccount.meta.name} />}
</SelectTrigger>
<VisuallyHidden>
<input ref={inputRef} autoComplete='off' tabIndex={-1} value={value} {...props} />
</VisuallyHidden>
</Flex>
{accounts && (
<AccountListModal
isOpen={isOpen}
accounts={accounts}
selectedAccount={selectedAccount?.address}
onClose={handleClose}
onSelectionChange={chain(handleAccount, handleClose)}
/>
)}
</>
);
}
);

AccountSelect.displayName = 'AccountSelect';

export { AccountSelect };
export type { AccountSelectProps };
2 changes: 2 additions & 0 deletions src/components/AccountSelect/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export type { AccountSelectProps } from './AccountSelect';
export { AccountSelect } from './AccountSelect';
2 changes: 2 additions & 0 deletions src/components/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export type { AccountSelectProps } from './AccountSelect';
export { AccountSelect } from './AccountSelect';
export type { AuthCTAProps } from './AuthCTA';
export { AuthCTA } from './AuthCTA';
export type { AssetCellProps, BalanceCellProps, CellProps, TableProps } from './DataGrid';
Expand Down
Loading

2 comments on commit ab6a554

@vercel
Copy link

@vercel vercel bot commented on ab6a554 May 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on ab6a554 May 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.