From 4ca407dbf0cd8fd1af6deefd7d572069bde0bf29 Mon Sep 17 00:00:00 2001 From: MananTank Date: Fri, 5 Sep 2025 20:17:40 +0000 Subject: [PATCH] [MNY-139] Dashboard, Portal, Playground: Add Bridge Product (#7984) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ## PR-Codex overview This PR focuses on enhancing the payment and bridge functionalities in the dashboard and playground applications. It includes UI improvements, new components, and refactoring for better organization and usability. ### Detailed summary - Deleted `RouteDiscoveryCard.tsx`. - Replaced `BridgeIcon` export. - Added route rewrites in `next.config.mjs`. - Updated navigation links in `navLinks.ts`. - Modified `useTokensData.ts` to include price parameters. - Refactored `CreatePaymentWebhookButton` to a function. - Enhanced layout and styling in various components, including `ProjectSidebarLayout`, `EmptyState`, and `QuickstartSection`. - Introduced `ProjectSettingsBreadcrumb` for better navigation. - Added support for locked widgets in payment components. - Improved `PaymentLinksTable` and `PaymentHistory` components with new table structures. - Enhanced `FeatureCard` and `PayAnalytics` with updated styling and structure. - Created `CheckoutPlayground` and `TransactionPlayground` components for better payment handling. - Reorganized the bridge section with improved UI and functionality. > The following files were skipped due to too many changes: `apps/playground-web/src/app/payments/transactions/page.tsx` > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` ## Summary by CodeRabbit * **New Features** * Bridge page with analytics, route discovery, and Quickstart; CSV export for payment history; new Buy, Checkout, and Transaction playgrounds. * **Navigation** * Added "Bridge" to project sidebar; Webhooks consolidated with tabs; Payments page streamlined; redirect from /payments/ui-components → /payments and removed UI Components page. * **UI/UX Improvements** * Redesigned analytics and payment history table; updated FeatureCard and EmptyState visuals; removed "Swap Tokens" card; settings pages use breadcrumbs; assorted copy and spacing tweaks. --- .../project-page/project-page-header.tsx | 15 +- apps/dashboard/src/@/icons/BridgeIcon.tsx | 1 + .../bridge/QuickstartSection.client.tsx | 71 ++++++++ .../payments => bridge}/RouteDiscovery.tsx | 27 +-- .../(sidebar)/bridge}/RouteDiscoveryCard.tsx | 0 .../[project_slug]/(sidebar)/bridge/page.tsx | 161 ++++++++++++++++++ .../components/ProjectSidebarLayout.tsx | 6 + .../components/AdvancedSection.client.tsx | 18 +- .../payments/components/EmptyState.tsx | 12 +- .../components/FeatureCard.client.tsx | 90 +++++----- .../payments/components/PayAnalytics.tsx | 124 ++++++++------ .../components/PaymentHistory.client.tsx | 107 ++++++------ .../payments/components/PaymentsTableRow.tsx | 31 ++-- .../components/QuickstartSection.client.tsx | 7 +- .../components/PaymentLinksTable.client.tsx | 9 +- .../(sidebar)/payments/page.tsx | 83 +++------ .../webhooks/components/webhooks.client.tsx | 2 +- .../project-settings-breadcrumb.tsx | 32 ++++ .../settings/account-abstraction/page.tsx | 90 +++++----- .../(sidebar)/settings/payments/page.tsx | 30 +--- .../(sidebar)/settings/wallets/page.tsx | 20 +-- .../(sidebar)/webhooks/contracts/page.tsx | 56 +----- .../(sidebar)/webhooks/layout.tsx | 57 +++++++ .../(sidebar)/webhooks/payments/page.tsx | 62 +------ apps/playground-web/next.config.mjs | 5 + .../src/app/data/pages-metadata.ts | 8 +- apps/playground-web/src/app/navLinks.ts | 4 - .../payments/commerce/CheckoutPlayground.tsx | 48 ++++++ .../src/app/payments/commerce/page.tsx | 45 +---- .../{embed => components}/LeftSection.tsx | 68 ++++---- .../{embed => components}/RightSection.tsx | 9 +- .../BuyPlayground.tsx} | 29 ++-- .../src/app/payments/fund-wallet/page.tsx | 38 +---- .../transactions/TransactionPlayground.tsx | 48 ++++++ .../src/app/payments/transactions/page.tsx | 144 ++++++++-------- .../src/app/payments/ui-components/page.tsx | 37 ---- .../src/components/code/code-example.tsx | 4 +- .../src/components/pay/transaction-button.tsx | 15 +- .../playground-web/src/hooks/useTokensData.ts | 1 + 39 files changed, 888 insertions(+), 726 deletions(-) create mode 100644 apps/dashboard/src/@/icons/BridgeIcon.tsx create mode 100644 apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/bridge/QuickstartSection.client.tsx rename apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/{settings/payments => bridge}/RouteDiscovery.tsx (83%) rename apps/dashboard/src/{@/components/blocks => app/(app)/team/[team_slug]/[project_slug]/(sidebar)/bridge}/RouteDiscoveryCard.tsx (100%) create mode 100644 apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/bridge/page.tsx create mode 100644 apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/settings/_components/project-settings-breadcrumb.tsx create mode 100644 apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/layout.tsx create mode 100644 apps/playground-web/src/app/payments/commerce/CheckoutPlayground.tsx rename apps/playground-web/src/app/payments/{embed => components}/LeftSection.tsx (92%) rename apps/playground-web/src/app/payments/{embed => components}/RightSection.tsx (96%) rename apps/playground-web/src/app/payments/{embed/page.tsx => fund-wallet/BuyPlayground.tsx} (55%) create mode 100644 apps/playground-web/src/app/payments/transactions/TransactionPlayground.tsx delete mode 100644 apps/playground-web/src/app/payments/ui-components/page.tsx diff --git a/apps/dashboard/src/@/components/blocks/project-page/project-page-header.tsx b/apps/dashboard/src/@/components/blocks/project-page/project-page-header.tsx index 768eb10e0c7..6a027f917f4 100644 --- a/apps/dashboard/src/@/components/blocks/project-page/project-page-header.tsx +++ b/apps/dashboard/src/@/components/blocks/project-page/project-page-header.tsx @@ -23,7 +23,7 @@ type Action = component: React.ReactNode; }; -function Action(props: { action: Action; variant?: "default" | "secondary" }) { +function Action(props: { action: Action; variant?: "default" | "outline" }) { const action = props.action; return "component" in action ? ( @@ -31,10 +31,7 @@ function Action(props: { action: Action; variant?: "default" | "secondary" }) { ) : ( ) : ( diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/components/PayAnalytics.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/components/PayAnalytics.tsx index c9962e20fd2..a66b2024455 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/components/PayAnalytics.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/components/PayAnalytics.tsx @@ -76,67 +76,87 @@ export async function PayAnalytics(props: { return (
-
- -
- - -
- - -
-
- +
+
+

+ Analytics +

+

+ Track Bridge volume, customers, payouts, and success rates. +

+
+
+ +
+ +
+ + +
+
+ + +
+
- -
- } - searchParamsUsed={["from", "to", "interval"]} - > -
- -
- +
+ +
+ x.status === "completed") || [] + } + /> +
+ x.status === "completed") || []} + dateFormat={dateFormat} /> +
+ +
+ + x.status === "completed") || [] + } + dateFormat={dateFormat} + /> + + + +
- x.status === "completed") || []} - dateFormat={dateFormat} - /> - -
- - x.status === "completed") || []} - dateFormat={dateFormat} + +
+ +
+ -
- - - +
+ +
- -
- -
- -
- -
- +
+ +
); } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/components/PaymentHistory.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/components/PaymentHistory.client.tsx index da56a465295..d6888cea4f2 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/components/PaymentHistory.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/components/PaymentHistory.client.tsx @@ -11,8 +11,13 @@ import { } from "@/api/universal-bridge/developer"; import { ExportToCSVButton } from "@/components/blocks/ExportToCSVButton"; import { PaginationButtons } from "@/components/blocks/pagination-buttons"; -import { ScrollShadow } from "@/components/ui/ScrollShadow"; import { Skeleton } from "@/components/ui/skeleton"; +import { + Table, + TableBody, + TableContainer, + TableHeader, +} from "@/components/ui/table"; import { TableData, TableHeading, TableHeadingRow } from "./common"; import { formatTokenAmount } from "./format"; import { TableRow } from "./PaymentsTableRow"; @@ -50,11 +55,11 @@ export function PaymentHistory(props: {
-

+

Transaction History

- Past transactions from your project. + Track past transactions with amount, status, recipient, and date.

-
- - - - - Sent - Received - Type - Status - Recipient - Date - - - - {(!isEmpty || isLoading) && - (payPurchaseData && !isLoading - ? payPurchaseData.data - .filter(isBridgePayment) - .map((purchase) => { - return ( - - ); - }) - : new Array(pageSize).fill(0).map((_, i) => ( - // biome-ignore lint/suspicious/noArrayIndexKey: ok - - )))} - -
+ + + + + Sent + Received + Type + Status + Recipient + Date + + + + {(!isEmpty || isLoading) && + (payPurchaseData && !isLoading + ? payPurchaseData.data + .filter(isBridgePayment) + .map((purchase) => { + return ( + + ); + }) + : new Array(pageSize).fill(0).map((_, i) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: ok + + )))} + +
- {isEmpty && !isLoading ? ( -
- No data available -
- ) : payPurchaseData ? ( -
- -
- ) : null} -
-
+ {isEmpty && !isLoading ? ( +
+ No data available +
+ ) : payPurchaseData ? ( +
+ +
+ ) : null} +
); } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/components/PaymentsTableRow.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/components/PaymentsTableRow.tsx index 160d4c63a55..03a06446547 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/components/PaymentsTableRow.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/components/PaymentsTableRow.tsx @@ -3,8 +3,11 @@ import { type ThirdwebClient, toTokens } from "thirdweb"; import type { BridgePayment } from "@/api/universal-bridge/developer"; import { WalletAddress } from "@/components/blocks/wallet-address"; import { Badge } from "@/components/ui/badge"; +import { + TableCell, + TableRow as TableRowComponent, +} from "@/components/ui/table"; import { cn } from "@/lib/utils"; -import { TableData } from "./common"; import { formatTokenAmount } from "./format"; export function TableRow(props: { @@ -31,20 +34,20 @@ export function TableRow(props: { })(); return ( - {/* Paid */} - {`${formatTokenAmount(originAmount)} ${purchase.originToken.symbol}`} + {`${formatTokenAmount(originAmount)} ${purchase.originToken.symbol}`} {/* Bought */} - + {`${formatTokenAmount(destinationAmount)} ${purchase.destinationToken.symbol}`} - + {/* Type */} - + {type.toLowerCase()} - + {/* Status */} - + {purchase.status.toLowerCase()} - + {/* Address */} - + - + {/* Date */} - +

{format(new Date(purchase.createdAt), "LLL dd, y h:mm a")}

-
- + + ); } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/components/QuickstartSection.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/components/QuickstartSection.client.tsx index 6fb08dffc16..eda409817e6 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/components/QuickstartSection.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/components/QuickstartSection.client.tsx @@ -30,7 +30,6 @@ export function QuickStartSection(props: { description="Create hosted payment UIs to receive any token in seconds." icon={LinkIcon} id="payment_links" - color="violet" badge={{ label: "New", variant: "success", @@ -47,8 +46,7 @@ export function QuickStartSection(props: { teamId={props.teamId} >
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/links/components/PaymentLinksTable.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/links/components/PaymentLinksTable.client.tsx index 4e0a7f9b7bc..de1f2cf9a07 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/links/components/PaymentLinksTable.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/links/components/PaymentLinksTable.client.tsx @@ -1,7 +1,7 @@ "use client"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { LinkIcon, PlusIcon, TrashIcon } from "lucide-react"; +import { PlusIcon, TrashIcon } from "lucide-react"; import { type PropsWithChildren, useState } from "react"; import { toast } from "sonner"; import { toTokens } from "thirdweb"; @@ -44,9 +44,9 @@ export function PaymentLinksTable(props: { clientId: string; teamId: string }) { return (
-

Payments

+

Your Payments

- Payments you have created in this project. + The payments you have created in this project

@@ -114,7 +114,6 @@ function PaymentLinksTableInner(props: { clientId: string; teamId: string }) { if (!paymentLinksQuery.isLoading && paymentLinksQuery.data?.length === 0) { return ( - diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/page.tsx index 56cfb2d1e1c..b6214ea7e07 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/page.tsx @@ -1,7 +1,6 @@ import { Button } from "@workspace/ui/components/button"; -import { PlusIcon } from "lucide-react"; +import { PlusIcon, WebhookIcon } from "lucide-react"; import { redirect } from "next/navigation"; -import { ResponsiveSearchParamsProvider } from "responsive-rsc"; import { getAuthToken } from "@/api/auth-token"; import { getProject } from "@/api/project/projects"; import { ProjectPage } from "@/components/blocks/project-page/project-page"; @@ -9,9 +8,7 @@ import { getClientThirdwebClient } from "@/constants/thirdweb-client.client"; import { PayIcon } from "@/icons/PayIcon"; import { loginRedirect } from "@/utils/redirects"; import { AdvancedSection } from "./components/AdvancedSection.client"; -import { PayAnalytics } from "./components/PayAnalytics"; import { QuickStartSection } from "./components/QuickstartSection.client"; -import { getUniversalBridgeFiltersFromSearchParams } from "./components/time"; import { CreatePaymentLinkButton } from "./links/components/CreatePaymentLinkButton.client"; import { PaymentLinksTable } from "./links/components/PaymentLinksTable.client"; @@ -20,14 +17,8 @@ export default async function Page(props: { team_slug: string; project_slug: string; }>; - searchParams: Promise<{ - from?: string | undefined | string[]; - to?: string | undefined | string[]; - interval?: string | undefined | string[]; - }>; }) { const [params, authToken] = await Promise.all([props.params, getAuthToken()]); - const project = await getProject(params.team_slug, params.project_slug); if (!authToken) { @@ -38,15 +29,6 @@ export default async function Page(props: { redirect(`/team/${params.team_slug}`); } - const searchParams = await props.searchParams; - - const { range, interval } = getUniversalBridgeFiltersFromSearchParams({ - defaultRange: "last-30", - from: searchParams.from, - interval: searchParams.interval, - to: searchParams.to, - }); - const client = getClientThirdwebClient({ jwt: authToken, teamId: project.teamId, @@ -60,9 +42,8 @@ export default async function Page(props: { icon: PayIcon, description: ( <> - Payments allow you to create advanced payment flows to monetize your - app through
product sales, peer to - peer payments, token sales, and more. + Payments allows developers accept crypto payments for goods and + services ), actions: { @@ -79,6 +60,11 @@ export default async function Page(props: { ), }, + secondary: { + href: `/team/${params.team_slug}/${params.project_slug}/webhooks/payments`, + label: "Webhooks", + icon: , + }, }, settings: { href: `/team/${params.team_slug}/${params.project_slug}/settings/payments`, @@ -96,14 +82,6 @@ export default async function Page(props: { type: "api", href: "https://api.thirdweb.com/reference#tag/payments", }, - { - type: "webhooks", - href: `/team/${params.team_slug}/${params.project_slug}/webhooks/payments`, - }, - // { - // type: "settings", - // href: `/team/${params.team_slug}/${params.project_slug}/settings/payments`, - // }, ], }} footer={{ @@ -160,36 +138,23 @@ export default async function Page(props: { }, }} > - -
- - - - - +
+ + - -
- + +
); } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/webhooks/components/webhooks.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/webhooks/components/webhooks.client.tsx index 24ffe371852..742f266211e 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/webhooks/components/webhooks.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/webhooks/components/webhooks.client.tsx @@ -156,7 +156,7 @@ const formSchema = z.object({ version: z.string(), }); -export function CreatePaymentWebhookButton( +function CreatePaymentWebhookButton( props: PropsWithChildren, ) { const [open, setOpen] = useState(false); diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/settings/_components/project-settings-breadcrumb.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/settings/_components/project-settings-breadcrumb.tsx new file mode 100644 index 00000000000..bb87b88c4e8 --- /dev/null +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/settings/_components/project-settings-breadcrumb.tsx @@ -0,0 +1,32 @@ +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator, +} from "@/components/ui/breadcrumb"; + +export function ProjectSettingsBreadcrumb(props: { + teamSlug: string; + projectSlug: string; + page: string; +}) { + const { teamSlug, projectSlug } = props; + + return ( + + + + + Project Settings + + + + + {props.page} + + + + ); +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/settings/account-abstraction/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/settings/account-abstraction/page.tsx index 77f64f8b6d2..8764b31bfed 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/settings/account-abstraction/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/settings/account-abstraction/page.tsx @@ -1,6 +1,4 @@ -import { Button } from "@workspace/ui/components/button"; -import { ArrowLeftIcon, CircleAlertIcon } from "lucide-react"; -import Link from "next/link"; +import { CircleAlertIcon } from "lucide-react"; import { redirect } from "next/navigation"; import { getAuthToken } from "@/api/auth-token"; import { getProject } from "@/api/project/projects"; @@ -13,6 +11,7 @@ import { loginRedirect } from "@/utils/redirects"; import { DefaultFactoriesSection } from "../../account-abstraction/factories/AccountFactories"; import { YourFactoriesSection } from "../../account-abstraction/factories/AccountFactories/your-factories"; import { AccountAbstractionSettingsPage } from "../../account-abstraction/settings/SponsorshipPolicies"; +import { ProjectSettingsBreadcrumb } from "../_components/project-settings-breadcrumb"; export default async function Page(props: { params: Promise<{ team_slug: string; project_slug: string }>; @@ -46,50 +45,51 @@ export default async function Page(props: { const bundlerService = project.services.find((s) => s.name === "bundler"); return ( -
-
- -

Account Abstraction

-
+
+ - {!bundlerService ? ( - - - Account Abstraction service is disabled - - Enable Account Abstraction service in{" "} - - project settings - {" "} - to configure the sponsorship rules - - - ) : ( - <> - +
+ {!bundlerService ? ( + + + Account Abstraction service is disabled + + Enable Account Abstraction service in{" "} + + project settings + {" "} + to configure the sponsorship rules + + + ) : ( + <> + - - - - )} + + + + )} +
); } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/settings/payments/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/settings/payments/page.tsx index 0b0f742477f..eb0fb508508 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/settings/payments/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/settings/payments/page.tsx @@ -1,15 +1,11 @@ -import { Button } from "@workspace/ui/components/button"; -import { ArrowLeftIcon } from "lucide-react"; -import Link from "next/link"; import { redirect } from "next/navigation"; import { getAuthToken } from "@/api/auth-token"; import { getProject } from "@/api/project/projects"; import { getTeamBySlug } from "@/api/team/get-team"; import { getFees } from "@/api/universal-bridge/developer"; -import { getClientThirdwebClient } from "@/constants/thirdweb-client.client"; import { loginRedirect } from "@/utils/redirects"; +import { ProjectSettingsBreadcrumb } from "../_components/project-settings-breadcrumb"; import { PayConfig } from "./PayConfig"; -import { RouteDiscovery } from "./RouteDiscovery"; export default async function Page(props: { params: Promise<{ team_slug: string; project_slug: string }>; @@ -24,7 +20,7 @@ export default async function Page(props: { ]); if (!authToken) { - loginRedirect(`/team/${team_slug}/settings/payments`); + loginRedirect(`/team/${team_slug}/${project_slug}/settings/payments`); } if (!team) { @@ -56,21 +52,13 @@ export default async function Page(props: { }; } - const client = getClientThirdwebClient({ - jwt: authToken, - teamId: team.id, - }); - return ( -
-
- -

Payments

-
+
+ - -
); } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/settings/wallets/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/settings/wallets/page.tsx index 095bf20a187..38319af7c89 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/settings/wallets/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/settings/wallets/page.tsx @@ -1,6 +1,3 @@ -import { Button } from "@workspace/ui/components/button"; -import { ArrowLeftIcon } from "lucide-react"; -import Link from "next/link"; import { redirect } from "next/navigation"; import { getAuthToken } from "@/api/auth-token"; import { getProject } from "@/api/project/projects"; @@ -8,6 +5,7 @@ import { getTeamBySlug } from "@/api/team/get-team"; import { getClientThirdwebClient } from "@/constants/thirdweb-client.client"; import { getValidTeamPlan } from "@/utils/getValidTeamPlan"; import { loginRedirect } from "@/utils/redirects"; +import { ProjectSettingsBreadcrumb } from "../_components/project-settings-breadcrumb"; import { getSMSCountryTiers } from "./api/sms"; import { InAppWalletSettingsPage } from "./components"; @@ -41,15 +39,13 @@ export default async function Page(props: { }); return ( -
-
- -

Wallets

-
+
+ + - ), - }, - }, - links: [ - { - type: "docs", - href: "https://portal.thirdweb.com/payments/webhooks", - }, - ], - }} - tabs={[ - { - name: "Contracts", - path: `/team/${params.team_slug}/${params.project_slug}/webhooks/contracts`, - }, - { - name: "Payments", - path: `/team/${params.team_slug}/${params.project_slug}/webhooks/payments`, - }, - ]} - > - - + ); } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/layout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/layout.tsx new file mode 100644 index 00000000000..6441480b017 --- /dev/null +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/layout.tsx @@ -0,0 +1,57 @@ +import { WebhookIcon } from "lucide-react"; +import { notFound } from "next/navigation"; +import { getAuthToken } from "@/api/auth-token"; +import { getProject } from "@/api/project/projects"; +import { ProjectPage } from "@/components/blocks/project-page/project-page"; +import { getClientThirdwebClient } from "@/constants/thirdweb-client.client"; + +export default async function Layout(props: { + params: Promise<{ + team_slug: string; + project_slug: string; + }>; + children: React.ReactNode; +}) { + const [authToken, params] = await Promise.all([getAuthToken(), props.params]); + + const project = await getProject(params.team_slug, params.project_slug); + + if (!project || !authToken) { + notFound(); + } + + const client = getClientThirdwebClient({ + jwt: authToken, + teamId: project.teamId, + }); + + return ( + + {props.children} + + ); +} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/payments/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/payments/page.tsx index 5efcb34720b..8538df1428a 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/payments/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/payments/page.tsx @@ -1,14 +1,7 @@ -import { Button } from "@workspace/ui/components/button"; -import { PlusIcon, WebhookIcon } from "lucide-react"; import { notFound } from "next/navigation"; import { getAuthToken } from "@/api/auth-token"; import { getProject } from "@/api/project/projects"; -import { ProjectPage } from "@/components/blocks/project-page/project-page"; -import { getClientThirdwebClient } from "@/constants/thirdweb-client.client"; -import { - CreatePaymentWebhookButton, - PayWebhooksPage, -} from "../../payments/webhooks/components/webhooks.client"; +import { PayWebhooksPage } from "../../payments/webhooks/components/webhooks.client"; export default async function Page(props: { params: Promise<{ @@ -24,55 +17,10 @@ export default async function Page(props: { notFound(); } - const client = getClientThirdwebClient({ - jwt: authToken, - teamId: project.teamId, - }); - return ( - - - - ), - }, - }, - links: [ - { - type: "docs", - href: "https://portal.thirdweb.com/payments/webhooks", - }, - ], - }} - tabs={[ - { - name: "Contracts", - path: `/team/${params.team_slug}/${params.project_slug}/webhooks/contracts`, - }, - { - name: "Payments", - path: `/team/${params.team_slug}/${params.project_slug}/webhooks/payments`, - }, - ]} - > - - + ); } diff --git a/apps/playground-web/next.config.mjs b/apps/playground-web/next.config.mjs index 818452dbe63..48675364033 100644 --- a/apps/playground-web/next.config.mjs +++ b/apps/playground-web/next.config.mjs @@ -157,6 +157,11 @@ const nextConfig = { destination: "/reference#tag/payments", permanent: false, }, + { + source: "/payments/ui-components", + destination: "/payments", + permanent: false, + }, ]; }, async rewrites() { diff --git a/apps/playground-web/src/app/data/pages-metadata.ts b/apps/playground-web/src/app/data/pages-metadata.ts index f3dde86eda2..efe9e9e8874 100644 --- a/apps/playground-web/src/app/data/pages-metadata.ts +++ b/apps/playground-web/src/app/data/pages-metadata.ts @@ -164,12 +164,6 @@ export const contractsFeatureCards: FeatureCardMetadata[] = [ ]; export const paymentsFeatureCards: FeatureCardMetadata[] = [ - { - icon: BoxIcon, - title: "Payments UI Components", - link: "/payments/ui-components", - description: "Onramp, swap, and bridge over 1,000+ crypto tokens", - }, { icon: ShoppingBagIcon, title: "Buy Crypto", @@ -186,7 +180,7 @@ export const paymentsFeatureCards: FeatureCardMetadata[] = [ }, { icon: ArrowLeftRightIcon, - title: "Transactions", + title: "Onchain Transaction", link: "/payments/transactions", description: "Enable users to pay for onchain transactions with fiat or crypto", diff --git a/apps/playground-web/src/app/navLinks.ts b/apps/playground-web/src/app/navLinks.ts index 7467bf77cb7..16a283a54c1 100644 --- a/apps/playground-web/src/app/navLinks.ts +++ b/apps/playground-web/src/app/navLinks.ts @@ -189,10 +189,6 @@ const payments: ShadcnSidebarLink = { href: "/payments", exactMatch: true, }, - { - href: "/payments/ui-components", - label: "UI Components", - }, { href: "/payments/fund-wallet", label: "Buy Crypto", diff --git a/apps/playground-web/src/app/payments/commerce/CheckoutPlayground.tsx b/apps/playground-web/src/app/payments/commerce/CheckoutPlayground.tsx new file mode 100644 index 00000000000..8df4071b824 --- /dev/null +++ b/apps/playground-web/src/app/payments/commerce/CheckoutPlayground.tsx @@ -0,0 +1,48 @@ +"use client"; + +import { useState } from "react"; +import { arbitrum } from "thirdweb/chains"; +import { LeftSection } from "../components/LeftSection"; +import { RightSection } from "../components/RightSection"; +import type { BridgeComponentsPlaygroundOptions } from "../components/types"; + +const defaultOptions: BridgeComponentsPlaygroundOptions = { + payOptions: { + buyTokenAddress: undefined, + buyTokenAmount: "0.01", + buyTokenChain: arbitrum, + description: "", + image: "", + buttonLabel: "", + paymentMethods: ["crypto", "card"], + sellerAddress: "0x0000000000000000000000000000000000000000", + title: "", + transactionData: "", + widget: "checkout", + currency: "USD", + showThirdwebBranding: true, + }, + theme: { + darkColorOverrides: {}, + lightColorOverrides: {}, + type: "dark", + }, +}; + +export function CheckoutPlayground() { + const [options, setOptions] = + useState(defaultOptions); + + return ( +
+
+ +
+ +
+ ); +} diff --git a/apps/playground-web/src/app/payments/commerce/page.tsx b/apps/playground-web/src/app/payments/commerce/page.tsx index 2f1fc22d8bb..785ad96f158 100644 --- a/apps/playground-web/src/app/payments/commerce/page.tsx +++ b/apps/playground-web/src/app/payments/commerce/page.tsx @@ -1,9 +1,8 @@ import { CreditCardIcon } from "lucide-react"; import { PageLayout } from "@/components/blocks/APIHeader"; -import { CodeExample } from "@/components/code/code-example"; -import { BuyMerchPreview } from "@/components/pay/direct-payment"; import ThirdwebProvider from "@/components/thirdweb-provider"; import { createMetadata } from "@/lib/metadata"; +import { CheckoutPlayground } from "./CheckoutPlayground"; const title = "Checkout Component"; const description = @@ -29,48 +28,8 @@ export default function Page() { description={description} docsLink="https://portal.thirdweb.com/payments?utm_source=playground" > - + ); } - -function BuyMerch() { - return ( - - ); - };`} - header={{ - description: ( - <> - Take payments from Fiat or Crypto directly to your seller wallet. -
- Get notified for every sale through webhooks, which lets you trigger - any action you want like shipping physical goods, activating - services or doing onchain actions. - - ), - title: "Checkout", - }} - lang="tsx" - preview={} - /> - ); -} diff --git a/apps/playground-web/src/app/payments/embed/LeftSection.tsx b/apps/playground-web/src/app/payments/components/LeftSection.tsx similarity index 92% rename from apps/playground-web/src/app/payments/embed/LeftSection.tsx rename to apps/playground-web/src/app/payments/components/LeftSection.tsx index 90a22d0dd1f..f61fb4ca55f 100644 --- a/apps/playground-web/src/app/payments/embed/LeftSection.tsx +++ b/apps/playground-web/src/app/payments/components/LeftSection.tsx @@ -29,16 +29,18 @@ import { THIRDWEB_CLIENT } from "@/lib/client"; import type { TokenMetadata } from "@/lib/types"; import { CollapsibleSection } from "../../wallets/sign-in/components/CollapsibleSection"; import { ColorFormGroup } from "../../wallets/sign-in/components/ColorFormGroup"; -import type { BridgeComponentsPlaygroundOptions } from "../components/types"; +import type { BridgeComponentsPlaygroundOptions } from "./types"; export function LeftSection(props: { options: BridgeComponentsPlaygroundOptions; setOptions: React.Dispatch< React.SetStateAction >; + lockedWidget?: "buy" | "checkout" | "transaction"; }) { const { options, setOptions } = props; const { theme, payOptions } = options; + const effectiveWidget = props.lockedWidget || payOptions.widget || "buy"; const setThemeType = (themeType: "dark" | "light") => { setOptions((v) => ({ ...v, @@ -117,30 +119,32 @@ export function LeftSection(props: { title="Payment Options" >
-
- - { - setOptions( - (v) => - ({ - ...v, - payOptions: { - ...v.payOptions, - widget: value as "buy" | "checkout" | "transaction", - }, - }) satisfies BridgeComponentsPlaygroundOptions, - ); - }} - options={[ - { label: "Buy", value: "buy" }, - { label: "Checkout", value: "checkout" }, - { label: "Transaction", value: "transaction" }, - ]} - value={payOptions.widget || "buy"} - /> -
+ {props.lockedWidget === undefined && ( +
+ + { + setOptions( + (v) => + ({ + ...v, + payOptions: { + ...v.payOptions, + widget: value as "buy" | "checkout" | "transaction", + }, + }) satisfies BridgeComponentsPlaygroundOptions, + ); + }} + options={[ + { label: "Buy", value: "buy" }, + { label: "Checkout", value: "checkout" }, + { label: "Transaction", value: "transaction" }, + ]} + value={payOptions.widget || "buy"} + /> +
+ )}
@@ -156,7 +160,7 @@ export function LeftSection(props: { })); }} > - + @@ -188,9 +192,7 @@ export function LeftSection(props: {
{/* Shared Chain and Token Selection - Always visible for Buy and Checkout modes */} - {(!payOptions.widget || - payOptions.widget === "buy" || - payOptions.widget === "checkout") && ( + {(effectiveWidget === "buy" || effectiveWidget === "checkout") && (
{/* Chain selection */}
@@ -200,6 +202,7 @@ export function LeftSection(props: { disableTestnets={true} onChange={handleChainChange} placeholder="Select a chain" + className="bg-card" />
@@ -215,6 +218,7 @@ export function LeftSection(props: { onChange={handleTokenChange} placeholder="Select a token" selectedToken={selectedToken} + className="bg-card" />
)} @@ -224,7 +228,7 @@ export function LeftSection(props: { {/* Mode-specific form fields */}
{/* Buy Mode - Amount and Payment Methods */} - {(!payOptions.widget || payOptions.widget === "buy") && ( + {effectiveWidget === "buy" && (
@@ -305,7 +309,7 @@ export function LeftSection(props: { )} {/* Checkout Mode - Seller Address, Price and Payment Methods */} - {payOptions.widget === "checkout" && ( + {effectiveWidget === "checkout" && (
@@ -407,7 +411,7 @@ export function LeftSection(props: { )} {/* Transaction Mode Options */} - {payOptions.widget === "transaction" && ( + {effectiveWidget === "transaction" && (
diff --git a/apps/playground-web/src/app/payments/embed/RightSection.tsx b/apps/playground-web/src/app/payments/components/RightSection.tsx similarity index 96% rename from apps/playground-web/src/app/payments/embed/RightSection.tsx rename to apps/playground-web/src/app/payments/components/RightSection.tsx index b7b6a5052ea..36b76a8a215 100644 --- a/apps/playground-web/src/app/payments/embed/RightSection.tsx +++ b/apps/playground-web/src/app/payments/components/RightSection.tsx @@ -29,6 +29,7 @@ type Tab = "ui" | "code"; export function RightSection(props: { options: BridgeComponentsPlaygroundOptions; tab?: string; + lockedWidget?: "buy" | "checkout" | "transaction"; }) { const pathname = usePathname(); const [previewTab, _setPreviewTab] = useState(() => { @@ -51,8 +52,10 @@ export function RightSection(props: { colors: props.options.theme.lightColorOverrides, }); + const effectiveWidget = props.lockedWidget || props.options.payOptions.widget; + let embed: React.ReactNode; - if (props.options.payOptions.widget === "buy") { + if (effectiveWidget === "buy") { embed = ( ; -}) { - const searchParams = use(props.searchParams); - const [options, setOptions] = useState( - defaultConnectOptions, - ); +export function BuyPlayground() { + const [options, setOptions] = + useState(defaultOptions); return (
- +
- - +
); } diff --git a/apps/playground-web/src/app/payments/fund-wallet/page.tsx b/apps/playground-web/src/app/payments/fund-wallet/page.tsx index c16f446a36d..48b45ec8290 100644 --- a/apps/playground-web/src/app/payments/fund-wallet/page.tsx +++ b/apps/playground-web/src/app/payments/fund-wallet/page.tsx @@ -1,9 +1,8 @@ import { ShoppingBagIcon } from "lucide-react"; import { PageLayout } from "@/components/blocks/APIHeader"; -import { CodeExample } from "@/components/code/code-example"; import ThirdwebProvider from "@/components/thirdweb-provider"; -import { StyledBuyWidgetPreview } from "@/components/universal-bridge/buy"; import { createMetadata } from "@/lib/metadata"; +import { BuyPlayground } from "./BuyPlayground"; const title = "Buy Crypto Component"; const description = @@ -29,41 +28,8 @@ export default function Page() { description={description} docsLink="https://portal.thirdweb.com/wallets/sponsor-gas?utm_source=playground" > - + ); } - -function StyledPayWidget() { - return ( - - ); -}`} - header={{ - description: ( - <> - Inline component that allows users to buy any currency. -
- Customize theme, currency, amounts, payment methods and more. - - ), - title: "Buy Crypto", - }} - lang="tsx" - preview={} - /> - ); -} diff --git a/apps/playground-web/src/app/payments/transactions/TransactionPlayground.tsx b/apps/playground-web/src/app/payments/transactions/TransactionPlayground.tsx new file mode 100644 index 00000000000..4b0a9959f39 --- /dev/null +++ b/apps/playground-web/src/app/payments/transactions/TransactionPlayground.tsx @@ -0,0 +1,48 @@ +"use client"; + +import { useState } from "react"; +import { arbitrum } from "thirdweb/chains"; +import { LeftSection } from "../components/LeftSection"; +import { RightSection } from "../components/RightSection"; +import type { BridgeComponentsPlaygroundOptions } from "../components/types"; + +const defaultOptions: BridgeComponentsPlaygroundOptions = { + payOptions: { + buyTokenAddress: undefined, + buyTokenAmount: "0.01", + buyTokenChain: arbitrum, + description: "", + image: "", + buttonLabel: "", + paymentMethods: ["crypto", "card"], + sellerAddress: "0x0000000000000000000000000000000000000000", + title: "", + transactionData: "", + widget: "transaction", + currency: "USD", + showThirdwebBranding: true, + }, + theme: { + darkColorOverrides: {}, + lightColorOverrides: {}, + type: "dark", + }, +}; + +export function TransactionPlayground() { + const [options, setOptions] = + useState(defaultOptions); + + return ( +
+
+ +
+ +
+ ); +} diff --git a/apps/playground-web/src/app/payments/transactions/page.tsx b/apps/playground-web/src/app/payments/transactions/page.tsx index 5c63c63eda7..dce9cf6772e 100644 --- a/apps/playground-web/src/app/payments/transactions/page.tsx +++ b/apps/playground-web/src/app/payments/transactions/page.tsx @@ -1,14 +1,12 @@ import { ArrowLeftRightIcon } from "lucide-react"; import { PageLayout } from "@/components/blocks/APIHeader"; import { CodeExample } from "@/components/code/code-example"; -import { - PayTransactionButtonPreview, - PayTransactionPreview, -} from "@/components/pay/transaction-button"; +import { PayTransactionButtonPreview } from "@/components/pay/transaction-button"; import ThirdwebProvider from "@/components/thirdweb-provider"; import { createMetadata } from "@/lib/metadata"; +import { TransactionPlayground } from "./TransactionPlayground"; -const title = "Onchain Transaction Component"; +const title = "Onchain Transaction Components"; const description = "Enable seamless onchain transactions for any contract with fiat or crypto with amounts calculated and automatic execution after funds are confirmed."; const ogDescription = @@ -33,88 +31,86 @@ export default function Page() { docsLink="https://portal.thirdweb.com/wallets/sponsor-gas?utm_source=playground" title={title} > - +
+

+ Transaction Widget +

+

+ Render a prebuilt UI for performing transactions using any token or + fiat.
It handles the complete payment flow, supporting both + crypto and fiat payments across 50+ chains. +

+ +
); } -function BuyOnchainAsset() { +function NoFundsPopup() { return ( - ); - };`} - header={{ - description: ( - <> - Let your users pay for onchain transactions with fiat or crypto on - any chain. -
- Amounts are calculated automatically from the transaction, and will - get executed after the user has obtained the necessary funds via - onramp or swap. - - ), - title: "Transactions", - }} - lang="tsx" - preview={} - /> - ); -} -function NoFundsPopup() { - return ( - +} + +return ( +
+

Price: 50 USDC

+ { + if (!account) { throw new Error("No wallet connected"); } + return transfer({ + contract: usdcContract, + amount: "50", + to: account.address, + }); + }} + > + Buy VIP Pass + - return ( - { - if (!account) { throw new Error("No wallet connected"); } - return transfer({ - contract: usdcContract, - amount: "50", - to: account.address, - }); - }} - > - Buy VIP Pass - - ); - };`} + +

Price: 0.1 ETH

+ { + if (!account) { throw new Error("No wallet connected"); } + return transfer({ + contract: usdcContract, + amount: "50", + to: account.address, + }); + }} + > + Buy VIP Pass + +
+); +};`} header={{ description: ( <> @@ -122,7 +118,7 @@ function NoFundsPopup() { the wallet if needed before executing the transaction. ), - title: "Automatic Onramp", + title: "Transaction Button", }} lang="tsx" preview={} diff --git a/apps/playground-web/src/app/payments/ui-components/page.tsx b/apps/playground-web/src/app/payments/ui-components/page.tsx deleted file mode 100644 index 7420766a344..00000000000 --- a/apps/playground-web/src/app/payments/ui-components/page.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { BoxIcon } from "lucide-react"; -import { PageLayout } from "@/components/blocks/APIHeader"; -import ThirdwebProvider from "@/components/thirdweb-provider"; -import { createMetadata } from "@/lib/metadata"; -import PayEmbedPlayground from "../embed/page"; - -const title = "Crypto Payments UI Components"; -const description = - "Onramp, swap, & bridge over 1,000+ tokens to enable seamless crypto payments, checkouts, and transactions"; -const ogDescription = - "Onramp, swap, and bridge cryptocurrency with easy to implement components for purchasing crypto, checking out physical or digital goods and services, and executing onchain transactions."; - -export const metadata = createMetadata({ - description: ogDescription, - title, - image: { - icon: "payments", - title, - }, -}); - -export default function Page(props: { - searchParams: Promise<{ tab: string }>; -}) { - return ( - - - - - - ); -} diff --git a/apps/playground-web/src/components/code/code-example.tsx b/apps/playground-web/src/components/code/code-example.tsx index 03c6b0499cf..fe7fded10cc 100644 --- a/apps/playground-web/src/components/code/code-example.tsx +++ b/apps/playground-web/src/components/code/code-example.tsx @@ -24,10 +24,10 @@ export const CodeExample: React.FC = ({
{header && (
-

+

{header.title}

-

+

{header.description}

diff --git a/apps/playground-web/src/components/pay/transaction-button.tsx b/apps/playground-web/src/components/pay/transaction-button.tsx index e6944edaa16..6259fab27aa 100644 --- a/apps/playground-web/src/components/pay/transaction-button.tsx +++ b/apps/playground-web/src/components/pay/transaction-button.tsx @@ -60,20 +60,15 @@ export function PayTransactionButtonPreview() { const account = useActiveAccount(); const { theme } = useTheme(); + if (!account) { + return ; + } + return ( <> - -
{account && (
-
- Price:{" "} - {USDC?.icon && ( - // eslint-disable-next-line @next/next/no-img-element - {USDC.name} - )} - 50 {USDC?.symbol} -
+
Price: 50 USDC
{ console.error(e); diff --git a/apps/playground-web/src/hooks/useTokensData.ts b/apps/playground-web/src/hooks/useTokensData.ts index 7b0b59ee24e..4ed8b1eca43 100644 --- a/apps/playground-web/src/hooks/useTokensData.ts +++ b/apps/playground-web/src/hooks/useTokensData.ts @@ -13,6 +13,7 @@ async function fetchTokensFromApi(chainId?: number) { url.searchParams.append("chainId", String(chainId)); } url.searchParams.append("limit", "1000"); + url.searchParams.append("includePrices", "false"); const res = await fetch(url.toString(), { headers: {