-
Notifications
You must be signed in to change notification settings - Fork 615
Dashboard: /pay/<id> page UI improvements, SDK: CheckoutWidget minor UI tweaks #8310
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "thirdweb": patch | ||
| --- | ||
|
|
||
| Minor UI adjustments in CheckoutWidget |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| "use client"; | ||
| import { Img } from "@workspace/ui/components/img"; | ||
| import { MoonIcon, SunIcon } from "lucide-react"; | ||
| import { useTheme } from "next-themes"; | ||
| import { ClientOnly } from "@/components/blocks/client-only"; | ||
| import { Button } from "@/components/ui/button"; | ||
| import { Skeleton } from "@/components/ui/skeleton"; | ||
| import { cn } from "@/lib/utils"; | ||
| import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler"; | ||
| import { payAppThirdwebClient } from "../constants"; | ||
|
|
||
| export function PayIdPageHeader(props: { | ||
| projectName: string; | ||
| projectIcon: string | undefined; | ||
| }) { | ||
| return ( | ||
| <div className="border-b border-border/70"> | ||
| <header className="container flex max-w-7xl justify-between py-4"> | ||
| <div className="flex items-center gap-3"> | ||
| {props.projectIcon && ( | ||
| <Img | ||
| src={ | ||
| resolveSchemeWithErrorHandler({ | ||
| uri: props.projectIcon, | ||
| client: payAppThirdwebClient, | ||
| }) || "" | ||
| } | ||
| alt="" | ||
| className="rounded-full size-6 object-cover" | ||
| /> | ||
| )} | ||
|
|
||
| <h2 className="text-xl font-semibold tracking-tight"> | ||
| {props.projectName} | ||
| </h2> | ||
| </div> | ||
|
|
||
| <div className="flex items-center gap-3 lg:gap-5"> | ||
| <ToggleThemeButton /> | ||
| </div> | ||
| </header> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| function ToggleThemeButton(props: { className?: string }) { | ||
| const { setTheme, theme } = useTheme(); | ||
|
|
||
| return ( | ||
| <ClientOnly | ||
| ssr={<Skeleton className="size-[36px] rounded-full border bg-accent" />} | ||
| > | ||
| <Button | ||
| aria-label="Toggle theme" | ||
| className={cn( | ||
| "h-auto w-auto rounded-full p-2 text-muted-foreground hover:text-foreground", | ||
| props.className, | ||
| )} | ||
| onClick={() => { | ||
| setTheme(theme === "dark" ? "light" : "dark"); | ||
| }} | ||
| variant="ghost" | ||
| > | ||
| {theme === "light" ? ( | ||
| <SunIcon className="size-5 " /> | ||
| ) : ( | ||
| <MoonIcon className="size-5 " /> | ||
| )} | ||
| </Button> | ||
| </ClientOnly> | ||
| ); | ||
| } | ||
|
Comment on lines
+46
to
+72
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Code duplication - reuse existing ToggleThemeButton. This ToggleThemeButton implementation duplicates the existing component at Consider importing and reusing the existing component: +import { ToggleThemeButton } from "@/components/blocks/color-mode-toggle";
+
export function PayIdPageHeader(props: {
projectName: string;
projectIcon: string | undefined;
}) {
return (
<div className="border-b border-border/70">
<header className="container flex max-w-7xl justify-between py-4">
<div className="flex items-center gap-3">
{props.projectIcon && (
<Img
src={
resolveSchemeWithErrorHandler({
uri: props.projectIcon,
client: payAppThirdwebClient,
}) || ""
}
alt=""
className="rounded-full size-6 object-cover"
/>
)}
<h2 className="text-xl font-semibold tracking-tight">
{props.projectName}
</h2>
</div>
<div className="flex items-center gap-3 lg:gap-5">
- <ToggleThemeButton />
+ <ToggleThemeButton className="h-auto w-auto p-2 text-muted-foreground hover:text-foreground" />
</div>
</header>
</div>
);
}
-
-function ToggleThemeButton(props: { className?: string }) {
- const { setTheme, theme } = useTheme();
-
- return (
- <ClientOnly
- ssr={<Skeleton className="size-[36px] rounded-full border bg-accent" />}
- >
- <Button
- aria-label="Toggle theme"
- className={cn(
- "h-auto w-auto rounded-full p-2 text-muted-foreground hover:text-foreground",
- props.className,
- )}
- onClick={() => {
- setTheme(theme === "dark" ? "light" : "dark");
- }}
- variant="ghost"
- >
- {theme === "light" ? (
- <SunIcon className="size-5 " />
- ) : (
- <MoonIcon className="size-5 " />
- )}
- </Button>
- </ClientOnly>
- );
-}If styling differences are needed, extend the existing component to accept additional props rather than duplicating the implementation.
🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,21 +1,16 @@ | ||
| import { ShieldCheckIcon } from "lucide-react"; | ||
| import { cn } from "@workspace/ui/lib/utils"; | ||
| import type { Metadata } from "next"; | ||
| import { ThemeProvider } from "next-themes"; | ||
| import { Bridge, defineChain, toTokens } from "thirdweb"; | ||
| import { getChainMetadata } from "thirdweb/chains"; | ||
| import { shortenAddress } from "thirdweb/utils"; | ||
| import { Bridge } from "thirdweb"; | ||
| import { getPaymentLink } from "@/api/universal-bridge/links"; | ||
| import { Badge } from "@/components/ui/badge"; | ||
| import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs"; | ||
| import { | ||
| API_SERVER_SECRET, | ||
| DASHBOARD_THIRDWEB_SECRET_KEY, | ||
| } from "@/constants/server-envs"; | ||
| import { getConfiguredThirdwebClient } from "@/constants/thirdweb.server"; | ||
| import { resolveEns } from "@/lib/ens"; | ||
| import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler"; | ||
| import { PayPageWidget } from "../components/client/PayPageWidget.client"; | ||
| import { payAppThirdwebClient } from "../constants"; | ||
| import { PayIdPageHeader } from "./header"; | ||
|
|
||
| const title = "thirdweb Pay"; | ||
| const description = "Fast, secure, and simple payments."; | ||
|
|
@@ -54,171 +49,44 @@ export default async function PayPage({ | |
| tokenAddress: paymentLink.destinationToken.address, | ||
| }); | ||
|
|
||
| const chainPromise = getChainMetadata( | ||
| // eslint-disable-next-line no-restricted-syntax | ||
| defineChain(Number(paymentLink.destinationToken.chainId)), | ||
| ); | ||
|
|
||
| const recipientPromise = resolveEns( | ||
| paymentLink.receiver, | ||
| getConfiguredThirdwebClient({ | ||
| secretKey: DASHBOARD_THIRDWEB_SECRET_KEY, | ||
| teamId: undefined, | ||
| }), | ||
| ); | ||
|
|
||
| const [tokens, projectMetadata, chain, recipientEnsOrAddress] = | ||
| await Promise.all([ | ||
| tokensPromise, | ||
| projectMetadataPromise, | ||
| chainPromise, | ||
| recipientPromise, | ||
| ]); | ||
| const [tokens, projectMetadata] = await Promise.all([ | ||
| tokensPromise, | ||
| projectMetadataPromise, | ||
| ]); | ||
|
|
||
| const token = tokens[0]; | ||
| if (!token) { | ||
| throw new Error("Token not found"); | ||
| } | ||
|
|
||
| return ( | ||
| <div className="relative flex h-dvh w-full items-center justify-center"> | ||
| <div className="relative flex flex-col min-h-dvh w-full"> | ||
| <ThemeProvider | ||
| forcedTheme={theme === "light" ? "light" : "dark"} | ||
| defaultTheme={theme === "light" ? "light" : "dark"} | ||
| attribute="class" | ||
| disableTransitionOnChange | ||
| enableSystem={false} | ||
| > | ||
| <div className="flex z-10 flex-col lg:flex-row h-full w-full"> | ||
| <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"> | ||
| <div> | ||
| <div className="flex flex-row items-center justify-start gap-4"> | ||
| {projectMetadata.image && ( | ||
| <img | ||
| src={ | ||
| resolveSchemeWithErrorHandler({ | ||
| uri: projectMetadata.image, | ||
| client: payAppThirdwebClient, | ||
| }) || "" | ||
| } | ||
| alt={projectMetadata.name} | ||
| width={25} | ||
| height={25} | ||
| className="rounded-full overflow-hidden" | ||
| /> | ||
| )} | ||
| <h2 className="text-xl font-bold">{projectMetadata.name}</h2> | ||
| </div> | ||
| {projectMetadata.description && ( | ||
| <p className="mt-2 text-sm text-muted-foreground"> | ||
| {projectMetadata.description} | ||
| </p> | ||
| )} | ||
| </div> | ||
|
|
||
| <div className="hidden lg:block my-4 w-full"> | ||
| {paymentLink.amount && ( | ||
| <div className="flex flex-col gap-1 w-full my-4"> | ||
| <span className="text-muted-foreground text-xs">Details</span> | ||
| <div className="font-medium flex-row flex justify-between items-center w-full"> | ||
| <div className="flex flex-row items-center gap-2"> | ||
| {token.iconUri && ( | ||
| <img | ||
| src={resolveSchemeWithErrorHandler({ | ||
| uri: token.iconUri, | ||
| client: getConfiguredThirdwebClient({ | ||
| secretKey: DASHBOARD_THIRDWEB_SECRET_KEY, | ||
| teamId: undefined, | ||
| }), | ||
| })} | ||
| alt={token.name} | ||
| width={25} | ||
| height={25} | ||
| className="size-5 rounded-full overflow-hidden" | ||
| /> | ||
| )} | ||
| {toTokens(BigInt(paymentLink.amount), token.decimals)}{" "} | ||
| {token.symbol} | ||
| </div> | ||
| {token.prices.USD && ( | ||
| <span> | ||
| $ | ||
| {( | ||
| Number(token.prices.USD) * | ||
| Number( | ||
| toTokens( | ||
| BigInt(paymentLink.amount), | ||
| token.decimals, | ||
| ), | ||
| ) | ||
| ).toFixed(2)} | ||
| </span> | ||
| )} | ||
| </div> | ||
| </div> | ||
| )} | ||
| {chain && ( | ||
| <div className="flex flex-col gap-1 w-full my-4"> | ||
| <span className="text-muted-foreground text-xs">Network</span> | ||
| <div className="font-medium flex-row flex justify-between items-center w-full"> | ||
| <div className="flex flex-row items-center gap-2"> | ||
| {chain.icon?.url && ( | ||
| <img | ||
| src={resolveSchemeWithErrorHandler({ | ||
| uri: chain.icon.url, | ||
| client: getConfiguredThirdwebClient({ | ||
| secretKey: DASHBOARD_THIRDWEB_SECRET_KEY, | ||
| teamId: undefined, | ||
| }), | ||
| })} | ||
| alt={chain.name} | ||
| width={chain.icon.width} | ||
| height={chain.icon.height} | ||
| className="size-5 rounded-full overflow-hidden" | ||
| /> | ||
| )} | ||
| {chain.name} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| )} | ||
| {recipientEnsOrAddress.ensName || | ||
| (recipientEnsOrAddress.address && ( | ||
| <div className="flex flex-col gap-1 w-full my-4"> | ||
| <span className="text-muted-foreground text-xs"> | ||
| Seller | ||
| </span> | ||
| <div className="font-medium flex-row flex justify-between items-center w-full"> | ||
| {recipientEnsOrAddress.ensName ?? | ||
| shortenAddress(recipientEnsOrAddress.address)} | ||
| </div> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| <PayIdPageHeader | ||
| projectIcon={projectMetadata.image || undefined} | ||
| projectName={projectMetadata.name} | ||
| /> | ||
|
|
||
| <div className="mt-auto hidden lg:block"> | ||
| <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"> | ||
| <ShieldCheckIcon className="size-3" /> | ||
| Secured by thirdweb | ||
| </Badge> | ||
| </div> | ||
| </header> | ||
| <main className="flex justify-center py-12 w-full items-center grow"> | ||
| <PayPageWidget | ||
| amount={ | ||
| paymentLink.amount ? BigInt(paymentLink.amount) : undefined | ||
| } | ||
| chainId={Number(paymentLink.destinationToken.chainId)} | ||
| clientId={undefined} // Payment links don't need to use the same client ID to be executed | ||
| image={paymentLink.imageUrl} | ||
| name={paymentLink.title} | ||
| paymentLinkId={id} | ||
| purchaseData={paymentLink.purchaseData} | ||
| recipientAddress={paymentLink.receiver} | ||
| redirectUri={redirectUri} | ||
| token={token} | ||
| /> | ||
| </main> | ||
| </div> | ||
| <main className="flex justify-center py-12 w-full items-center grow overflow-hidden relative"> | ||
| <DotsBackgroundPattern /> | ||
| <PayPageWidget | ||
| amount={paymentLink.amount ? BigInt(paymentLink.amount) : undefined} | ||
| chainId={Number(paymentLink.destinationToken.chainId)} | ||
| clientId={undefined} // Payment links don't need to use the same client ID to be executed | ||
| image={paymentLink.imageUrl} | ||
| name={paymentLink.title} | ||
| paymentLinkId={id} | ||
| purchaseData={paymentLink.purchaseData} | ||
| recipientAddress={paymentLink.receiver} | ||
| redirectUri={redirectUri} | ||
| token={token} | ||
| /> | ||
| </main> | ||
| </ThemeProvider> | ||
| </div> | ||
| ); | ||
|
|
@@ -237,7 +105,24 @@ async function getProjectMetadata(clientId: string) { | |
| } | ||
|
|
||
| const { data } = (await response.json()) as { | ||
| data: { name: string; image: string | null; description: string | null }; | ||
| data: { name: string; image: string | null }; | ||
| }; | ||
| return data; | ||
| } | ||
|
|
||
| function DotsBackgroundPattern(props: { className?: string }) { | ||
| return ( | ||
| <div | ||
| className={cn( | ||
| "pointer-events-none absolute -inset-x-36 -inset-y-24 text-foreground/20 dark:text-muted-foreground/15 hidden lg:block", | ||
| props.className, | ||
| )} | ||
| style={{ | ||
| backgroundImage: "radial-gradient(currentColor 1px, transparent 1px)", | ||
| backgroundSize: "24px 24px", | ||
| maskImage: | ||
| "radial-gradient(ellipse 100% 100% at 50% 50%, black 30%, transparent 50%)", | ||
| }} | ||
| /> | ||
| ); | ||
| } | ||
|
Comment on lines
+113
to
+128
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Code duplication - import existing DotsBackgroundPattern. This Import and reuse the existing component: +import { DotsBackgroundPattern } from "@/components/ui/background-patterns";
+
// ...
<main className="flex justify-center py-12 w-full items-center grow overflow-hidden relative">
- <DotsBackgroundPattern />
+ <DotsBackgroundPattern className="pointer-events-none absolute -inset-x-36 -inset-y-24 text-foreground/20 dark:text-muted-foreground/15 hidden lg:block" />
<PayPageWidget
amount={paymentLink.amount ? BigInt(paymentLink.amount) : undefined}
chainId={Number(paymentLink.destinationToken.chainId)}
clientId={undefined}
image={paymentLink.imageUrl}
name={paymentLink.title}
paymentLinkId={id}
purchaseData={paymentLink.purchaseData}
recipientAddress={paymentLink.receiver}
redirectUri={redirectUri}
token={token}
/>
</main>
</ThemeProvider>
</div>
);
}
async function getProjectMetadata(clientId: string) {
// ...
}
-
-function DotsBackgroundPattern(props: { className?: string }) {
- return (
- <div
- className={cn(
- "pointer-events-none absolute -inset-x-36 -inset-y-24 text-foreground/20 dark:text-muted-foreground/15 hidden lg:block",
- props.className,
- )}
- style={{
- backgroundImage: "radial-gradient(currentColor 1px, transparent 1px)",
- backgroundSize: "24px 24px",
- maskImage:
- "radial-gradient(ellipse 100% 100% at 50% 50%, black 30%, transparent 50%)",
- }}
- />
- );
-}
🤖 Prompt for AI Agents |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Add explicit return types.
Per coding guidelines, all function declarations should have explicit return types.
As per coding guidelines
Apply this diff:
Also applies to: 46-46
🤖 Prompt for AI Agents