Skip to content

Commit 23cb3bd

Browse files
committed
Dashboard: /pay/<id> page UI improvements, SDK: CheckoutWidget minor UI tweaks (#8310)
<!-- ## 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 UI enhancements and minor adjustments in the `CheckoutWidget` and related components of the payment dashboard, improving styling, layout, and functionality. ### Detailed summary - Minor UI adjustments in `CheckoutWidget`. - Updated `formatTokenBalance.ts` to allow more flexible currency formatting. - Added `trackingTight` prop to `Text` in `FiatValue.tsx`. - Enhanced `PayPageWidget` with new class names for styling. - Adjusted color values in SDK theme definitions. - Improved layout and spacing in `WithHeader.tsx`. - Modified text properties in `DirectPayment.tsx` for better readability. - Created a new `PayIdPageHeader` component for improved header structure. - Replaced complex JSX in `PayPage` with cleaner component structure, including `DotsBackgroundPattern`. > ✨ 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** * Theme toggle added to Pay page headers; streamlined Pay page header and layout with decorative background. * **Style** * Refined secondary/tertiary button and background colors, typography weights/letter spacing, spacing and divider styles. * Adjusted price and label styling, currency formatting precision, and visual spacing around branding. * Pay widget now uses a fallback image when no project image is provided. * **Chores** * Added changelog entry documenting a patch release. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent dcef3f4 commit 23cb3bd

File tree

10 files changed

+155
-189
lines changed

10 files changed

+155
-189
lines changed

.changeset/curly-crews-sleep.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
Minor UI adjustments in CheckoutWidget
2.06 MB
Loading

apps/dashboard/src/@/utils/sdk-component-theme.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ export function getSDKTheme(theme: "light" | "dark"): Theme {
2121
primaryButtonText: "hsl(var(--inverted-foreground))",
2222
primaryText: "hsl(var(--foreground))",
2323
scrollbarBg: "hsl(var(--muted))",
24-
secondaryButtonBg: "hsl(var(--secondary))",
25-
secondaryButtonHoverBg: "hsl(var(--secondary)/80%)",
24+
secondaryButtonBg: "hsl(var(--secondary)/70%)",
25+
secondaryButtonHoverBg: "hsl(var(--secondary))",
2626
secondaryButtonText: "hsl(var(--secondary-foreground))",
2727
secondaryIconColor: "hsl(var(--secondary-foreground))",
2828
secondaryIconHoverBg: "hsl(var(--accent))",
@@ -33,7 +33,7 @@ export function getSDKTheme(theme: "light" | "dark"): Theme {
3333
separatorLine: "hsl(var(--border))",
3434
skeletonBg: "hsl(var(--secondary-foreground)/15%)",
3535
success: "hsl(var(--success-text))",
36-
tertiaryBg: "hsl(var(--muted)/30%)",
36+
tertiaryBg: "hsl(var(--muted)/50%)",
3737
tooltipBg: "hsl(var(--popover))",
3838
tooltipText: "hsl(var(--popover-foreground))",
3939
},
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"use client";
2+
import { Img } from "@workspace/ui/components/img";
3+
import { MoonIcon, SunIcon } from "lucide-react";
4+
import { useTheme } from "next-themes";
5+
import { ClientOnly } from "@/components/blocks/client-only";
6+
import { Button } from "@/components/ui/button";
7+
import { Skeleton } from "@/components/ui/skeleton";
8+
import { cn } from "@/lib/utils";
9+
import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler";
10+
import { payAppThirdwebClient } from "../constants";
11+
12+
export function PayIdPageHeader(props: {
13+
projectName: string;
14+
projectIcon: string | undefined;
15+
}) {
16+
return (
17+
<div className="border-b border-border/70">
18+
<header className="container flex max-w-7xl justify-between py-4">
19+
<div className="flex items-center gap-3">
20+
{props.projectIcon && (
21+
<Img
22+
src={
23+
resolveSchemeWithErrorHandler({
24+
uri: props.projectIcon,
25+
client: payAppThirdwebClient,
26+
}) || ""
27+
}
28+
alt=""
29+
className="rounded-full size-6 object-cover"
30+
/>
31+
)}
32+
33+
<h2 className="text-xl font-semibold tracking-tight">
34+
{props.projectName}
35+
</h2>
36+
</div>
37+
38+
<div className="flex items-center gap-3 lg:gap-5">
39+
<ToggleThemeButton />
40+
</div>
41+
</header>
42+
</div>
43+
);
44+
}
45+
46+
function ToggleThemeButton(props: { className?: string }) {
47+
const { setTheme, theme } = useTheme();
48+
49+
return (
50+
<ClientOnly
51+
ssr={<Skeleton className="size-[36px] rounded-full border bg-accent" />}
52+
>
53+
<Button
54+
aria-label="Toggle theme"
55+
className={cn(
56+
"h-auto w-auto rounded-full p-2 text-muted-foreground hover:text-foreground",
57+
props.className,
58+
)}
59+
onClick={() => {
60+
setTheme(theme === "dark" ? "light" : "dark");
61+
}}
62+
variant="ghost"
63+
>
64+
{theme === "light" ? (
65+
<SunIcon className="size-5 " />
66+
) : (
67+
<MoonIcon className="size-5 " />
68+
)}
69+
</Button>
70+
</ClientOnly>
71+
);
72+
}
Lines changed: 46 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,16 @@
1-
import { ShieldCheckIcon } from "lucide-react";
1+
import { cn } from "@workspace/ui/lib/utils";
22
import type { Metadata } from "next";
33
import { ThemeProvider } from "next-themes";
4-
import { Bridge, defineChain, toTokens } from "thirdweb";
5-
import { getChainMetadata } from "thirdweb/chains";
6-
import { shortenAddress } from "thirdweb/utils";
4+
import { Bridge } from "thirdweb";
75
import { getPaymentLink } from "@/api/universal-bridge/links";
8-
import { Badge } from "@/components/ui/badge";
96
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
107
import {
118
API_SERVER_SECRET,
129
DASHBOARD_THIRDWEB_SECRET_KEY,
1310
} from "@/constants/server-envs";
1411
import { getConfiguredThirdwebClient } from "@/constants/thirdweb.server";
15-
import { resolveEns } from "@/lib/ens";
16-
import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler";
1712
import { PayPageWidget } from "../components/client/PayPageWidget.client";
18-
import { payAppThirdwebClient } from "../constants";
13+
import { PayIdPageHeader } from "./header";
1914

2015
const title = "thirdweb Pay";
2116
const description = "Fast, secure, and simple payments.";
@@ -54,171 +49,44 @@ export default async function PayPage({
5449
tokenAddress: paymentLink.destinationToken.address,
5550
});
5651

57-
const chainPromise = getChainMetadata(
58-
// eslint-disable-next-line no-restricted-syntax
59-
defineChain(Number(paymentLink.destinationToken.chainId)),
60-
);
61-
62-
const recipientPromise = resolveEns(
63-
paymentLink.receiver,
64-
getConfiguredThirdwebClient({
65-
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
66-
teamId: undefined,
67-
}),
68-
);
69-
70-
const [tokens, projectMetadata, chain, recipientEnsOrAddress] =
71-
await Promise.all([
72-
tokensPromise,
73-
projectMetadataPromise,
74-
chainPromise,
75-
recipientPromise,
76-
]);
52+
const [tokens, projectMetadata] = await Promise.all([
53+
tokensPromise,
54+
projectMetadataPromise,
55+
]);
7756

7857
const token = tokens[0];
7958
if (!token) {
8059
throw new Error("Token not found");
8160
}
8261

8362
return (
84-
<div className="relative flex h-dvh w-full items-center justify-center">
63+
<div className="relative flex flex-col min-h-dvh w-full">
8564
<ThemeProvider
86-
forcedTheme={theme === "light" ? "light" : "dark"}
65+
defaultTheme={theme === "light" ? "light" : "dark"}
8766
attribute="class"
8867
disableTransitionOnChange
8968
enableSystem={false}
9069
>
91-
<div className="flex z-10 flex-col lg:flex-row h-full w-full">
92-
<header className="min-w-full lg:min-w-[500px] border-b lg:border-r lg:h-full bg-card flex flex-col gap-4 items-start p-4 lg:p-8">
93-
<div>
94-
<div className="flex flex-row items-center justify-start gap-4">
95-
{projectMetadata.image && (
96-
<img
97-
src={
98-
resolveSchemeWithErrorHandler({
99-
uri: projectMetadata.image,
100-
client: payAppThirdwebClient,
101-
}) || ""
102-
}
103-
alt={projectMetadata.name}
104-
width={25}
105-
height={25}
106-
className="rounded-full overflow-hidden"
107-
/>
108-
)}
109-
<h2 className="text-xl font-bold">{projectMetadata.name}</h2>
110-
</div>
111-
{projectMetadata.description && (
112-
<p className="mt-2 text-sm text-muted-foreground">
113-
{projectMetadata.description}
114-
</p>
115-
)}
116-
</div>
117-
118-
<div className="hidden lg:block my-4 w-full">
119-
{paymentLink.amount && (
120-
<div className="flex flex-col gap-1 w-full my-4">
121-
<span className="text-muted-foreground text-xs">Details</span>
122-
<div className="font-medium flex-row flex justify-between items-center w-full">
123-
<div className="flex flex-row items-center gap-2">
124-
{token.iconUri && (
125-
<img
126-
src={resolveSchemeWithErrorHandler({
127-
uri: token.iconUri,
128-
client: getConfiguredThirdwebClient({
129-
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
130-
teamId: undefined,
131-
}),
132-
})}
133-
alt={token.name}
134-
width={25}
135-
height={25}
136-
className="size-5 rounded-full overflow-hidden"
137-
/>
138-
)}
139-
{toTokens(BigInt(paymentLink.amount), token.decimals)}{" "}
140-
{token.symbol}
141-
</div>
142-
{token.prices.USD && (
143-
<span>
144-
$
145-
{(
146-
Number(token.prices.USD) *
147-
Number(
148-
toTokens(
149-
BigInt(paymentLink.amount),
150-
token.decimals,
151-
),
152-
)
153-
).toFixed(2)}
154-
</span>
155-
)}
156-
</div>
157-
</div>
158-
)}
159-
{chain && (
160-
<div className="flex flex-col gap-1 w-full my-4">
161-
<span className="text-muted-foreground text-xs">Network</span>
162-
<div className="font-medium flex-row flex justify-between items-center w-full">
163-
<div className="flex flex-row items-center gap-2">
164-
{chain.icon?.url && (
165-
<img
166-
src={resolveSchemeWithErrorHandler({
167-
uri: chain.icon.url,
168-
client: getConfiguredThirdwebClient({
169-
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
170-
teamId: undefined,
171-
}),
172-
})}
173-
alt={chain.name}
174-
width={chain.icon.width}
175-
height={chain.icon.height}
176-
className="size-5 rounded-full overflow-hidden"
177-
/>
178-
)}
179-
{chain.name}
180-
</div>
181-
</div>
182-
</div>
183-
)}
184-
{recipientEnsOrAddress.ensName ||
185-
(recipientEnsOrAddress.address && (
186-
<div className="flex flex-col gap-1 w-full my-4">
187-
<span className="text-muted-foreground text-xs">
188-
Seller
189-
</span>
190-
<div className="font-medium flex-row flex justify-between items-center w-full">
191-
{recipientEnsOrAddress.ensName ??
192-
shortenAddress(recipientEnsOrAddress.address)}
193-
</div>
194-
</div>
195-
))}
196-
</div>
70+
<PayIdPageHeader
71+
projectIcon={projectMetadata.image || undefined}
72+
projectName={projectMetadata.name}
73+
/>
19774

198-
<div className="mt-auto hidden lg:block">
199-
<Badge className="flex items-center gap-1.5 bg-purple-100 text-purple-800 border-purple-200 dark:bg-purple-950 dark:text-purple-300 dark:border-purple-800">
200-
<ShieldCheckIcon className="size-3" />
201-
Secured by thirdweb
202-
</Badge>
203-
</div>
204-
</header>
205-
<main className="flex justify-center py-12 w-full items-center grow">
206-
<PayPageWidget
207-
amount={
208-
paymentLink.amount ? BigInt(paymentLink.amount) : undefined
209-
}
210-
chainId={Number(paymentLink.destinationToken.chainId)}
211-
clientId={undefined} // Payment links don't need to use the same client ID to be executed
212-
image={paymentLink.imageUrl}
213-
name={paymentLink.title}
214-
paymentLinkId={id}
215-
purchaseData={paymentLink.purchaseData}
216-
recipientAddress={paymentLink.receiver}
217-
redirectUri={redirectUri}
218-
token={token}
219-
/>
220-
</main>
221-
</div>
75+
<main className="flex justify-center py-12 w-full items-center grow overflow-hidden relative">
76+
<DotsBackgroundPattern />
77+
<PayPageWidget
78+
amount={paymentLink.amount ? BigInt(paymentLink.amount) : undefined}
79+
chainId={Number(paymentLink.destinationToken.chainId)}
80+
clientId={undefined} // Payment links don't need to use the same client ID to be executed
81+
image={paymentLink.imageUrl}
82+
name={paymentLink.title}
83+
paymentLinkId={id}
84+
purchaseData={paymentLink.purchaseData}
85+
recipientAddress={paymentLink.receiver}
86+
redirectUri={redirectUri}
87+
token={token}
88+
/>
89+
</main>
22290
</ThemeProvider>
22391
</div>
22492
);
@@ -237,7 +105,24 @@ async function getProjectMetadata(clientId: string) {
237105
}
238106

239107
const { data } = (await response.json()) as {
240-
data: { name: string; image: string | null; description: string | null };
108+
data: { name: string; image: string | null };
241109
};
242110
return data;
243111
}
112+
113+
function DotsBackgroundPattern(props: { className?: string }) {
114+
return (
115+
<div
116+
className={cn(
117+
"pointer-events-none absolute -inset-x-36 -inset-y-24 text-foreground/20 dark:text-muted-foreground/15 hidden lg:block",
118+
props.className,
119+
)}
120+
style={{
121+
backgroundImage: "radial-gradient(currentColor 1px, transparent 1px)",
122+
backgroundSize: "24px 24px",
123+
maskImage:
124+
"radial-gradient(ellipse 100% 100% at 50% 50%, black 30%, transparent 50%)",
125+
}}
126+
/>
127+
);
128+
}

apps/dashboard/src/app/pay/components/client/PayPageWidget.client.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"use client";
2+
import { cn } from "@workspace/ui/lib/utils";
23
import { payAppThirdwebClient } from "app/pay/constants";
34
import { useTheme } from "next-themes";
45
import { createThirdwebClient, NATIVE_TOKEN_ADDRESS, toTokens } from "thirdweb";
@@ -63,7 +64,12 @@ export function PayPageWidget({
6364
client={
6465
clientId ? createThirdwebClient({ clientId }) : payAppThirdwebClient
6566
}
66-
image={image}
67+
className={cn(
68+
"shadow-xl",
69+
!image &&
70+
"[&_.tw-header-image]:invert dark:[&_.tw-header-image]:invert-0",
71+
)}
72+
image={image || "/assets/pay/general-pay.png"}
6773
name={name}
6874
onSuccess={() => {
6975
reportPaymentLinkBuySuccessful();

0 commit comments

Comments
 (0)