From 834bbc088350419d6af4878be2a23d7aed95ccf1 Mon Sep 17 00:00:00 2001 From: MananTank Date: Sat, 2 Aug 2025 02:24:48 +0000 Subject: [PATCH] [MNY-62] Dashboard: Add CheckoutWidget in server wallets & backend wallets table. server wallets UI improvements (#7782) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ## PR-Codex overview This PR focuses on updating the dashboard application with various improvements, including dependency updates, UI adjustments, and the introduction of new components for better wallet management. ### Detailed summary - Removed `react-qrcode-logo` from dependencies. - Changed `tabNames` in `IntegrateAPIKeyCodeTabs.tsx`. - Updated button structure in `layout.tsx` for better UI. - Adjusted styles in `wallet-address.tsx` for consistency. - Modified `PaymentLinksTable.client.tsx` button variants and sizes. - Removed unused imports in `page.tsx`. - Enhanced `Switch` component with size prop. - Added `FundWalletModal` component with form validation. - Updated `ServerWalletsTableUI` with improved wallet display and functionality. - Introduced `WalletActionsDropdown` for wallet actions. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` ## Summary by CodeRabbit * **New Features** * Introduced a modal for funding wallets, allowing users to select chain, token, and amount, and complete payments via a checkout flow. * Added chain selection and a toggle to display ERC4337 Smart Accounts in the server wallets table. * Enhanced wallet actions with a dropdown menu for sending test transactions and funding wallets. * **UI Improvements** * Updated wallet and smart account tables with improved layout, balance refresh, and modular components. * Refined button and modal styles across various components for a more consistent look and feel. * Adjusted the size of wallet avatars and icons for better visual balance. * Simplified and clarified descriptive text in payment links and wallet creation dialogs. * Improved switch component with a smaller size variant option. * Modified tab order in API key integration for better user flow. * Updated transaction page button styling and composition. * **Bug Fixes** * Removed the QR code-based receive funds modal, replacing it with the new funding modal. * **Chores** * Removed an unused dependency related to QR code generation. --- apps/dashboard/package.json | 3 +- .../fund-wallets-modal.stories.tsx | 49 ++ .../blocks/fund-wallets-modal/index.tsx | 280 +++++++++ .../@/components/blocks/wallet-address.tsx | 4 +- apps/dashboard/src/@/components/ui/switch.tsx | 7 +- .../ProjectFTUX/IntegrateAPIKeyCodeTabs.tsx | 4 +- .../components/backend-wallets-table.tsx | 94 +-- .../components/PaymentLinksTable.client.tsx | 6 +- .../(sidebar)/payments/links/page.tsx | 10 +- .../(sidebar)/transactions/layout.tsx | 8 +- .../create-server-wallet.client.tsx | 60 +- .../wallet-table/wallet-table-ui.client.tsx | 533 ++++++++++++------ pnpm-lock.yaml | 33 -- 13 files changed, 753 insertions(+), 338 deletions(-) create mode 100644 apps/dashboard/src/@/components/blocks/fund-wallets-modal/fund-wallets-modal.stories.tsx create mode 100644 apps/dashboard/src/@/components/blocks/fund-wallets-modal/index.tsx diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index 2a26242600b..af8254c1482 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -57,7 +57,6 @@ "react-error-boundary": "6.0.0", "react-hook-form": "7.55.0", "react-markdown": "10.1.0", - "react-qrcode-logo": "^3.0.0", "react-table": "^7.8.0", "recharts": "2.15.3", "remark-gfm": "4.0.1", @@ -130,4 +129,4 @@ "update-checkly": "npx checkly deploy" }, "version": "3.0.0" -} \ No newline at end of file +} diff --git a/apps/dashboard/src/@/components/blocks/fund-wallets-modal/fund-wallets-modal.stories.tsx b/apps/dashboard/src/@/components/blocks/fund-wallets-modal/fund-wallets-modal.stories.tsx new file mode 100644 index 00000000000..d44624544c0 --- /dev/null +++ b/apps/dashboard/src/@/components/blocks/fund-wallets-modal/fund-wallets-modal.stories.tsx @@ -0,0 +1,49 @@ +import type { Meta, StoryObj } from "@storybook/nextjs"; +import { useState } from "react"; +import { ConnectButton, ThirdwebProvider } from "thirdweb/react"; +import { Button } from "@/components/ui/button"; +import { storybookThirdwebClient } from "@/storybook/utils"; +import { FundWalletModal } from "./index"; + +const meta: Meta = { + title: "Blocks/FundWalletModal", + component: Variant, + decorators: [ + (Story) => ( + +
+ +
+ +
+ + ), + ], +}; + +export default meta; +type Story = StoryObj; + +export const Test: Story = { + args: {}, +}; +function Variant() { + const [isOpen, setIsOpen] = useState(false); + + return ( +
+ + + +
+ ); +} diff --git a/apps/dashboard/src/@/components/blocks/fund-wallets-modal/index.tsx b/apps/dashboard/src/@/components/blocks/fund-wallets-modal/index.tsx new file mode 100644 index 00000000000..b8dc90de256 --- /dev/null +++ b/apps/dashboard/src/@/components/blocks/fund-wallets-modal/index.tsx @@ -0,0 +1,280 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react"; +import { useTheme } from "next-themes"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import type { ThirdwebClient } from "thirdweb"; +import { defineChain } from "thirdweb/chains"; +import { CheckoutWidget, useActiveWalletChain } from "thirdweb/react"; +import { z } from "zod"; +import { SingleNetworkSelector } from "@/components/blocks/NetworkSelectors"; +import { TokenSelector } from "@/components/blocks/TokenSelector"; +import { Button } from "@/components/ui/button"; +import { DecimalInput } from "@/components/ui/decimal-input"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { getSDKTheme } from "@/utils/sdk-component-theme"; +import { WalletAddress } from "../wallet-address"; + +const formSchema = z.object({ + chainId: z.number({ + required_error: "Chain is required", + }), + token: z.object( + { + chainId: z.number(), + address: z.string(), + symbol: z.string(), + name: z.string(), + decimals: z.number(), + }, + { + required_error: "Token is required", + }, + ), + amount: z + .string() + .min(1, "Amount is required") + .refine((value) => { + const num = Number(value); + return !Number.isNaN(num) && num > 0; + }, "Amount must be greater than 0"), +}); + +type FormData = z.infer; + +type FundWalletModalProps = { + open: boolean; + onOpenChange: (open: boolean) => void; + title: string; + description: string; + recipientAddress: string; + client: ThirdwebClient; + defaultChainId?: number; + checkoutWidgetTitle?: string; +}; + +export function FundWalletModal(props: FundWalletModalProps) { + return ( + + + + + + ); +} + +function FundWalletModalContent(props: FundWalletModalProps) { + const [step, setStep] = useState<"form" | "checkout">("form"); + const activeChain = useActiveWalletChain(); + const { theme } = useTheme(); + + const form = useForm({ + defaultValues: { + chainId: props.defaultChainId || activeChain?.id || 1, + token: undefined, + amount: "0.1", + }, + mode: "onChange", + resolver: zodResolver(formSchema), + }); + + const selectedChainId = form.watch("chainId"); + + return ( +
+ {step === "form" ? ( +
+ { + setStep("checkout"); + })} + > + + {props.title} + {props.description} + + +
+
+

