Skip to content

Commit 834bbc0

Browse files
committed
[MNY-62] Dashboard: Add CheckoutWidget in server wallets & backend wallets table. server wallets UI improvements (#7782)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## 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}` <!-- end pr-codex --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## 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. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 3a9aa1a commit 834bbc0

File tree

13 files changed

+753
-338
lines changed

13 files changed

+753
-338
lines changed

apps/dashboard/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@
5757
"react-error-boundary": "6.0.0",
5858
"react-hook-form": "7.55.0",
5959
"react-markdown": "10.1.0",
60-
"react-qrcode-logo": "^3.0.0",
6160
"react-table": "^7.8.0",
6261
"recharts": "2.15.3",
6362
"remark-gfm": "4.0.1",
@@ -130,4 +129,4 @@
130129
"update-checkly": "npx checkly deploy"
131130
},
132131
"version": "3.0.0"
133-
}
132+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import type { Meta, StoryObj } from "@storybook/nextjs";
2+
import { useState } from "react";
3+
import { ConnectButton, ThirdwebProvider } from "thirdweb/react";
4+
import { Button } from "@/components/ui/button";
5+
import { storybookThirdwebClient } from "@/storybook/utils";
6+
import { FundWalletModal } from "./index";
7+
8+
const meta: Meta<typeof FundWalletModal> = {
9+
title: "Blocks/FundWalletModal",
10+
component: Variant,
11+
decorators: [
12+
(Story) => (
13+
<ThirdwebProvider>
14+
<div className="p-10">
15+
<ConnectButton client={storybookThirdwebClient} />
16+
<div className="h-4" />
17+
<Story />
18+
</div>
19+
</ThirdwebProvider>
20+
),
21+
],
22+
};
23+
24+
export default meta;
25+
type Story = StoryObj<typeof meta>;
26+
27+
export const Test: Story = {
28+
args: {},
29+
};
30+
function Variant() {
31+
const [isOpen, setIsOpen] = useState(false);
32+
33+
return (
34+
<div>
35+
<Button type="button" onClick={() => setIsOpen(true)}>
36+
Open
37+
</Button>
38+
39+
<FundWalletModal
40+
open={isOpen}
41+
onOpenChange={setIsOpen}
42+
title="This is a title"
43+
description="This is a description"
44+
recipientAddress="0x83Dd93fA5D8343094f850f90B3fb90088C1bB425"
45+
client={storybookThirdwebClient}
46+
/>
47+
</div>
48+
);
49+
}
Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
"use client";
2+
3+
import { zodResolver } from "@hookform/resolvers/zod";
4+
import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react";
5+
import { useTheme } from "next-themes";
6+
import { useState } from "react";
7+
import { useForm } from "react-hook-form";
8+
import type { ThirdwebClient } from "thirdweb";
9+
import { defineChain } from "thirdweb/chains";
10+
import { CheckoutWidget, useActiveWalletChain } from "thirdweb/react";
11+
import { z } from "zod";
12+
import { SingleNetworkSelector } from "@/components/blocks/NetworkSelectors";
13+
import { TokenSelector } from "@/components/blocks/TokenSelector";
14+
import { Button } from "@/components/ui/button";
15+
import { DecimalInput } from "@/components/ui/decimal-input";
16+
import {
17+
Dialog,
18+
DialogContent,
19+
DialogDescription,
20+
DialogHeader,
21+
DialogTitle,
22+
} from "@/components/ui/dialog";
23+
import {
24+
Form,
25+
FormControl,
26+
FormField,
27+
FormItem,
28+
FormLabel,
29+
FormMessage,
30+
} from "@/components/ui/form";
31+
import { getSDKTheme } from "@/utils/sdk-component-theme";
32+
import { WalletAddress } from "../wallet-address";
33+
34+
const formSchema = z.object({
35+
chainId: z.number({
36+
required_error: "Chain is required",
37+
}),
38+
token: z.object(
39+
{
40+
chainId: z.number(),
41+
address: z.string(),
42+
symbol: z.string(),
43+
name: z.string(),
44+
decimals: z.number(),
45+
},
46+
{
47+
required_error: "Token is required",
48+
},
49+
),
50+
amount: z
51+
.string()
52+
.min(1, "Amount is required")
53+
.refine((value) => {
54+
const num = Number(value);
55+
return !Number.isNaN(num) && num > 0;
56+
}, "Amount must be greater than 0"),
57+
});
58+
59+
type FormData = z.infer<typeof formSchema>;
60+
61+
type FundWalletModalProps = {
62+
open: boolean;
63+
onOpenChange: (open: boolean) => void;
64+
title: string;
65+
description: string;
66+
recipientAddress: string;
67+
client: ThirdwebClient;
68+
defaultChainId?: number;
69+
checkoutWidgetTitle?: string;
70+
};
71+
72+
export function FundWalletModal(props: FundWalletModalProps) {
73+
return (
74+
<Dialog open={props.open} onOpenChange={props.onOpenChange}>
75+
<DialogContent className="p-0 gap-0 !w-full !max-w-md">
76+
<FundWalletModalContent
77+
open={props.open}
78+
onOpenChange={props.onOpenChange}
79+
title={props.title}
80+
description={props.description}
81+
recipientAddress={props.recipientAddress}
82+
client={props.client}
83+
defaultChainId={props.defaultChainId}
84+
checkoutWidgetTitle={props.checkoutWidgetTitle}
85+
/>
86+
</DialogContent>
87+
</Dialog>
88+
);
89+
}
90+
91+
function FundWalletModalContent(props: FundWalletModalProps) {
92+
const [step, setStep] = useState<"form" | "checkout">("form");
93+
const activeChain = useActiveWalletChain();
94+
const { theme } = useTheme();
95+
96+
const form = useForm<FormData>({
97+
defaultValues: {
98+
chainId: props.defaultChainId || activeChain?.id || 1,
99+
token: undefined,
100+
amount: "0.1",
101+
},
102+
mode: "onChange",
103+
resolver: zodResolver(formSchema),
104+
});
105+
106+
const selectedChainId = form.watch("chainId");
107+
108+
return (
109+
<div>
110+
{step === "form" ? (
111+
<Form {...form}>
112+
<form
113+
onSubmit={form.handleSubmit(() => {
114+
setStep("checkout");
115+
})}
116+
>
117+
<DialogHeader className="p-4 lg:p-6">
118+
<DialogTitle>{props.title}</DialogTitle>
119+
<DialogDescription>{props.description}</DialogDescription>
120+
</DialogHeader>
121+
122+
<div className="space-y-4 px-4 lg:px-6 pb-8">
123+
<div>
124+
<h3 className="text-sm font-medium"> Recipient</h3>
125+
<WalletAddress
126+
address={props.recipientAddress}
127+
client={props.client}
128+
className="py-1 h-auto"
129+
iconClassName="size-4"
130+
/>
131+
</div>
132+
133+
<FormField
134+
control={form.control}
135+
name="chainId"
136+
render={({ field }) => (
137+
<FormItem>
138+
<FormLabel>Chain</FormLabel>
139+
<FormControl>
140+
<SingleNetworkSelector
141+
className="bg-card"
142+
chainId={field.value}
143+
onChange={(token) => {
144+
field.onChange(token);
145+
form.resetField("token", {
146+
defaultValue: undefined,
147+
});
148+
}}
149+
client={props.client}
150+
placeholder="Select a chain"
151+
disableDeprecated
152+
disableTestnets
153+
disableChainId
154+
/>
155+
</FormControl>
156+
<FormMessage />
157+
</FormItem>
158+
)}
159+
/>
160+
161+
<FormField
162+
control={form.control}
163+
name="token"
164+
render={({ field }) => {
165+
return (
166+
<FormItem>
167+
<FormLabel>Token</FormLabel>
168+
<FormControl>
169+
<TokenSelector
170+
className="bg-card"
171+
disableAddress
172+
selectedToken={
173+
field.value
174+
? {
175+
chainId: field.value.chainId,
176+
address: field.value.address,
177+
}
178+
: undefined
179+
}
180+
onChange={field.onChange}
181+
chainId={selectedChainId}
182+
client={props.client}
183+
placeholder="Select a token"
184+
enabled={!!selectedChainId}
185+
addNativeTokenIfMissing={true}
186+
showCheck={true}
187+
/>
188+
</FormControl>
189+
<FormMessage />
190+
</FormItem>
191+
);
192+
}}
193+
/>
194+
195+
<FormField
196+
control={form.control}
197+
name="amount"
198+
render={({ field }) => (
199+
<FormItem>
200+
<FormLabel>Amount</FormLabel>
201+
<FormControl>
202+
<div className="relative">
203+
<DecimalInput
204+
className="bg-card !text-xl h-auto font-semibold"
205+
value={field.value}
206+
onChange={field.onChange}
207+
placeholder="0.1"
208+
/>
209+
{form.watch("token") && (
210+
<div className="absolute right-4 top-1/2 -translate-y-1/2 text-sm text-muted-foreground">
211+
{form.watch("token").symbol}
212+
</div>
213+
)}
214+
</div>
215+
</FormControl>
216+
<FormMessage />
217+
</FormItem>
218+
)}
219+
/>
220+
</div>
221+
222+
<div className="flex justify-end gap-3 p-4 lg:p-6 bg-card border-t rounded-b-lg">
223+
<Button
224+
variant="outline"
225+
onClick={() => props.onOpenChange(false)}
226+
>
227+
Cancel
228+
</Button>
229+
<Button type="submit" className="gap-2">
230+
Next
231+
<ArrowRightIcon className="w-4 h-4" />
232+
</Button>
233+
</div>
234+
</form>
235+
</Form>
236+
) : (
237+
<div>
238+
<DialogHeader className="sr-only">
239+
<DialogTitle>{props.title}</DialogTitle>
240+
<DialogDescription>{props.description}</DialogDescription>
241+
</DialogHeader>
242+
243+
<div>
244+
<CheckoutWidget
245+
client={props.client}
246+
// eslint-disable-next-line no-restricted-syntax
247+
chain={defineChain(form.getValues("chainId"))}
248+
tokenAddress={form.getValues("token").address as `0x${string}`}
249+
amount={form.getValues("amount")}
250+
seller={props.recipientAddress as `0x${string}`}
251+
showThirdwebBranding={false}
252+
className="!w-full !max-w-full !min-w-0 !rounded-b-none !border-none"
253+
theme={getSDKTheme(theme === "dark" ? "dark" : "light")}
254+
name={props.checkoutWidgetTitle}
255+
/>
256+
</div>
257+
258+
<div className="flex justify-end gap-3 p-4 lg:p-6 bg-card border-t rounded-b-lg">
259+
<Button
260+
variant="outline"
261+
onClick={() => setStep("form")}
262+
className="gap-2"
263+
>
264+
<ArrowLeftIcon className="size-4 text-muted-foreground" />
265+
Back
266+
</Button>
267+
268+
<Button
269+
className="gap-2"
270+
variant="outline"
271+
onClick={() => props.onOpenChange(false)}
272+
>
273+
Cancel
274+
</Button>
275+
</div>
276+
</div>
277+
)}
278+
</div>
279+
);
280+
}

