Skip to content

Commit 965d8c7

Browse files
committed
Dashboard, Portal, Playground: Add Bridge Product
1 parent 17d40af commit 965d8c7

File tree

15 files changed

+332
-136
lines changed

15 files changed

+332
-136
lines changed

apps/dashboard/src/@/components/blocks/project-page/project-page-header.tsx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,15 @@ type Action =
2323
component: React.ReactNode;
2424
};
2525

26-
function Action(props: { action: Action; variant?: "default" | "secondary" }) {
26+
function Action(props: { action: Action; variant?: "default" | "outline" }) {
2727
const action = props.action;
2828

2929
return "component" in action ? (
3030
action.component
3131
) : (
3232
<Button
3333
asChild
34-
className={cn(
35-
"rounded-full",
36-
props.variant === "secondary" && "border border-border",
37-
)}
34+
className={cn("rounded-full")}
3835
size="sm"
3936
variant={props.variant}
4037
>
@@ -60,7 +57,7 @@ export type ProjectPageHeaderProps = {
6057
icon: React.FC<{ className?: string }>;
6158
isProjectIcon?: boolean;
6259
actions: {
63-
primary: Action;
60+
primary?: Action;
6461
secondary?: Action;
6562
} | null;
6663

@@ -114,7 +111,7 @@ export function ProjectPageHeader(props: ProjectPageHeaderProps) {
114111
{props.actions && (
115112
<div className="hidden lg:flex items-center gap-3">
116113
{props.actions.secondary && (
117-
<Action action={props.actions.secondary} variant="secondary" />
114+
<Action action={props.actions.secondary} variant="outline" />
118115
)}
119116

120117
{props.actions.primary && (
@@ -132,7 +129,7 @@ export function ProjectPageHeader(props: ProjectPageHeaderProps) {
132129
{props.title}
133130
</h2>
134131
{/* description */}
135-
<p className="text-sm text-muted-foreground line-clamp-3 md:line-clamp-2">
132+
<p className="text-sm text-muted-foreground text-pretty">
136133
{props.description}
137134
</p>
138135
</div>
@@ -145,7 +142,7 @@ export function ProjectPageHeader(props: ProjectPageHeaderProps) {
145142
)}
146143

147144
{props.actions?.secondary && (
148-
<Action action={props.actions.secondary} variant="secondary" />
145+
<Action action={props.actions.secondary} variant="outline" />
149146
)}
150147
</div>
151148
)}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { BringToFrontIcon as BridgeIcon } from "lucide-react";
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"use client";
2+
3+
import { BadgeDollarSignIcon, CodeIcon } from "lucide-react";
4+
import { FeatureCard } from "../payments/components/FeatureCard.client";
5+
6+
export function QuickStartSection(props: {
7+
teamSlug: string;
8+
projectSlug: string;
9+
clientId: string;
10+
teamId: string;
11+
}) {
12+
return (
13+
<section>
14+
<div className="mb-4">
15+
<h2 className="font-semibold text-xl tracking-tight">Quick Start</h2>
16+
<p className="text-muted-foreground text-sm">
17+
Choose how to integrate bridge into your project.
18+
</p>
19+
</div>
20+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
21+
<FeatureCard
22+
title="Earn Fees"
23+
description="Setup fees to earn any time a user swaps or bridges funds."
24+
icon={BadgeDollarSignIcon}
25+
id="fees"
26+
setupTime={1}
27+
features={[
28+
"Fees on every purchase",
29+
"Custom percentage",
30+
"Directly to your wallet",
31+
]}
32+
link={{
33+
href: `/team/${props.teamSlug}/${props.projectSlug}/settings/payments`,
34+
label: "Configure Fees",
35+
}}
36+
/>
37+
<FeatureCard
38+
title="UI Components"
39+
description="Instantly add bridging to your React app with prebuilt components."
40+
icon={CodeIcon}
41+
id="components"
42+
setupTime={2}
43+
features={[
44+
"Drop-in components",
45+
"Supports custom user data",
46+
"Transactions, products, and direct payments",
47+
]}
48+
link={{
49+
href: "https://portal.thirdweb.com/payments/products",
50+
label: "Get Started",
51+
}}
52+
/>
53+
</div>
54+
</section>
55+
);
56+
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import { WebhookIcon } from "lucide-react";
2+
import { redirect } from "next/navigation";
3+
import { ResponsiveSearchParamsProvider } from "responsive-rsc";
4+
import { getAuthToken } from "@/api/auth-token";
5+
import { getProject } from "@/api/project/projects";
6+
import { ProjectPage } from "@/components/blocks/project-page/project-page";
7+
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
8+
import { BridgeIcon } from "@/icons/BridgeIcon";
9+
import { loginRedirect } from "@/utils/redirects";
10+
import { AdvancedSection } from "../payments/components/AdvancedSection.client";
11+
import { PayAnalytics } from "../payments/components/PayAnalytics";
12+
import { getUniversalBridgeFiltersFromSearchParams } from "../payments/components/time";
13+
import { QuickStartSection } from "./QuickstartSection.client";
14+
15+
export default async function Page(props: {
16+
params: Promise<{
17+
team_slug: string;
18+
project_slug: string;
19+
}>;
20+
searchParams: Promise<{
21+
from?: string | undefined | string[];
22+
to?: string | undefined | string[];
23+
interval?: string | undefined | string[];
24+
}>;
25+
}) {
26+
const [params, authToken] = await Promise.all([props.params, getAuthToken()]);
27+
28+
const project = await getProject(params.team_slug, params.project_slug);
29+
30+
if (!authToken) {
31+
loginRedirect(`/team/${params.team_slug}/${params.project_slug}/payments`);
32+
}
33+
34+
if (!project) {
35+
redirect(`/team/${params.team_slug}`);
36+
}
37+
38+
const searchParams = await props.searchParams;
39+
40+
const { range, interval } = getUniversalBridgeFiltersFromSearchParams({
41+
defaultRange: "last-30",
42+
from: searchParams.from,
43+
interval: searchParams.interval,
44+
to: searchParams.to,
45+
});
46+
47+
const client = getClientThirdwebClient({
48+
jwt: authToken,
49+
teamId: project.teamId,
50+
});
51+
52+
return (
53+
<ProjectPage
54+
header={{
55+
client,
56+
title: "Bridge",
57+
icon: BridgeIcon,
58+
description: (
59+
<>
60+
Bridge allows you to enable seamless onramps, swaps, and cross-chain
61+
transfers <br className="max-sm:hidden" /> to move funds
62+
effortlessly across any EVM network
63+
</>
64+
),
65+
actions: {
66+
secondary: {
67+
href: `/team/${params.team_slug}/${params.project_slug}/webhooks/payments`,
68+
label: "Webhooks",
69+
icon: <WebhookIcon className="size-3.5 text-muted-foreground" />,
70+
},
71+
},
72+
settings: {
73+
href: `/team/${params.team_slug}/${params.project_slug}/settings/payments`,
74+
},
75+
links: [
76+
{
77+
type: "docs",
78+
href: "https://portal.thirdweb.com/payments",
79+
},
80+
{
81+
type: "playground",
82+
href: "https://playground.thirdweb.com/payments/ui-components",
83+
},
84+
{
85+
type: "api",
86+
href: "https://api.thirdweb.com/reference#tag/payments",
87+
},
88+
],
89+
}}
90+
footer={{
91+
center: {
92+
links: [
93+
{
94+
href: "https://playground.thirdweb.com/payments/ui-components",
95+
label: "UI Component",
96+
},
97+
{
98+
href: "https://playground.thirdweb.com/connect/payments/fund-wallet",
99+
label: "Buy Crypto",
100+
},
101+
{
102+
href: "https://playground.thirdweb.com/connect/payments/commerce",
103+
label: "Checkout",
104+
},
105+
{
106+
href: "https://playground.thirdweb.com/connect/payments/transactions",
107+
label: "Transactions",
108+
},
109+
],
110+
title: "Demos",
111+
},
112+
left: {
113+
links: [
114+
{
115+
href: "https://portal.thirdweb.com/payments",
116+
label: "Overview",
117+
},
118+
{
119+
href: "https://portal.thirdweb.com/typescript/v5/convertCryptoToFiat",
120+
label: "TypeScript",
121+
},
122+
{
123+
href: "https://portal.thirdweb.com/react/v5/pay/fund-wallets",
124+
label: "React",
125+
},
126+
{
127+
href: "https://portal.thirdweb.com/dotnet/universal-bridge/quickstart",
128+
label: ".NET",
129+
},
130+
],
131+
title: "Documentation",
132+
},
133+
right: {
134+
links: [
135+
{
136+
href: "https://www.youtube.com/watch?v=aBu175-VsNY",
137+
label: "Implement cross-chain payments in any app",
138+
},
139+
],
140+
title: "Tutorials",
141+
},
142+
}}
143+
>
144+
<div className="flex flex-col gap-12">
145+
<ResponsiveSearchParamsProvider value={searchParams}>
146+
<PayAnalytics
147+
client={client}
148+
interval={interval}
149+
projectClientId={project.publishableKey}
150+
projectId={project.id}
151+
range={range}
152+
teamId={project.teamId}
153+
authToken={authToken}
154+
/>
155+
</ResponsiveSearchParamsProvider>
156+
157+
<QuickStartSection
158+
projectSlug={params.project_slug}
159+
teamSlug={params.team_slug}
160+
clientId={project.publishableKey}
161+
teamId={project.teamId}
162+
/>
163+
164+
<AdvancedSection
165+
projectSlug={params.project_slug}
166+
teamSlug={params.team_slug}
167+
/>
168+
</div>
169+
</ProjectPage>
170+
);
171+
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
} from "lucide-react";
1313
import { FullWidthSidebarLayout } from "@/components/blocks/full-width-sidebar-layout";
1414
import { Badge } from "@/components/ui/badge";
15+
import { BridgeIcon } from "@/icons/BridgeIcon";
1516
import { ContractIcon } from "@/icons/ContractIcon";
1617
import { InsightIcon } from "@/icons/InsightIcon";
1718
import { NebulaIcon } from "@/icons/NebulaIcon";
@@ -72,6 +73,11 @@ export function ProjectSidebarLayout(props: {
7273
icon: PayIcon,
7374
label: "Payments",
7475
},
76+
{
77+
href: `${props.layoutPath}/bridge`,
78+
icon: BridgeIcon,
79+
label: "Bridge",
80+
},
7581
{
7682
href: `${props.layoutPath}/tokens`,
7783
icon: TokenIcon,

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ export function AdvancedSection({
2424
description="Build your own branded experiences with the HTTP API or TypeScript SDK."
2525
icon={CodeIcon}
2626
setupTime={10}
27-
color="green"
2827
id="http_api"
2928
features={["Route discovery", "Real-time token prices"]}
3029
link={{
@@ -37,7 +36,6 @@ export function AdvancedSection({
3736
description="Create Webhooks to get notified on each purchase or transaction."
3837
icon={WebhookIcon}
3938
setupTime={5}
40-
color="green"
4139
id="webhooks"
4240
features={["Instant events", "Transaction verification"]}
4341
link={{
@@ -49,14 +47,12 @@ export function AdvancedSection({
4947
title="Swap Tokens"
5048
icon={ArrowRightLeftIcon}
5149
setupTime={5}
52-
color="green"
5350
id="swap_tokens"
5451
features={["Swap any token", "Cross-chain swaps"]}
5552
description="Swap tokens cross-chain with dedicated swapping endpoints."
5653
link={{
5754
href: `https://portal.thirdweb.com/payments/swap`,
5855
label: "Setup Swaps",
59-
target: "_blank",
6056
}}
6157
/>
6258
</div>

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1+
import { XIcon } from "lucide-react";
12
import { Card } from "@/components/ui/card";
23

34
export function EmptyState(props: {
4-
icon: React.FC<{ className?: string }>;
55
title: string;
66
description: string;
77
buttons: Array<React.ReactNode>;
88
}) {
99
return (
10-
<Card className="flex flex-col p-16 gap-8 items-center justify-center">
11-
<div className="bg-violet-800/25 text-muted-foreground rounded-full size-16 flex items-center justify-center">
12-
<props.icon className="size-8 text-violet-500" />
10+
<Card className="flex flex-col p-16 gap-5 items-center justify-center">
11+
<div className="rounded-full p-2.5 border bg-background">
12+
<XIcon className="size-5 text-muted-foreground" />
1313
</div>
1414
<div className="flex flex-col gap-1 items-center text-center">
15-
<h3 className="text-foreground font-medium text-xl">{props.title}</h3>
15+
<h3 className="text-foreground font-medium text-lg tracking-tight">
16+
{props.title}
17+
</h3>
1618
<p className="text-muted-foreground text-sm max-w-md">
1719
{props.description}
1820
</p>

0 commit comments

Comments
 (0)