Skip to content

Commit

Permalink
feat: send arc200 asset (#71)
Browse files Browse the repository at this point in the history
* feat: add type to asset holdings

* feat: add arc200 support to asset page

* feat: add arc200 support to the asset select

* fix: arc200 explorer links now go to applications

* feat: update modal to handle response from sumbit transaction response

* feat: implement sending arc200 tokens

* fix: encode uri when opening links

* fix: liniting errors
  • Loading branch information
kieranroneill authored Dec 27, 2023
1 parent 9b16906 commit c481a8a
Show file tree
Hide file tree
Showing 38 changed files with 1,163 additions and 525 deletions.
6 changes: 1 addition & 5 deletions src/extension/components/AccountItem/AccountItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import React, { FC } from 'react';
import { IoWalletOutline } from 'react-icons/io5';

// hooks
import useAccountInformation from '@extension/hooks/useAccountInformation';
import useDefaultTextColor from '@extension/hooks/useDefaultTextColor';
import usePrimaryButtonTextColor from '@extension/hooks/usePrimaryButtonTextColor';
import usePrimaryColor from '@extension/hooks/usePrimaryColor';
Expand All @@ -13,7 +12,7 @@ import useSubTextColor from '@extension/hooks/useSubTextColor';
import { AccountService } from '@extension/services';

// types
import { IAccount, IAccountInformation } from '@extension/types';
import { IAccount } from '@extension/types';

// utils
import { ellipseAddress } from '@extension/utils';
Expand All @@ -29,9 +28,6 @@ const AccountItem: FC<IProps> = ({
subTextColor,
textColor,
}: IProps) => {
const accountInformation: IAccountInformation | null = useAccountInformation(
account.id
);
const defaultSubTextColor: string = useSubTextColor();
const defaultTextColor: string = useDefaultTextColor();
const primaryButtonTextColor: string = usePrimaryButtonTextColor();
Expand Down
126 changes: 98 additions & 28 deletions src/extension/components/AssetSelect/AssetSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@ import Select, { GroupBase, OptionProps, SingleValueProps } from 'react-select';
import { FilterOptionOption } from 'react-select/dist/declarations/src/filters';

// components
import AssetSelectOption from './AssetSelectOption';
import AssetSelectSingleValue from './AssetSelectSingleValue';
import AssetSelectArc200AssetOption from './AssetSelectArc200AssetOption';
import AssetSelectArc200AssetSingleValue from './AssetSelectArc200AssetSingleValue';
import AssetSelectStandardAssetOption from './AssetSelectStandardAssetOption';
import AssetSelectStandardAssetSingleValue from './AssetSelectStandardAssetSingleValue';

// constants
import { OPTION_HEIGHT } from './constants';

// enums
import { AssetTypeEnum } from '@extension/enums';

// hooks
import useColorModeValue from '@extension/hooks/useColorModeValue';

Expand All @@ -21,6 +26,7 @@ import {
IAccountInformation,
IStandardAsset,
INetworkWithTransactionParams,
IArc200Asset,
} from '@extension/types';
import { IOption } from './types';

Expand All @@ -32,11 +38,11 @@ import {

interface IProps {
account: IAccount;
assets: IStandardAsset[];
assets: (IArc200Asset | IStandardAsset)[];
includeNativeCurrency?: boolean;
network: INetworkWithTransactionParams;
onAssetChange: (value: IStandardAsset) => void;
value: IStandardAsset;
onAssetChange: (value: IArc200Asset | IStandardAsset) => void;
value: IArc200Asset | IStandardAsset;
width?: string | number;
}

Expand Down Expand Up @@ -71,17 +77,36 @@ const AssetSelect: FC<IProps> = ({
account.networkInformation[
convertGenesisHashToHex(network.genesisHash).toUpperCase()
] || null;
const selectableAssets: IStandardAsset[] =
accountInformation?.standardAssetHoldings.reduce<IStandardAsset[]>(
(acc, assetHolding) => {
const asset: IStandardAsset | null =
assets.find((value) => value.id === assetHolding.id) || null;
const selectableAssets: (IArc200Asset | IStandardAsset)[] =
assets.reduce<(IArc200Asset | IStandardAsset)[]>(
(acc, asset) => {
let selectedAsset: IArc200Asset | IStandardAsset | null;

// check if the asset exists in the asset holdings of the account; has it been "added"
switch (asset.type) {
case AssetTypeEnum.Arc200:
selectedAsset = accountInformation?.arc200AssetHoldings.find(
(value) => value.id === asset.id
)
? asset
: null;
break;
case AssetTypeEnum.Standard:
selectedAsset = accountInformation?.standardAssetHoldings.find(
(value) => value.id === asset.id
)
? asset
: null;
break;
default:
selectedAsset = null;
}

if (!asset) {
if (!selectedAsset) {
return acc;
}

return [...acc, asset];
return [...acc, selectedAsset];
},
includeNativeCurrency ? [createNativeCurrencyAsset(network)] : []
) || [];
Expand All @@ -91,11 +116,23 @@ const AssetSelect: FC<IProps> = ({
{ data }: FilterOptionOption<IOption>,
inputValue: string
) => {
return !!(
data.asset.id.toUpperCase().includes(inputValue.toUpperCase()) ||
(data.asset.unitName &&
data.asset.unitName.toUpperCase().includes(inputValue.toUpperCase()))
);
switch (data.asset.type) {
case AssetTypeEnum.Arc200:
return (
data.asset.id.toUpperCase().includes(inputValue.toUpperCase()) ||
data.asset.symbol.toUpperCase().includes(inputValue.toUpperCase())
);
case AssetTypeEnum.Standard:
return !!(
data.asset.id.toUpperCase().includes(inputValue.toUpperCase()) ||
(data.asset.unitName &&
data.asset.unitName
.toUpperCase()
.includes(inputValue.toUpperCase()))
);
default:
return false;
}
};

return (
Expand All @@ -105,19 +142,52 @@ const AssetSelect: FC<IProps> = ({
data,
innerProps,
isSelected,
}: OptionProps<IOption, false, GroupBase<IOption>>) => (
<AssetSelectOption
asset={data.asset}
isSelected={isSelected}
onClick={innerProps.onClick}
network={network}
/>
),
}: OptionProps<IOption, false, GroupBase<IOption>>) => {
switch (data.asset.type) {
case AssetTypeEnum.Arc200:
return (
<AssetSelectArc200AssetOption
asset={data.asset}
isSelected={isSelected}
onClick={innerProps.onClick}
network={network}
/>
);
case AssetTypeEnum.Standard:
return (
<AssetSelectStandardAssetOption
asset={data.asset}
isSelected={isSelected}
onClick={innerProps.onClick}
network={network}
/>
);
default:
return null;
}
},
SingleValue: ({
data,
}: SingleValueProps<IOption, false, GroupBase<IOption>>) => (
<AssetSelectSingleValue asset={data.asset} network={network} />
),
}: SingleValueProps<IOption, false, GroupBase<IOption>>) => {
switch (data.asset.type) {
case AssetTypeEnum.Arc200:
return (
<AssetSelectArc200AssetSingleValue
asset={data.asset}
network={network}
/>
);
case AssetTypeEnum.Standard:
return (
<AssetSelectStandardAssetSingleValue
asset={data.asset}
network={network}
/>
);
default:
return null;
}
},
}}
filterOption={handleSearchFilter}
onChange={handleAssetChange}
Expand Down
138 changes: 138 additions & 0 deletions src/extension/components/AssetSelect/AssetSelectArc200AssetOption.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { HStack, Spacer, Text, VStack } from '@chakra-ui/react';
import React, { FC, ReactEventHandler, useState } from 'react';

// components
import AssetAvatar from '@extension/components/AssetAvatar';
import AssetBadge from '@extension/components/AssetBadge';
import AssetIcon from '@extension/components/AssetIcon';

// constants
import { DEFAULT_GAP } from '@extension/constants';
import { OPTION_HEIGHT } from './constants';

// hooks
import useButtonHoverBackgroundColor from '@extension/hooks/useButtonHoverBackgroundColor';
import useColorModeValue from '@extension/hooks/useColorModeValue';
import useDefaultTextColor from '@extension/hooks/useDefaultTextColor';
import usePrimaryButtonTextColor from '@extension/hooks/usePrimaryButtonTextColor';
import useSubTextColor from '@extension/hooks/useSubTextColor';

// theme
import { theme } from '@extension/theme';

// types
import { IArc200Asset, INetworkWithTransactionParams } from '@extension/types';

interface IProps {
asset: IArc200Asset;
isSelected: boolean;
onClick?: ReactEventHandler<HTMLDivElement>;
network: INetworkWithTransactionParams;
}

const AssetSelectOption: FC<IProps> = ({
asset,
isSelected,
onClick,
network,
}: IProps) => {
// hooks
const buttonHoverBackgroundColor: string = useButtonHoverBackgroundColor();
const defaultTextColor: string = useDefaultTextColor();
const primaryButtonTextColor: string = usePrimaryButtonTextColor();
const primaryColor: string = useColorModeValue(
theme.colors.primaryLight['500'],
theme.colors.primaryDark['500']
);
const subTextColor: string = useSubTextColor();
// state
const [backgroundColor, setBackgroundColor] = useState<string>(
isSelected ? primaryColor : 'var(--chakra-colors-chakra-body-bg)'
);
// misc
const formattedDefaultTextColor: string = isSelected
? primaryButtonTextColor
: defaultTextColor;
const formattedSubTextColor: string = isSelected
? primaryButtonTextColor
: subTextColor;
// handlers
const handleMouseEnter = () => {
if (!isSelected) {
setBackgroundColor(buttonHoverBackgroundColor);
}
};
const handleMouseLeave = () => {
if (!isSelected) {
setBackgroundColor('var(--chakra-colors-chakra-body-bg)');
}
};

return (
<HStack
alignItems="center"
backgroundColor={backgroundColor}
cursor="pointer"
h={OPTION_HEIGHT}
m={0}
onClick={onClick}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
p={DEFAULT_GAP / 2}
spacing={2}
w="full"
>
{/*icon*/}
<AssetAvatar
asset={asset}
fallbackIcon={
<AssetIcon
color={primaryButtonTextColor}
networkTheme={network.chakraTheme}
h={6}
w={6}
/>
}
size="xs"
/>

{/*name/symbol*/}
<VStack
alignItems="flex-start"
justifyContent="space-between"
spacing={0}
>
<Text
color={formattedDefaultTextColor}
fontSize="sm"
maxW={175}
noOfLines={1}
>
{asset.name}
</Text>

<Text color={formattedSubTextColor} fontSize="xs">
{asset.symbol}
</Text>
</VStack>

<Spacer />

{/*id/type*/}
<VStack alignItems="flex-end" justifyContent="space-between" spacing={0}>
<AssetBadge type={asset.type} />

<Text
color={formattedSubTextColor}
fontSize="xs"
maxW={175}
noOfLines={1}
>
{asset.id}
</Text>
</VStack>
</HStack>
);
};

export default AssetSelectOption;
Loading

0 comments on commit c481a8a

Please sign in to comment.