diff --git a/apps/dashboard/src/app/nebula-app/(app)/components/AssetsSection/AssetsSection.stories.tsx b/apps/dashboard/src/app/nebula-app/(app)/components/AssetsSection/AssetsSection.stories.tsx new file mode 100644 index 00000000000..abd3e819961 --- /dev/null +++ b/apps/dashboard/src/app/nebula-app/(app)/components/AssetsSection/AssetsSection.stories.tsx @@ -0,0 +1,101 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { storybookThirdwebClient } from "../../../../../stories/utils"; +import { type AssetBalance, AssetsSectionUI } from "./AssetsSection"; + +const meta = { + title: "Nebula/AssetsSection", + component: AssetsSectionUI, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const tokensStub: AssetBalance[] = [ + { + chain_id: 8453, + token_address: "0xe8e55a847bb446d967ef92f4580162fb8f2d3f38", + name: "Broge", + symbol: "BROGE", + decimals: 18, + balance: "10000000000000000000000", + }, + { + chain_id: 8453, + token_address: "0x8d2757ea27aabf172da4cca4e5474c76016e3dc5", + name: "clBTC", + symbol: "clBTC", + decimals: 18, + balance: "2", + }, + { + chain_id: 8453, + token_address: "0xb56d0839998fd79efcd15c27cf966250aa58d6d3", + name: "BASED USA", + symbol: "USA", + decimals: 18, + balance: "1000000000000000000", + }, + { + chain_id: 8453, + token_address: "0x600c9b69a65fb6d2551623a53ddef17b050233cd", + name: "BearPaw", + symbol: "PAW", + decimals: 18, + balance: "48888800000000000000", + }, + { + chain_id: 8453, + token_address: "0x4c96a67b0577358894407af7bc3158fc1dffbeb5", + name: "Degen Point Of View", + symbol: "POV", + decimals: 18, + balance: "69000000000000000000", + }, + { + chain_id: 8453, + token_address: "0x4200000000000000000000000000000000000006", + name: "Wrapped Ether", + symbol: "WETH", + decimals: 18, + balance: "6237535850425", + }, +]; + +export const MultipleAssets: Story = { + args: { + data: tokensStub, + isPending: false, + client: storybookThirdwebClient, + }, +}; + +export const SingleAsset: Story = { + args: { + data: tokensStub.slice(0, 1), + isPending: false, + client: storybookThirdwebClient, + }, +}; + +export const EmptyAssets: Story = { + args: { + data: [], + isPending: false, + client: storybookThirdwebClient, + }, +}; + +export const Loading: Story = { + args: { + data: [], + isPending: true, + client: storybookThirdwebClient, + }, +}; diff --git a/apps/dashboard/src/app/nebula-app/(app)/components/AssetsSection/AssetsSection.tsx b/apps/dashboard/src/app/nebula-app/(app)/components/AssetsSection/AssetsSection.tsx new file mode 100644 index 00000000000..5ae31714b9b --- /dev/null +++ b/apps/dashboard/src/app/nebula-app/(app)/components/AssetsSection/AssetsSection.tsx @@ -0,0 +1,173 @@ +import { Skeleton } from "@/components/ui/skeleton"; +import { isProd } from "@/constants/env-utils"; +import { useQuery } from "@tanstack/react-query"; +import { XIcon } from "lucide-react"; +import Link from "next/link"; +import { type ThirdwebClient, defineChain, toTokens } from "thirdweb"; +import { + Blobbie, + TokenIcon, + TokenProvider, + useActiveAccount, + useActiveWalletChain, +} from "thirdweb/react"; +import { ChainIconClient } from "../../../../../components/icons/ChainIcon"; +import { useAllChainsData } from "../../../../../hooks/chains/allChains"; +import { nebulaAppThirdwebClient } from "../../utils/nebulaThirdwebClient"; + +export type AssetBalance = { + chain_id: number; + token_address: string; + balance: string; + name: string; + symbol: string; + decimals: number; +}; + +export function AssetsSectionUI(props: { + data: AssetBalance[]; + isPending: boolean; + client: ThirdwebClient; +}) { + if (props.data.length === 0 && !props.isPending) { + return ( +
+
+ +
+
No Assets
+
+ ); + } + + return ( +
+ {!props.isPending && + props.data.map((asset) => ( + + ))} + + {props.isPending && + new Array(10).fill(null).map((_, index) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: + + ))} +
+ ); +} + +function SkeletonAssetItem() { + return ( +
+ +
+ + +
+
+ ); +} + +function AssetItem(props: { + asset: AssetBalance; + client: ThirdwebClient; +}) { + const { idToChain } = useAllChainsData(); + const chainMeta = idToChain.get(props.asset.chain_id); + return ( + +
+
+ + } + fallbackComponent={ + + } + /> +
+ +
+
+ +
+ + {props.asset.name} + + +

+ {`${toTokens(BigInt(props.asset.balance), props.asset.decimals)} ${props.asset.symbol}`} +

+
+
+
+ ); +} + +export function AssetsSection(props: { + client: ThirdwebClient; +}) { + const account = useActiveAccount(); + const activeChain = useActiveWalletChain(); + + const assetsQuery = useQuery({ + queryKey: ["v1/tokens/erc20", account?.address, activeChain?.id], + queryFn: async () => { + if (!account || !activeChain) { + return []; + } + const chains = [...new Set([1, 8453, 10, 137, activeChain.id])]; + const url = new URL( + `https://insight.${isProd ? "thirdweb" : "thirdweb-dev"}.com/v1/tokens/erc20/${account?.address}`, + ); + url.searchParams.set("limit", "50"); + url.searchParams.set("metadata", "true"); + url.searchParams.set("include_spam", "false"); + url.searchParams.set("clientId", nebulaAppThirdwebClient.clientId); + for (const chain of chains) { + url.searchParams.append("chain", chain.toString()); + } + + const response = await fetch(url.toString()); + const json = (await response.json()) as { + data: AssetBalance[]; + }; + + return json.data; + }, + enabled: !!account && !!activeChain, + }); + + return ( + + ); +} diff --git a/apps/dashboard/src/app/nebula-app/(app)/components/ChatBar.tsx b/apps/dashboard/src/app/nebula-app/(app)/components/ChatBar.tsx index 4d7949350f0..699b69f2325 100644 --- a/apps/dashboard/src/app/nebula-app/(app)/components/ChatBar.tsx +++ b/apps/dashboard/src/app/nebula-app/(app)/components/ChatBar.tsx @@ -36,8 +36,10 @@ import { AccountBlobbie, AccountName, AccountProvider, + WalletIcon, WalletName, WalletProvider, + useActiveWallet, } from "thirdweb/react"; import { shortenAddress } from "thirdweb/utils"; import type { Wallet } from "thirdweb/wallets"; @@ -451,8 +453,18 @@ function WalletSelector(props: { selectedAddress: string | undefined; }) { const [open, setOpen] = useState(false); + const activeWallet = useActiveWallet(); + + const accountBlobbie = ; + const accountAvatarFallback = ( + + ); - if (!props.selectedAddress) { + if (!props.selectedAddress || !activeWallet) { return null; } @@ -471,34 +483,32 @@ function WalletSelector(props: { aria-expanded={open} className="flex h-auto items-center gap-1 rounded-full px-2 py-1.5 text-xs" > - - - } - fallbackComponent={ - - } - /> - - {shortenAddress(props.selectedAddress)} - - } - fallbackComponent={ - - {shortenAddress(props.selectedAddress)} - - } - /> - - + + + + + {shortenAddress(props.selectedAddress)} + + } + fallbackComponent={ + + {shortenAddress(props.selectedAddress)} + + } + /> + + + @@ -513,81 +523,107 @@ function WalletSelector(props: {
- {sortedWallets.map((wallet) => ( - // biome-ignore lint/a11y/useKeyWithClickEvents: -
{ - setOpen(false); - props.onClick(wallet); - }} - > -
- + {sortedWallets.map((wallet) => { + const accountBlobbie = ( + + ); + const accountAvatarFallback = ( + + ); + + return ( +
{ + if (e.key === "Enter" || e.key === " ") { + setOpen(false); + props.onClick(wallet); + } + }} + onClick={() => { + setOpen(false); + props.onClick(wallet); + }} + > +
-
- - } - fallbackComponent={ - - } - /> - -
-
- - {shortenAddress(wallet.address)} - - } - fallbackComponent={ - - {shortenAddress(wallet.address)} - - } + + +
+ - - - {wallet.walletId === "smart" && ( - - Gasless - - )} +
+
+ + {shortenAddress(wallet.address)} + + } + fallbackComponent={ + + {shortenAddress(wallet.address)} + + } + /> + + + + {wallet.walletId === "smart" && ( + + Gasless + + )} +
+ +
+ {wallet.walletId === "smart" ? ( + "Smart Account" + ) : ( + Your Account + } + loadingComponent={ + + } + /> + )} +
+
- -
- {wallet.walletId === "smart" ? ( - "Smart Account" - ) : ( - Your Account } - loadingComponent={ - - } - /> - )} -
-
-
+ + - -
+
- {props.selectedAddress === wallet.address && ( - - )} -
- ))} + {props.selectedAddress === wallet.address && ( + + )} +
+ ); + })}
diff --git a/apps/dashboard/src/app/nebula-app/(app)/components/ChatPageLayout.tsx b/apps/dashboard/src/app/nebula-app/(app)/components/ChatPageLayout.tsx index cfca346a380..428132cf200 100644 --- a/apps/dashboard/src/app/nebula-app/(app)/components/ChatPageLayout.tsx +++ b/apps/dashboard/src/app/nebula-app/(app)/components/ChatPageLayout.tsx @@ -17,7 +17,7 @@ export function ChatPageLayout(props: { props.className, )} > -