diff --git a/apps/wallet-dashboard/components/Coins/CoinItem.tsx b/apps/wallet-dashboard/components/Coins/CoinItem.tsx deleted file mode 100644 index a055f87def1..00000000000 --- a/apps/wallet-dashboard/components/Coins/CoinItem.tsx +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import { useFormatCoin } from '@iota/core'; -import React from 'react'; - -interface CoinItemProps { - coinType: string; - balance: bigint; - onClick?: () => void; -} - -function CoinItem({ coinType, balance, onClick }: CoinItemProps): React.JSX.Element { - const [formattedCoin, coinSymbol, { data: coinMeta }] = useFormatCoin(balance, coinType); - - return ( -
-
-
- {coinMeta?.name} -
-
- - {formattedCoin} {coinSymbol} - -
-
-
- ); -} - -export default CoinItem; diff --git a/apps/wallet-dashboard/components/Coins/MyCoins.tsx b/apps/wallet-dashboard/components/Coins/MyCoins.tsx deleted file mode 100644 index 6fb6e8eed49..00000000000 --- a/apps/wallet-dashboard/components/Coins/MyCoins.tsx +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import React from 'react'; -import { useCurrentAccount, useIotaClientQuery } from '@iota/dapp-kit'; -import { CoinItem, SendCoinPopup } from '@/components'; -import { usePopups } from '@/hooks'; -import { CoinBalance } from '@iota/iota-sdk/client'; -import { - COINS_QUERY_REFETCH_INTERVAL, - COINS_QUERY_STALE_TIME, - filterAndSortTokenBalances, - useSortedCoinsByCategories, -} from '@iota/core'; - -function MyCoins(): React.JSX.Element { - const { openPopup, closePopup } = usePopups(); - const account = useCurrentAccount(); - const activeAccountAddress = account?.address; - - const { data: coinBalances } = useIotaClientQuery( - 'getAllBalances', - { owner: activeAccountAddress! }, - { - enabled: !!activeAccountAddress, - staleTime: COINS_QUERY_STALE_TIME, - refetchInterval: COINS_QUERY_REFETCH_INTERVAL, - select: filterAndSortTokenBalances, - }, - ); - const { recognized, unrecognized } = useSortedCoinsByCategories(coinBalances ?? []); - - function openSendTokenPopup(coin: CoinBalance, address: string): void { - if (coinBalances) { - openPopup( - , - ); - } - } - - return ( -
-

My Coins:

- {recognized?.map((coin, index) => { - return ( - openSendTokenPopup(coin, account?.address ?? '')} - /> - ); - })} - Unrecognized coins - {unrecognized?.map((coin, index) => { - return ( - openSendTokenPopup(coin, account?.address ?? '')} - /> - ); - })} -
- ); -} - -export default MyCoins; diff --git a/apps/wallet-dashboard/components/ImageIcon.tsx b/apps/wallet-dashboard/components/ImageIcon.tsx new file mode 100644 index 00000000000..06939516971 --- /dev/null +++ b/apps/wallet-dashboard/components/ImageIcon.tsx @@ -0,0 +1,80 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { useState } from 'react'; +import Image from 'next/image'; +import cn from 'clsx'; + +export enum ImageIconSize { + Small = 'w-5 h-5', + Medium = 'w-8 h-8', + Large = 'w-10 h-10', + Full = 'w-full h-full', +} + +interface FallBackAvatarProps { + text: string; + rounded?: boolean; + size?: ImageIconSize; +} +function FallBackAvatar({ text, rounded, size = ImageIconSize.Large }: FallBackAvatarProps) { + const textSize = (() => { + switch (size) { + case ImageIconSize.Small: + return 'text-label-sm'; + case ImageIconSize.Medium: + return 'text-label-md'; + case ImageIconSize.Large: + return 'text-title-md'; + case ImageIconSize.Full: + return 'text-title-lg'; + } + })(); + + return ( +
+ {text.slice(0, 2)} +
+ ); +} +export interface ImageIconProps { + src: string | null | undefined; + label: string; + fallbackText: string; + alt?: string; + rounded?: boolean; + size?: ImageIconSize; +} + +export function ImageIcon({ + src, + label, + alt = label, + fallbackText, + rounded, + size, +}: ImageIconProps) { + const [error, setError] = useState(false); + return ( +
+ {error || !src ? ( + + ) : ( + {alt} setError(true)} + layout="fill" + objectFit="cover" + /> + )} +
+ ); +} diff --git a/apps/wallet-dashboard/components/coins/CoinIcon.tsx b/apps/wallet-dashboard/components/coins/CoinIcon.tsx new file mode 100644 index 00000000000..a5c24107baa --- /dev/null +++ b/apps/wallet-dashboard/components/coins/CoinIcon.tsx @@ -0,0 +1,45 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { useCoinMetadata } from '@iota/core'; +import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; +import { IotaLogoMark } from '@iota/ui-icons'; +import cx from 'clsx'; +import { ImageIcon, ImageIconSize } from '../ImageIcon'; + +interface NonIotaCoinProps { + coinType: string; + size?: ImageIconSize; + rounded?: boolean; +} + +function NonIotaCoin({ coinType, size = ImageIconSize.Full, rounded }: NonIotaCoinProps) { + const { data: coinMeta } = useCoinMetadata(coinType); + return ( +
+ +
+ ); +} + +export interface CoinIconProps { + coinType: string; + size?: ImageIconSize; + rounded?: boolean; +} + +export function CoinIcon({ coinType, size = ImageIconSize.Full, rounded }: CoinIconProps) { + return coinType === IOTA_TYPE_ARG ? ( +
+ +
+ ) : ( + + ); +} diff --git a/apps/wallet-dashboard/components/coins/CoinItem.tsx b/apps/wallet-dashboard/components/coins/CoinItem.tsx new file mode 100644 index 00000000000..29e8be5aa35 --- /dev/null +++ b/apps/wallet-dashboard/components/coins/CoinItem.tsx @@ -0,0 +1,61 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { + Card, + CardAction, + CardActionType, + CardBody, + CardImage, + CardType, + ImageType, +} from '@iota/apps-ui-kit'; +import { useFormatCoin } from '@iota/core'; +import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; +import { type ReactNode } from 'react'; +import { ImageIconSize } from '../ImageIcon'; +import { CoinIcon } from './CoinIcon'; + +interface CoinItemProps { + coinType: string; + balance: bigint; + onClick?: () => void; + icon?: ReactNode; + clickableAction?: ReactNode; + usd?: number; +} + +function CoinItem({ + coinType, + balance, + onClick, + icon, + clickableAction, + usd, +}: CoinItemProps): React.JSX.Element { + const [formatted, symbol, { data: coinMeta }] = useFormatCoin(balance, coinType); + const isIota = coinType === IOTA_TYPE_ARG; + + return ( + + +
+ +
+
+ + +
+ ); +} + +export default CoinItem; diff --git a/apps/wallet-dashboard/components/coins/MyCoins.tsx b/apps/wallet-dashboard/components/coins/MyCoins.tsx new file mode 100644 index 00000000000..bf3a3d6a8f7 --- /dev/null +++ b/apps/wallet-dashboard/components/coins/MyCoins.tsx @@ -0,0 +1,145 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import React, { useState } from 'react'; +import { useCurrentAccount, useIotaClientQuery } from '@iota/dapp-kit'; +import { CoinItem, SendCoinPopup } from '@/components'; +import { usePopups } from '@/hooks'; +import { CoinBalance } from '@iota/iota-sdk/client'; +import { + COINS_QUERY_REFETCH_INTERVAL, + COINS_QUERY_STALE_TIME, + filterAndSortTokenBalances, + useSortedCoinsByCategories, +} from '@iota/core'; +import { + ButtonSegment, + Panel, + SegmentedButton, + SegmentedButtonType, + Title, +} from '@iota/apps-ui-kit'; +import { RecognizedBadge } from '@iota/ui-icons'; + +enum TokenCategory { + All = 'All', + Recognized = 'Recognized', + Unrecognized = 'Unrecognized', +} + +const TOKEN_CATEGORIES = [ + { + label: 'All', + value: TokenCategory.All, + }, + { + label: 'Recognized', + value: TokenCategory.Recognized, + }, + { + label: 'Unrecognized', + value: TokenCategory.Unrecognized, + }, +]; + +function MyCoins(): React.JSX.Element { + const [selectedTokenCategory, setSelectedTokenCategory] = useState(TokenCategory.All); + + const { openPopup, closePopup } = usePopups(); + const account = useCurrentAccount(); + const activeAccountAddress = account?.address; + + const { data: coinBalances } = useIotaClientQuery( + 'getAllBalances', + { owner: activeAccountAddress! }, + { + enabled: !!activeAccountAddress, + staleTime: COINS_QUERY_STALE_TIME, + refetchInterval: COINS_QUERY_REFETCH_INTERVAL, + select: filterAndSortTokenBalances, + }, + ); + const { recognized, unrecognized } = useSortedCoinsByCategories(coinBalances ?? []); + + function openSendTokenPopup(coin: CoinBalance, address: string): void { + if (coinBalances) { + openPopup( + , + ); + } + } + + return ( + +
+ + <div className="px-sm pb-md pt-sm"> + <div className="inline-flex"> + <SegmentedButton type={SegmentedButtonType.Filled}> + {TOKEN_CATEGORIES.map(({ label, value }) => { + const recognizedButEmpty = + value === TokenCategory.Recognized ? !recognized.length : false; + const notRecognizedButEmpty = + value === TokenCategory.Unrecognized + ? !unrecognized?.length + : false; + + return ( + <ButtonSegment + key={value} + onClick={() => setSelectedTokenCategory(value)} + label={label} + selected={selectedTokenCategory === value} + disabled={recognizedButEmpty || notRecognizedButEmpty} + /> + ); + })} + </SegmentedButton> + </div> + <div className="pb-md pt-sm"> + {[TokenCategory.All, TokenCategory.Recognized].includes( + selectedTokenCategory, + ) && + recognized?.map((coin, index) => { + return ( + <CoinItem + key={index} + coinType={coin.coinType} + balance={BigInt(coin.totalBalance)} + onClick={() => + openSendTokenPopup(coin, account?.address ?? '') + } + icon={ + <RecognizedBadge className="h-4 w-4 text-primary-40" /> + } + /> + ); + })} + {[TokenCategory.All, TokenCategory.Unrecognized].includes( + selectedTokenCategory, + ) && + unrecognized?.map((coin, index) => { + return ( + <CoinItem + key={index} + coinType={coin.coinType} + balance={BigInt(coin.totalBalance)} + onClick={() => + openSendTokenPopup(coin, account?.address ?? '') + } + /> + ); + })} + </div> + </div> + </div> + </Panel> + ); +} + +export default MyCoins; diff --git a/apps/wallet-dashboard/components/Coins/index.ts b/apps/wallet-dashboard/components/coins/index.ts similarity index 86% rename from apps/wallet-dashboard/components/Coins/index.ts rename to apps/wallet-dashboard/components/coins/index.ts index d1519105eec..51468194c31 100644 --- a/apps/wallet-dashboard/components/Coins/index.ts +++ b/apps/wallet-dashboard/components/coins/index.ts @@ -3,3 +3,4 @@ export { default as MyCoins } from './MyCoins'; export { default as CoinItem } from './CoinItem'; +export * from './CoinIcon'; diff --git a/apps/wallet-dashboard/components/index.ts b/apps/wallet-dashboard/components/index.ts index e7f010c273f..aff0cf01ce8 100644 --- a/apps/wallet-dashboard/components/index.ts +++ b/apps/wallet-dashboard/components/index.ts @@ -13,8 +13,9 @@ export { default as TransactionIcon } from './TransactionIcon'; export { default as Dropdown } from './Dropdown'; export * from './account-balance/AccountBalance'; -export * from './Coins'; +export * from './coins'; export * from './Popup'; export * from './AppList'; export * from './Cards'; export * from './Buttons'; +export * from './ImageIcon';