Skip to content

Commit 2f627e1

Browse files
authored
[Dashboard] Feature: New payments page ui (#7848)
1 parent 6b80f75 commit 2f627e1

File tree

6 files changed

+200
-51
lines changed

6 files changed

+200
-51
lines changed

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/components/AdvancedSection.client.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,9 @@ export function AdvancedSection({
5454
features={["Swap any token", "Cross-chain swaps"]}
5555
description="Swap tokens cross-chain with dedicated swapping endpoints."
5656
link={{
57-
href: `/team/${teamSlug}/${projectSlug}/payments/swap`,
57+
href: `https://portal.thirdweb.com/payments/swap`,
5858
label: "Setup Swaps",
59+
target: "_blank",
5960
}}
6061
/>
6162
</div>
Lines changed: 193 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
1+
import { ShieldCheckIcon } from "lucide-react";
12
import type { Metadata } from "next";
2-
import { defineChain, getContract } from "thirdweb";
3-
import { getCurrencyMetadata } from "thirdweb/extensions/erc20";
4-
import { checksumAddress } from "thirdweb/utils";
3+
import Image from "next/image";
4+
import { Bridge, defineChain, toTokens } from "thirdweb";
5+
import { getChainMetadata } from "thirdweb/chains";
6+
import { shortenAddress } from "thirdweb/utils";
57
import { getPaymentLink } from "@/api/universal-bridge/links";
6-
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
8+
import { Badge } from "@/components/ui/badge";
9+
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
10+
import {
11+
API_SERVER_SECRET,
12+
DASHBOARD_THIRDWEB_SECRET_KEY,
13+
} from "@/constants/server-envs";
14+
import { getConfiguredThirdwebClient } from "@/constants/thirdweb.server";
15+
import { resolveEns } from "@/lib/ens";
16+
import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler";
717
import { PayPageWidget } from "../components/client/PayPageWidget.client";
18+
import { payAppThirdwebClient } from "../constants";
819

920
const title = "thirdweb Pay";
1021
const description = "Fast, secure, and simple payments.";
@@ -32,40 +43,186 @@ export default async function PayPage({
3243
paymentId: id,
3344
});
3445

35-
const tokenContract = getContract({
36-
address: paymentLink.destinationToken.address, // for this RPC call, use the dashboard client
37-
// eslint-disable-next-line no-restricted-syntax
38-
chain: defineChain(Number(paymentLink.destinationToken.chainId)),
39-
client: getClientThirdwebClient(undefined),
40-
});
41-
const {
42-
symbol,
43-
decimals,
44-
name: tokenName,
45-
} = await getCurrencyMetadata({
46-
contract: tokenContract,
47-
});
48-
const token = {
49-
address: checksumAddress(paymentLink.destinationToken.address),
46+
const projectMetadataPromise = getProjectMetadata(paymentLink.clientId);
47+
48+
const tokensPromise = Bridge.tokens({
49+
client: getConfiguredThirdwebClient({
50+
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
51+
teamId: undefined,
52+
}),
5053
chainId: Number(paymentLink.destinationToken.chainId),
51-
decimals,
52-
name: tokenName,
53-
symbol,
54-
};
54+
tokenAddress: paymentLink.destinationToken.address,
55+
});
56+
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+
]);
77+
78+
const token = tokens[0];
79+
if (!token) {
80+
throw new Error("Token not found");
81+
}
5582

5683
return (
57-
<PayPageWidget
58-
amount={paymentLink.amount ? BigInt(paymentLink.amount) : undefined}
59-
chainId={Number(paymentLink.destinationToken.chainId)}
60-
clientId={undefined} // Payment links don't need to use the same client ID to be executed
61-
image={paymentLink.imageUrl}
62-
name={paymentLink.title}
63-
paymentLinkId={id}
64-
purchaseData={paymentLink.purchaseData}
65-
recipientAddress={paymentLink.receiver}
66-
redirectUri={redirectUri}
67-
theme={theme}
68-
token={token}
69-
/>
84+
<div className="flex z-10 flex-col lg:flex-row h-full w-full">
85+
<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">
86+
<div>
87+
<div className="flex flex-row items-center justify-start gap-4">
88+
{projectMetadata.image && (
89+
<Image
90+
src={
91+
resolveSchemeWithErrorHandler({
92+
uri: projectMetadata.image,
93+
client: payAppThirdwebClient,
94+
}) || ""
95+
}
96+
alt={projectMetadata.name}
97+
width={25}
98+
height={25}
99+
className="rounded-full overflow-hidden"
100+
/>
101+
)}
102+
<h2 className="text-xl font-bold">{projectMetadata.name}</h2>
103+
</div>
104+
{projectMetadata.description && (
105+
<p className="mt-2 text-sm text-muted-foreground">
106+
{projectMetadata.description}
107+
</p>
108+
)}
109+
</div>
110+
111+
<div className="hidden lg:block my-4 w-full">
112+
{paymentLink.amount && (
113+
<div className="flex flex-col gap-1 w-full my-4">
114+
<span className="text-muted-foreground text-xs">Details</span>
115+
<div className="font-medium flex-row flex justify-between items-center w-full">
116+
<div className="flex flex-row items-center gap-2">
117+
{token.iconUri && (
118+
<img
119+
src={resolveSchemeWithErrorHandler({
120+
uri: token.iconUri,
121+
client: getConfiguredThirdwebClient({
122+
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
123+
teamId: undefined,
124+
}),
125+
})}
126+
alt={token.name}
127+
width={25}
128+
height={25}
129+
className="size-5 rounded-full overflow-hidden"
130+
/>
131+
)}
132+
{toTokens(BigInt(paymentLink.amount), token.decimals)}{" "}
133+
{token.symbol}
134+
</div>
135+
{token.prices.USD && (
136+
<span>
137+
$
138+
{(
139+
Number(token.prices.USD) *
140+
Number(
141+
toTokens(BigInt(paymentLink.amount), token.decimals),
142+
)
143+
).toFixed(2)}
144+
</span>
145+
)}
146+
</div>
147+
</div>
148+
)}
149+
{chain && (
150+
<div className="flex flex-col gap-1 w-full my-4">
151+
<span className="text-muted-foreground text-xs">Network</span>
152+
<div className="font-medium flex-row flex justify-between items-center w-full">
153+
<div className="flex flex-row items-center gap-2">
154+
{chain.icon?.url && (
155+
<img
156+
src={resolveSchemeWithErrorHandler({
157+
uri: chain.icon.url,
158+
client: getConfiguredThirdwebClient({
159+
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
160+
teamId: undefined,
161+
}),
162+
})}
163+
alt={chain.name}
164+
width={chain.icon.width}
165+
height={chain.icon.height}
166+
className="size-5 rounded-full overflow-hidden"
167+
/>
168+
)}
169+
{chain.name}
170+
</div>
171+
</div>
172+
</div>
173+
)}
174+
{recipientEnsOrAddress.ensName ||
175+
(recipientEnsOrAddress.address && (
176+
<div className="flex flex-col gap-1 w-full my-4">
177+
<span className="text-muted-foreground text-xs">Seller</span>
178+
<div className="font-medium flex-row flex justify-between items-center w-full">
179+
{recipientEnsOrAddress.ensName ??
180+
shortenAddress(recipientEnsOrAddress.address)}
181+
</div>
182+
</div>
183+
))}
184+
</div>
185+
186+
<div className="mt-auto hidden lg:block">
187+
<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">
188+
<ShieldCheckIcon className="size-3" />
189+
Secured by thirdweb
190+
</Badge>
191+
</div>
192+
</header>
193+
<main className="flex justify-center p-12 w-full items-center">
194+
<PayPageWidget
195+
amount={paymentLink.amount ? BigInt(paymentLink.amount) : undefined}
196+
chainId={Number(paymentLink.destinationToken.chainId)}
197+
clientId={undefined} // Payment links don't need to use the same client ID to be executed
198+
image={paymentLink.imageUrl}
199+
name={paymentLink.title}
200+
paymentLinkId={id}
201+
purchaseData={paymentLink.purchaseData}
202+
recipientAddress={paymentLink.receiver}
203+
redirectUri={redirectUri}
204+
theme={theme}
205+
token={token}
206+
/>
207+
</main>
208+
</div>
70209
);
71210
}
211+
212+
async function getProjectMetadata(clientId: string) {
213+
const url = new URL(`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v2/keys/lookup`);
214+
url.searchParams.append("clientId", clientId);
215+
const response = await fetch(url.toString(), {
216+
headers: {
217+
"x-service-api-key": API_SERVER_SECRET,
218+
},
219+
});
220+
if (!response.ok) {
221+
throw new Error("Failed to fetch project");
222+
}
223+
224+
const { data } = (await response.json()) as {
225+
data: { name: string; image: string | null; description: string | null };
226+
};
227+
return data;
228+
}