apps/dashboard/src/@/components/blocks/wallet-address.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,11 +192,11 @@ function WalletAvatar(props: {
192192

193193
return (
194194
<div
195-
className={cn("size-6 overflow-hidden rounded-full", props.iconClassName)}
195+
className={cn("size-5 overflow-hidden rounded-full", props.iconClassName)}
196196
>
197197
{resolvedAvatarSrc ? (
198198
<Img
199-
className={cn("size-6 object-cover", props.iconClassName)}
199+
className={cn("size-5 object-cover", props.iconClassName)}
200200
src={resolvedAvatarSrc}
201201
/>
202202
) : (

apps/dashboard/src/@/components/ui/switch.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,24 @@ import { cn } from "@/lib/utils";
77

88
const Switch = React.forwardRef<
99
React.ElementRef<typeof SwitchPrimitives.Root>,
10-
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
10+
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root> & {
11+
size?: "sm";
12+
}
1113
>(({ className, ...props }, ref) => (
1214
<SwitchPrimitives.Root
1315
className={cn(
1416
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-foreground data-[state=unchecked]:bg-input",
1517
className,
18+
props.size === "sm" && "h-4.5 w-9",
1619
)}
1720
{...props}
1821
ref={ref}
1922
>
2023
<SwitchPrimitives.Thumb
2124
className={cn(
2225
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0",
26+
props.size === "sm" &&
27+
"h-4 w-4 translate-x-4 data-[state=checked]:translate-x-4",
2328
)}
2429
/>
2530
</SwitchPrimitives.Root>

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectFTUX/IntegrateAPIKeyCodeTabs.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import { TabButtons } from "@/components/ui/tabs";
66
type TabKey = "ts" | "react" | "react-native" | "dotnet" | "unity" | "unreal";
77

88
const tabNames: Record<TabKey, string> = {
9-
dotnet: ".NET",
9+
ts: "TypeScript",
1010
react: "React",
1111
"react-native": "React Native",
12-
ts: "TypeScript",
12+
dotnet: ".NET",
1313
unity: "Unity",
1414
unreal: "Unreal Engine",
1515
};

0 commit comments

Comments
 (0)