Recipient

+ +
+ + ( + + Chain + + { + field.onChange(token); + form.resetField("token", { + defaultValue: undefined, + }); + }} + client={props.client} + placeholder="Select a chain" + disableDeprecated + disableTestnets + disableChainId + /> + + + + )} + /> + + { + return ( + + Token + + + + + + ); + }} + /> + + ( + + Amount + +
+ + {form.watch("token") && ( +
+ {form.watch("token").symbol} +
+ )} +
+
+ +
+ )} + /> +
+ +
+ + +
+
+ + ) : ( +
+ + {props.title} + {props.description} + + +
+ +
+ +
+ + + +
+
+ )} +
+ ); +} diff --git a/apps/dashboard/src/@/components/blocks/wallet-address.tsx b/apps/dashboard/src/@/components/blocks/wallet-address.tsx index b4c6e284752..de6a1802fb8 100644 --- a/apps/dashboard/src/@/components/blocks/wallet-address.tsx +++ b/apps/dashboard/src/@/components/blocks/wallet-address.tsx @@ -192,11 +192,11 @@ function WalletAvatar(props: { return (
{resolvedAvatarSrc ? ( ) : ( diff --git a/apps/dashboard/src/@/components/ui/switch.tsx b/apps/dashboard/src/@/components/ui/switch.tsx index 071de9b0cbd..08d523a8bdf 100644 --- a/apps/dashboard/src/@/components/ui/switch.tsx +++ b/apps/dashboard/src/@/components/ui/switch.tsx @@ -7,12 +7,15 @@ import { cn } from "@/lib/utils"; const Switch = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef + React.ComponentPropsWithoutRef & { + size?: "sm"; + } >(({ className, ...props }, ref) => ( diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectFTUX/IntegrateAPIKeyCodeTabs.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectFTUX/IntegrateAPIKeyCodeTabs.tsx index 80f35934d75..8bf0524ae72 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectFTUX/IntegrateAPIKeyCodeTabs.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectFTUX/IntegrateAPIKeyCodeTabs.tsx @@ -6,10 +6,10 @@ import { TabButtons } from "@/components/ui/tabs"; type TabKey = "ts" | "react" | "react-native" | "dotnet" | "unity" | "unreal"; const tabNames: Record = { - dotnet: ".NET", + ts: "TypeScript", react: "React", "react-native": "React Native", - ts: "TypeScript", + dotnet: ".NET", unity: "Unity", unreal: "Unreal Engine", }; diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/overview/components/backend-wallets-table.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/overview/components/backend-wallets-table.tsx index a6d14072c62..b27be97b35e 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/overview/components/backend-wallets-table.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/engine/dedicated/(instance)/[engineId]/overview/components/backend-wallets-table.tsx @@ -11,20 +11,18 @@ import { UploadIcon, } from "lucide-react"; import Link from "next/link"; -import { useTheme } from "next-themes"; import { useMemo, useState } from "react"; import { useForm } from "react-hook-form"; -import { QRCode } from "react-qrcode-logo"; import { toast } from "sonner"; import { getAddress, type ThirdwebClient } from "thirdweb"; import { isAddress, shortenAddress } from "thirdweb/utils"; import { z } from "zod"; +import { FundWalletModal } from "@/components/blocks/fund-wallets-modal"; import { TWTable } from "@/components/blocks/TWTable"; import { WalletAddress } from "@/components/blocks/wallet-address"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; -import { CopyTextButton } from "@/components/ui/CopyTextButton"; import { Checkbox, CheckboxWithLabel } from "@/components/ui/checkbox"; import { Dialog, @@ -55,7 +53,6 @@ import { ToolTipLabel } from "@/components/ui/tooltip"; import { EngineBackendWalletOptions } from "@/constants/engine"; import { useAllChainsData } from "@/hooks/chains/allChains"; import { useV5DashboardChain } from "@/hooks/chains/v5-adapter"; -import { useIsMobile } from "@/hooks/use-mobile"; import { type BackendWallet, useEngineBackendWalletBalance, @@ -272,7 +269,7 @@ export const BackendWalletsTable: React.FC = ({ setSelectedBackendWallet(wallet); setReceiveOpen(true); }, - text: "Receive funds", + text: "Fund wallet", }, { icon: , @@ -311,9 +308,11 @@ export const BackendWalletsTable: React.FC = ({ )} {selectedBackendWallet && ( - @@ -443,87 +442,6 @@ function EditModalContent({ ); } -function ReceiveFundsModal({ - backendWallet, - open, - onOpenChange, -}: { - backendWallet: BackendWallet; - open: boolean; - onOpenChange: (open: boolean) => void; - client: ThirdwebClient; -}) { - // const qrCodeBase64Query = useQuery({ - // // only run this if we have a backendWallet address - // enabled: !!backendWallet.address, - // // start out with empty string - // placeholderData: "", - // queryFn: async () => { - // return new Promise((resolve, reject) => { - // QRCode.toDataURL( - // backendWallet.address, - // // biome-ignore lint/suspicious/noExplicitAny: FIXME - // (error: any, dataUrl: string) => { - // if (error) { - // reject(error); - // } else { - // resolve(dataUrl); - // } - // }, - // ); - // }); - // }, - // queryKey: ["engine", "receive-funds-qr-code", backendWallet.address], - // }); - - const qrCodeWidth = 300; - const isMobile = useIsMobile(); - const { theme } = useTheme(); - const isDarkMode = theme === "dark"; - - return ( - - - - Receive Funds - Send funds to this address - - -
-
- -
-
- - -
-
- ); -} - const SendFundsModal = ({ fromWallet, backendWallets, diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/links/components/PaymentLinksTable.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/links/components/PaymentLinksTable.client.tsx index 54da87f7750..15f307e2e3a 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/links/components/PaymentLinksTable.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/links/components/PaymentLinksTable.client.tsx @@ -191,8 +191,10 @@ export function PaymentLinksTable(props: { clientId: string; teamId: string }) { @@ -202,7 +204,7 @@ export function PaymentLinksTable(props: { clientId: string; teamId: string }) { paymentLinkId={paymentLink.id} > diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/links/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/links/page.tsx index 31acf2d1baf..2bd16a9a661 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/links/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/links/page.tsx @@ -2,7 +2,6 @@ import { PlusIcon } from "lucide-react"; import { redirect } from "next/navigation"; import { getProject } from "@/api/projects"; import { Button } from "@/components/ui/button"; -import { UnderlineLink } from "@/components/ui/UnderlineLink"; import { CreatePaymentLinkButton } from "./components/CreatePaymentLinkButton.client"; import { PaymentLinksTable } from "./components/PaymentLinksTable.client"; @@ -27,14 +26,7 @@ export default async function Page(props: { Payment Links

- Get notified for Bridge, Swap and Onramp events.{" "} - - Learn more - + Make money in any token instantly with hosted payment links

- - - + diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/transactions/server-wallets/components/create-server-wallet.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/transactions/server-wallets/components/create-server-wallet.client.tsx index 9c1b6407443..61d63443446 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/transactions/server-wallets/components/create-server-wallet.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/transactions/server-wallets/components/create-server-wallet.client.tsx @@ -1,7 +1,7 @@ "use client"; import { useMutation } from "@tanstack/react-query"; import { createEoa } from "@thirdweb-dev/vault-sdk"; -import { Loader2Icon, WalletIcon } from "lucide-react"; +import { PlusIcon } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; import { engineCloudProxy } from "@/actions/proxies"; @@ -10,11 +10,12 @@ import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, - DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Spinner } from "@/components/ui/Spinner/Spinner"; import { useDashboardRouter } from "@/lib/DashboardRouter"; import { initVaultClient } from "../../lib/vault.client"; @@ -100,19 +101,20 @@ export default function CreateServerWallet(props: { return ( <> - - + + Create server wallet - - Enter a label for your server wallet. - -
-
- setLabel(e.target.value)} - placeholder="Wallet label (optional)" - value={label} - /> -
+ +
+ + setLabel(e.target.value)} + placeholder="My Wallet" + value={label} + /> +

+ Adding a label will help you identify the wallet in the dashboard +

-
+ +
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/transactions/server-wallets/wallet-table/wallet-table-ui.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/transactions/server-wallets/wallet-table/wallet-table-ui.client.tsx index 75b2b9e035e..e5f92c46a72 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/transactions/server-wallets/wallet-table/wallet-table-ui.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/transactions/server-wallets/wallet-table/wallet-table-ui.client.tsx @@ -1,19 +1,33 @@ "use client"; -import { useQuery } from "@tanstack/react-query"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; import { format, formatDistanceToNowStrict } from "date-fns"; -import { SendIcon } from "lucide-react"; +import { + MoreVerticalIcon, + RefreshCcwIcon, + SendIcon, + XIcon, +} from "lucide-react"; import Link from "next/link"; -import { useId, useState } from "react"; -import type { ThirdwebClient } from "thirdweb"; +import { useMemo, useState } from "react"; +import { defineChain, type ThirdwebClient } from "thirdweb"; +import { useWalletBalance } from "thirdweb/react"; import { DEFAULT_ACCOUNT_FACTORY_V0_7, predictSmartAccountAddress, } from "thirdweb/wallets/smart"; import type { Project } from "@/api/projects"; +import { FundWalletModal } from "@/components/blocks/fund-wallets-modal"; +import { SingleNetworkSelector } from "@/components/blocks/NetworkSelectors"; import { WalletAddress } from "@/components/blocks/wallet-address"; -import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { Label } from "@/components/ui/label"; import { Pagination, PaginationContent, @@ -34,8 +48,8 @@ import { TableRow, } from "@/components/ui/table"; import { ToolTipLabel } from "@/components/ui/tooltip"; -import { useV5DashboardChain } from "@/hooks/chains/v5-adapter"; -import { useDashboardRouter } from "@/lib/DashboardRouter"; +import { WalletProductIcon } from "@/icons/WalletProductIcon"; +import { cn } from "@/lib/utils"; import CreateServerWallet from "../components/create-server-wallet.client"; import type { Wallet } from "./types"; @@ -58,190 +72,310 @@ export function ServerWalletsTableUI({ totalPages: number; client: ThirdwebClient; }) { - const [showContractWallets, setShowContractWallets] = useState(false); - const showContractWalletsId = useId(); + const [selectedChainId, setSelectedChainId] = useState(1); + const [showSmartAccount, setShowSmartAccount] = useState(false); + const queryClient = useQueryClient(); + return ( -
-
-
+
+
+
-

+
+
+ +
+
+

Server Wallets

- Create and manage server wallets for you project + Create and manage server wallets for your project

+ +
+ + +
+ + + +
+
-
- -
-
-
- - setShowContractWallets(!showContractWallets)} - /> -
+ + + + + Label + + {showSmartAccount + ? "Smart Account Address" + : "Wallet Address"} + + +
+ Balance + {wallets.length > 0 && ( + +
- - - - {showContractWallets ? "Smart Account Address" : "EOA Address"} - - Label - Created At - Updated At - Send test tx - - - - {wallets.map((wallet) => ( - - - {showContractWallets ? ( - - ) : ( - - )} - - {wallet.metadata.label || "none"} - - - - - - - - - + buttonEl.setAttribute("data-pending", "false"); + }} + size="sm" + variant="ghost" + > + + + + )} + + + Created + Actions - ))} - -
+ + + {wallets.map((wallet) => ( + + ))} + + + + {wallets.length === 0 && ( +
+
+ +
+

No server wallets found

+ +
+ )} +
- {wallets.length === 0 && ( -
- No server wallets found + {wallets.length > 0 && ( +
+
)} - - {totalPages > 1 && ( -
-
- Found {totalRecords} server wallets + {totalPages > 1 && ( +
+
+ Found {totalRecords} server wallets +
+ + + + 1 ? currentPage - 1 : 1 + }`} + legacyBehavior + passHref + > + + + + {Array.from({ length: totalPages }, (_, i) => i + 1).map( + (pageNumber) => ( + + + + {pageNumber} + + + + ), + )} + + + = totalPages + ? "pointer-events-none opacity-50" + : "" + } + /> + + + +
- - - - 1 ? currentPage - 1 : 1 - }`} - legacyBehavior - passHref - > - - - - {Array.from({ length: totalPages }, (_, i) => i + 1).map( - (pageNumber) => ( - - - - {pageNumber} - - - - ), - )} - - - = totalPages - ? "pointer-events-none opacity-50" - : "" - } - /> - - - - -
- )} + )} +
); } -function SmartAccountCell({ - wallet, - client, -}: { +function ServerWalletTableRow(props: { wallet: Wallet; + project: Project; + teamSlug: string; client: ThirdwebClient; + chainId: number; + showSmartAccount: boolean; }) { - const chainId = 1; // TODO: add chain switcher for balance + smart account address - const chain = useV5DashboardChain(chainId); + const { wallet, project, teamSlug, client, chainId, showSmartAccount } = + props; + + const chain = useMemo(() => { + // eslint-disable-next-line no-restricted-syntax + return defineChain(chainId); + }, [chainId]); const smartAccountAddressQuery = useQuery({ queryFn: async () => { const smartAccountAddress = await predictSmartAccountAddress({ adminAddress: wallet.address, - chain, + + chain: chain, client: client, factoryAddress: DEFAULT_ACCOUNT_FACTORY_V0_7, }); return smartAccountAddress; }, - queryKey: ["smart-account-address", wallet.address], + enabled: showSmartAccount, + queryKey: ["smart-account-address", wallet.address, chainId], }); return ( -
- {smartAccountAddressQuery.data ? ( -
- + {/* Label */} + + + {wallet.metadata.label || "N/A"} + + + + {/* Address */} + + {showSmartAccount ? ( +
+ {smartAccountAddressQuery.data ? ( + + ) : smartAccountAddressQuery.isPending ? ( + + ) : ( + N/A + )} +
+ ) : ( + + )} +
+ + {/* Balance */} + + {showSmartAccount && ( + // biome-ignore lint/complexity/noUselessFragments: keep for readability + <> + {smartAccountAddressQuery.isPending ? ( + + ) : smartAccountAddressQuery.data ? ( + + ) : ( + N/A + )} + + )} + + {!showSmartAccount && ( + - Smart Account -
- ) : ( - - )} -
+ )} + + + + + + + + + + ); } @@ -258,23 +392,90 @@ function WalletDateCell({ date }: { date: string }) { ); } -function SendTestTransaction(props: { +function WalletActionsDropdown(props: { + fundWalletAddress: string | undefined; wallet: Wallet; teamSlug: string; project: Project; + client: ThirdwebClient; + chainId: number; }) { - const router = useDashboardRouter(); + const [showFundModal, setShowFundModal] = useState(false); + + return ( + <> + + + + + + + + + Send test transaction + + + {props.fundWalletAddress && ( + setShowFundModal(true)} + className="flex items-center gap-2 h-9 rounded-lg" + > + + Fund wallet + + )} + + + + {props.fundWalletAddress && ( + + )} + + ); +} + +function WalletBalanceCell(props: { + address: string; + chainId: number; + client: ThirdwebClient; +}) { + const balance = useWalletBalance({ + address: props.address, + // eslint-disable-next-line no-restricted-syntax + chain: defineChain(props.chainId), + client: props.client, + }); + + if (balance.isFetching) { + return ; + } + + if (!balance.data) { + return N/A; + } + return ( - + + {balance.data.displayValue} {balance.data.symbol} + ); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cc35547b306..9ac90452f5a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -232,9 +232,6 @@ importers: react-markdown: specifier: 10.1.0 version: 10.1.0(@types/react@19.1.8)(react@19.1.0) - react-qrcode-logo: - specifier: ^3.0.0 - version: 3.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-table: specifier: ^7.8.0 version: 7.8.0(react@19.1.0) @@ -384,8 +381,6 @@ importers: specifier: 5.8.3 version: 5.8.3 - apps/login: {} - apps/nebula: dependencies: '@hookform/resolvers': @@ -1462,10 +1457,6 @@ importers: specifier: 3.2.4 version: 3.2.4(@types/debug@4.1.12)(@types/node@24.0.10)(@vitest/ui@3.2.4)(happy-dom@17.4.4)(jiti@2.4.2)(lightningcss@1.30.1)(msw@2.7.5(@types/node@24.0.10)(typescript@5.8.3))(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0) - packages/typedoc-gen: {} - - packages/unity-js-bridge: {} - packages/vault-sdk: dependencies: '@noble/ciphers': @@ -11117,10 +11108,6 @@ packages: lodash.isarguments@3.1.0: resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} - lodash.isequal@4.5.0: - resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} - deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. - lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -12827,9 +12814,6 @@ packages: resolution: {integrity: sha512-s0/5XkAWe0/dWISiljdrybjwDCHhgN31Nu/wznOZPKeikgcJtZtbvPKBz0t802XWqfSQnQDt3L6xiAE5JLlfuw==} engines: {node: '>=18'} - qrcode-generator@1.5.2: - resolution: {integrity: sha512-pItrW0Z9HnDBnFmgiNrY1uxRdri32Uh9EjNYLPVC2zZ3ZRIIEqBoDgm4DkvDwNNDHTK7FNkmr8zAa77BYc9xNw==} - qrcode-terminal@0.11.0: resolution: {integrity: sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ==} hasBin: true @@ -13072,12 +13056,6 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' - react-qrcode-logo@3.0.0: - resolution: {integrity: sha512-2+vZ3GNBdUpYxIKyt6SFZsDGXa0xniyUQ0wPI4O0hJTzRjttPIx1pPnH9IWQmp/4nDMoN47IBhi3Breu1KudYw==} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - react-redux@9.2.0: resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==} peerDependencies: @@ -30552,8 +30530,6 @@ snapshots: lodash.isarguments@3.1.0: {} - lodash.isequal@4.5.0: {} - lodash.merge@4.6.2: {} lodash.startcase@4.4.0: {} @@ -32711,8 +32687,6 @@ snapshots: - supports-color - utf-8-validate - qrcode-generator@1.5.2: {} - qrcode-terminal@0.11.0: {} qrcode@1.5.3: @@ -33043,13 +33017,6 @@ snapshots: react-dom: 19.1.0(react@19.1.0) tinycolor2: 1.6.0 - react-qrcode-logo@3.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): - dependencies: - lodash.isequal: 4.5.0 - qrcode-generator: 1.5.2 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - react-redux@9.2.0(@types/react@19.1.8)(react@19.1.0)(redux@5.0.1): dependencies: '@types/use-sync-external-store': 0.0.6