Skip to content

Commit 1607229

Browse files
authored
[Dashboard] Feature: New Payments Overview UI (#7702)
1 parent 75bf091 commit 1607229

File tree

8 files changed

+344
-114
lines changed

8 files changed

+344
-114
lines changed

apps/dashboard/src/app/(app)/(dashboard)/(bridge)/routes/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export default async function RoutesPage(props: {
7474
</div>
7575
<a
7676
className="inline-flex items-center gap-2 rounded-md bg-green-600 px-4 py-2 font-medium text-sm text-white transition-all hover:bg-green-600/90 hover:shadow-sm"
77-
href="https://portal.thirdweb.com/pay"
77+
href="https://portal.thirdweb.com/payments"
7878
rel="noopener noreferrer"
7979
target="_blank"
8080
>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"use client";
2+
3+
import { CodeIcon, WebhookIcon } from "lucide-react";
4+
import { FeatureCard } from "./FeatureCard.client";
5+
6+
export function AdvancedSection({
7+
teamSlug,
8+
projectSlug,
9+
}: {
10+
teamSlug: string;
11+
projectSlug: string;
12+
}) {
13+
return (
14+
<section>
15+
<div className="mb-4">
16+
<h2 className="font-semibold text-xl tracking-tight">Going Further</h2>
17+
<p className="text-muted-foreground text-sm">
18+
Advanced features to drive revenue in your app.
19+
</p>
20+
</div>
21+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
22+
<FeatureCard
23+
title="Customized Experience"
24+
description="Build your own branded experiences with the HTTP API or TypeScript SDK."
25+
icon={CodeIcon}
26+
setupTime={10}
27+
color="green"
28+
id="http_api"
29+
features={["Route discovery", "Real-time token prices"]}
30+
link={{
31+
href: "https://payments.thirdweb.com/reference",
32+
label: "Documentation",
33+
}}
34+
/>
35+
<FeatureCard
36+
title="Webhooks"
37+
description="Create Webhooks to get notified on each purchase or transaction."
38+
icon={WebhookIcon}
39+
setupTime={5}
40+
color="green"
41+
id="webhooks"
42+
features={["Instant events", "Transaction verification"]}
43+
link={{
44+
href: `/team/${teamSlug}/${projectSlug}/payments/webhooks`,
45+
label: "Setup Webhooks",
46+
}}
47+
/>
48+
</div>
49+
</section>
50+
);
51+
}

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

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,80 @@
11
"use client";
22

3+
import { ArrowRightIcon, ClockIcon } from "lucide-react";
34
import Link from "next/link";
45
import { reportPaymentCardClick } from "@/analytics/report";
6+
import { Badge } from "@/components/ui/badge";
57
import { Button } from "@/components/ui/button";
68
import { Card } from "@/components/ui/card";
79

810
export function FeatureCard(props: {
911
title: string;
1012
description: string;
11-
icon: React.ReactNode;
13+
badge?: { label: string; variant?: "outline" | "success" | "default" };
1214
id: string;
15+
icon: React.FC<{ className?: string }>;
16+
color?: "green" | "violet";
17+
setupTime?: number;
18+
features?: string[];
1319
link: { href: string; label: string; target?: string };
1420
}) {
1521
return (
16-
<Card className="p-4 flex flex-col items-start gap-4">
17-
<div className="text-muted-foreground rounded-full border bg-background size-12 flex items-center justify-center">
18-
{props.icon}
19-
</div>
20-
<div className="flex flex-col gap-0.5">
21-
<h3 className="font-semibold">{props.title}</h3>
22-
<p className="text-muted-foreground text-sm">{props.description}</p>
22+
<Card className="p-6 flex flex-col items-start gap-6 text-muted-foreground w-full">
23+
<div className="flex-1 flex flex-col items-start gap-6 w-full">
24+
<div className="relative w-full">
25+
<div
26+
className={`${props.color === "green" ? "bg-green-700/25" : "bg-violet-700/25"} rounded-lg size-9 flex items-center justify-center`}
27+
>
28+
<props.icon
29+
className={`size-5 ${props.color === "green" ? "text-green-500" : "text-violet-500"}`}
30+
/>
31+
</div>
32+
{props.badge && (
33+
<Badge
34+
variant={props.badge.variant}
35+
className="absolute top-0 right-0"
36+
>
37+
{props.badge.label}
38+
</Badge>
39+
)}
40+
</div>
41+
<div className="flex flex-col gap-1">
42+
<h3 className="font-semibold text-foreground">{props.title}</h3>
43+
<p className="text-muted-foreground text-sm font-medium">
44+
{props.description}
45+
</p>
46+
</div>
47+
{(props.setupTime || props.features) && (
48+
<div className="flex justify-center gap-4 text-xs flex-col">
49+
{props.setupTime && (
50+
<p className="flex gap-2 items-center">
51+
<ClockIcon className="size-3" />
52+
{props.setupTime} minute{props.setupTime > 1 && "s"} setup time
53+
</p>
54+
)}
55+
{props.features && (
56+
<ul>
57+
{props.features.map((feature) => (
58+
<li key={feature} className="flex gap-2 items-center mb-1.5">
59+
<span className="bg-violet-500 size-1.5 rounded-full" />
60+
{feature}
61+
</li>
62+
))}
63+
</ul>
64+
)}
65+
</div>
66+
)}
2367
</div>
2468
<Button
2569
onClick={() => reportPaymentCardClick({ id: props.id })}
2670
size="sm"
27-
variant="default"
28-
className="h-8"
71+
variant="outline"
72+
className="w-full gap-2 group text-foreground"
2973
asChild
3074
>
3175
<Link href={props.link.href} target={props.link.target}>
3276
{props.link.label}
77+
<ArrowRightIcon className="size-4 group-hover:translate-x-1 transition-all" />
3378
</Link>
3479
</Button>
3580
</Card>

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export function PaymentHistory(props: {
118118
);
119119
}
120120

121-
function TableRow(props: { purchase: Payment; client: ThirdwebClient }) {
121+
export function TableRow(props: { purchase: Payment; client: ThirdwebClient }) {
122122
const { purchase } = props;
123123
const originAmount = toTokens(
124124
purchase.originAmount,
@@ -197,7 +197,7 @@ function TableRow(props: { purchase: Payment; client: ThirdwebClient }) {
197197
);
198198
}
199199

200-
function SkeletonTableRow() {
200+
export function SkeletonTableRow() {
201201
return (
202202
<tr className="border-border border-b">
203203
<TableData>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
"use client";
2+
3+
import { BadgeDollarSignIcon, CodeIcon, LinkIcon } from "lucide-react";
4+
import { FeatureCard } from "./FeatureCard.client";
5+
6+
export function QuickStartSection({
7+
teamSlug,
8+
projectSlug,
9+
}: {
10+
teamSlug: string;
11+
projectSlug: string;
12+
}) {
13+
return (
14+
<section>
15+
<div className="mb-4">
16+
<h2 className="font-semibold text-xl tracking-tight">Quick Start</h2>
17+
<p className="text-muted-foreground text-sm">
18+
Choose how to integrate payments into your project.
19+
</p>
20+
</div>
21+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
22+
<FeatureCard
23+
title="Create Payment Links"
24+
description="Create shareable URLs to receive any token in seconds."
25+
icon={LinkIcon}
26+
id="payment_links"
27+
color="violet"
28+
badge={{
29+
label: "New",
30+
variant: "success",
31+
}}
32+
setupTime={1}
33+
features={[
34+
"No coding required",
35+
"Get paid in any token",
36+
"Send instantly",
37+
]}
38+
link={{
39+
href: `/pay`,
40+
label: "Create Link",
41+
}}
42+
/>
43+
<FeatureCard
44+
title="Earn Fees"
45+
description="Setup fees to earn any time a user swaps or bridges funds."
46+
icon={BadgeDollarSignIcon}
47+
id="fees"
48+
setupTime={1}
49+
color="violet"
50+
badge={{
51+
label: "Recommended",
52+
variant: "outline",
53+
}}
54+
features={[
55+
"Fees on every purchase",
56+
"Custom percentage",
57+
"Directly to your wallet",
58+
]}
59+
link={{
60+
href: `/team/${teamSlug}/${projectSlug}/payments/settings`,
61+
label: "Configure Fees",
62+
}}
63+
/>
64+
<FeatureCard
65+
title="UI Components"
66+
description="Instantly add payments to your React app with prebuild components."
67+
icon={CodeIcon}
68+
id="components"
69+
color="violet"
70+
setupTime={2}
71+
badge={{
72+
label: "Popular",
73+
variant: "outline",
74+
}}
75+
features={[
76+
"Drop-in components",
77+
"Supports custom user data",
78+
"Transactions, products, and direct payments",
79+
]}
80+
link={{
81+
href: "https://portal.thirdweb.com/payments/products",
82+
label: "Get Started",
83+
target: "_blank",
84+
}}
85+
/>
86+
</div>
87+
</section>
88+
);
89+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
"use client";
2+
3+
import { useQuery } from "@tanstack/react-query";
4+
import { ArrowRightIcon, CreditCardIcon } from "lucide-react";
5+
import Link from "next/link";
6+
import { useMemo } from "react";
7+
import type { ThirdwebClient } from "thirdweb";
8+
import {
9+
getPayments,
10+
type PaymentsResponse,
11+
} from "@/api/universal-bridge/developer";
12+
import { Button } from "@/components/ui/button";
13+
import { Card } from "@/components/ui/card";
14+
import { TableHeading, TableHeadingRow } from "./common";
15+
import { SkeletonTableRow, TableRow } from "./PaymentHistory";
16+
17+
export function RecentPaymentsSection(props: {
18+
client: ThirdwebClient;
19+
projectClientId: string;
20+
teamId: string;
21+
}) {
22+
const { data: payPurchaseData, isLoading } = useQuery<
23+
PaymentsResponse,
24+
Error
25+
>({
26+
queryFn: async () => {
27+
const res = await getPayments({
28+
clientId: props.projectClientId,
29+
limit: 10,
30+
offset: 0,
31+
teamId: props.teamId,
32+
});
33+
return res;
34+
},
35+
queryKey: ["recent-payments", props.projectClientId],
36+
refetchInterval: 10_000,
37+
});
38+
const isEmpty = useMemo(
39+
() => !payPurchaseData?.data.length,
40+
[payPurchaseData],
41+
);
42+
return (
43+
<section>
44+
<div className="mb-4">
45+
<h2 className="font-semibold text-xl tracking-tight">
46+
Recent Payments
47+
</h2>
48+
<p className="text-muted-foreground text-sm">
49+
The latest payments from your project.
50+
</p>
51+
</div>
52+
{!isEmpty || isLoading ? (
53+
<Card className="overflow-hidden">
54+
<table className="w-full selection:bg-inverted selection:text-inverted-foreground ">
55+
<thead>
56+
<TableHeadingRow>
57+
<TableHeading> Sent </TableHeading>
58+
<TableHeading> Received </TableHeading>
59+
<TableHeading>Type</TableHeading>
60+
<TableHeading>Status</TableHeading>
61+
<TableHeading>Recipient</TableHeading>
62+
<TableHeading>Date</TableHeading>
63+
</TableHeadingRow>
64+
</thead>
65+
<tbody>
66+
{payPurchaseData && !isLoading
67+
? payPurchaseData.data.map((purchase) => {
68+
return (
69+
<TableRow
70+
client={props.client}
71+
key={purchase.id}
72+
purchase={purchase}
73+
/>
74+
);
75+
})
76+
: new Array(10).fill(0).map((_, i) => (
77+
// biome-ignore lint/suspicious/noArrayIndexKey: ok
78+
<SkeletonTableRow key={i} />
79+
))}
80+
</tbody>
81+
</table>
82+
</Card>
83+
) : (
84+
<Card className="flex flex-col p-16 gap-8 items-center justify-center">
85+
<div className="bg-violet-800/25 text-muted-foreground rounded-full size-16 flex items-center justify-center">
86+
<CreditCardIcon className="size-8 text-violet-500" />
87+
</div>
88+
<div className="flex flex-col gap-1 items-center text-center">
89+
<h3 className="text-foreground font-medium text-xl">
90+
No payments yet
91+
</h3>
92+
<p className="text-muted-foreground text-sm max-w-md">
93+
Start accepting crypto payments with payment links, prebuilt
94+
components, or custom branded experiences.
95+
</p>
96+
</div>
97+
<div className="flex gap-4">
98+
<Button
99+
variant="default"
100+
size="sm"
101+
className="flex items-center gap-2"
102+
asChild
103+
>
104+
<Link href="/pay" target="_blank">
105+
Create Payment Link
106+
<ArrowRightIcon className="size-4" />
107+
</Link>
108+
</Button>
109+
<Button asChild variant="outline" size="sm">
110+
<Link href="https://portal.thirdweb.com/payments" target="_blank">
111+
View Documentation
112+
</Link>
113+
</Button>
114+
</div>
115+
</Card>
116+
)}
117+
</section>
118+
);
119+
}

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@ export default async function Layout(props: {
2828
Payments
2929
</h1>
3030
<p className="max-w-3xl text-muted-foreground text-sm leading-relaxed">
31-
Payments allows your users to bridge, swap, and purchase
32-
cryptocurrencies and execute transactions with any fiat options or
33-
tokens via cross-chain routing.{" "}
31+
Send and accept payments with cross-chain token routing.{" "}
3432
<UnderlineLink
3533
href="https://portal.thirdweb.com/payments"
3634
rel="noopener noreferrer"

0 commit comments

Comments
 (0)