apps/dashboard/src/app/pay/layout.tsx

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,8 @@ export default async function PayLayout({
3030
disableTransitionOnChange
3131
enableSystem={false}
3232
>
33-
<div className="relative mx-auto flex h-full w-full flex-col items-center justify-center overflow-x-hidden overflow-y-scroll py-10">
34-
<main className="container z-10 flex justify-center">
35-
{children}
36-
</main>
37-
38-
{/* eslint-disable-next-line @next/next/no-img-element */}
39-
<img
40-
alt=""
41-
className="-bottom-12 -right-12 pointer-events-none absolute lg:right-0 lg:bottom-0"
42-
src="/assets/login/background.svg"
43-
/>
33+
<div className="relative mx-auto flex h-full w-full items-center justify-center overflow-x-hidden overflow-y-scroll">
34+
{children}
4435
</div>
4536
</ThemeProvider>
4637
</PayProviders>

apps/dashboard/src/app/pay/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { PayPageWidget } from "./components/client/PayPageWidget.client";
77
import type { PayParams } from "./components/types";
88
import { payAppThirdwebClient } from "./constants";
99

10-
const title = "thirdweb Pay";
10+
const title = "thirdweb Payments";
1111
const description = "Fast, secure, and simple payments.";
1212

1313
export const metadata: Metadata = {

apps/portal/src/app/Header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ export function Header() {
341341

342342
{/* Mobile menu */}
343343
{showBurgerMenu && (
344-
<div className="fixed inset-0 top-sticky-top-height z-[50] overflow-auto bg-card p-6 xl:hidden">
344+
<div className="fixed inset-0 top-sticky-top-height z-50 overflow-auto bg-card p-6 xl:hidden">
345345
<div className="flex flex-col gap-6">
346346
<div className="flex flex-col gap-4">
347347
<h3 className="font-semibold text-lg">Products</h3>

apps/portal/src/components/code/RenderCode.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export function RenderCode(props: {
3030
/>
3131
</ScrollShadow>
3232
<CopyButton
33-
className="absolute top-4 right-4 z-[10] border bg-background p-2 opacity-0 transition-opacity duration-300 group-hover:opacity-100"
33+
className="absolute top-4 right-4 z-10 border bg-background p-2 opacity-0 transition-opacity duration-300 group-hover:opacity-100"
3434
iconClassName="size-3"
3535
text={props.code}
3636
/>

0 commit comments

Comments
 (0)