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: {