Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export default async function RoutesPage(props: {
</div>
<a
className="inline-flex items-center gap-2 rounded-md bg-green-600 px-4 py-2 font-medium text-sm text-white transition-all hover:bg-green-600/90 hover:shadow-sm"
href="https://portal.thirdweb.com/pay"
href="https://portal.thirdweb.com/payments"
rel="noopener noreferrer"
target="_blank"
>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"use client";

import { CodeIcon, WebhookIcon } from "lucide-react";
import { FeatureCard } from "./FeatureCard.client";

export function AdvancedSection({
teamSlug,
projectSlug,
}: {
teamSlug: string;
projectSlug: string;
}) {
return (
<section>
<div className="mb-4">
<h2 className="font-semibold text-xl tracking-tight">Going Further</h2>
<p className="text-muted-foreground text-sm">
Advanced features to drive revenue in your app.
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<FeatureCard
title="Customized Experience"
description="Build your own branded experiences with the HTTP API or TypeScript SDK."
icon={CodeIcon}
setupTime={10}
color="green"
id="http_api"
features={["Route discovery", "Real-time token prices"]}
link={{
href: "https://payments.thirdweb.com/reference",
label: "Documentation",
}}
/>
<FeatureCard
title="Webhooks"
description="Create Webhooks to get notified on each purchase or transaction."
icon={WebhookIcon}
setupTime={5}
color="green"
id="webhooks"
features={["Instant events", "Transaction verification"]}
link={{
href: `/team/${teamSlug}/${projectSlug}/payments/webhooks`,
label: "Setup Webhooks",
}}
/>
</div>
</section>
);
}
Original file line number Diff line number Diff line change
@@ -1,35 +1,80 @@
"use client";

import { ArrowRightIcon, ClockIcon } from "lucide-react";
import Link from "next/link";
import { reportPaymentCardClick } from "@/analytics/report";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";

export function FeatureCard(props: {
title: string;
description: string;
icon: React.ReactNode;
badge?: { label: string; variant?: "outline" | "success" | "default" };
id: string;
icon: React.FC<{ className?: string }>;
color?: "green" | "violet";
setupTime?: number;
features?: string[];
link: { href: string; label: string; target?: string };
}) {
return (
<Card className="p-4 flex flex-col items-start gap-4">
<div className="text-muted-foreground rounded-full border bg-background size-12 flex items-center justify-center">
{props.icon}
</div>
<div className="flex flex-col gap-0.5">
<h3 className="font-semibold">{props.title}</h3>
<p className="text-muted-foreground text-sm">{props.description}</p>
<Card className="p-6 flex flex-col items-start gap-6 text-muted-foreground w-full">
<div className="flex-1 flex flex-col items-start gap-6 w-full">
<div className="relative w-full">
<div
className={`${props.color === "green" ? "bg-green-700/25" : "bg-violet-700/25"} rounded-lg size-9 flex items-center justify-center`}
>
<props.icon
className={`size-5 ${props.color === "green" ? "text-green-500" : "text-violet-500"}`}
/>
</div>
{props.badge && (
<Badge
variant={props.badge.variant}
className="absolute top-0 right-0"
>
{props.badge.label}
</Badge>
)}
</div>
<div className="flex flex-col gap-1">
<h3 className="font-semibold text-foreground">{props.title}</h3>
<p className="text-muted-foreground text-sm font-medium">
{props.description}
</p>
</div>
{(props.setupTime || props.features) && (
<div className="flex justify-center gap-4 text-xs flex-col">
{props.setupTime && (
<p className="flex gap-2 items-center">
<ClockIcon className="size-3" />
{props.setupTime} minute{props.setupTime > 1 && "s"} setup time
</p>
)}
{props.features && (
<ul>
{props.features.map((feature) => (
<li key={feature} className="flex gap-2 items-center mb-1.5">
<span className="bg-violet-500 size-1.5 rounded-full" />
{feature}
</li>
))}
</ul>
)}
</div>
)}
</div>
<Button
onClick={() => reportPaymentCardClick({ id: props.id })}
size="sm"
variant="default"
className="h-8"
variant="outline"
className="w-full gap-2 group text-foreground"
asChild
>
<Link href={props.link.href} target={props.link.target}>
{props.link.label}
<ArrowRightIcon className="size-4 group-hover:translate-x-1 transition-all" />
</Link>
</Button>
</Card>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export function PaymentHistory(props: {
);
}

function TableRow(props: { purchase: Payment; client: ThirdwebClient }) {
export function TableRow(props: { purchase: Payment; client: ThirdwebClient }) {
const { purchase } = props;
const originAmount = toTokens(
purchase.originAmount,
Expand Down Expand Up @@ -197,7 +197,7 @@ function TableRow(props: { purchase: Payment; client: ThirdwebClient }) {
);
}

function SkeletonTableRow() {
export function SkeletonTableRow() {
return (
<tr className="border-border border-b">
<TableData>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"use client";

import { BadgeDollarSignIcon, CodeIcon, LinkIcon } from "lucide-react";
import { FeatureCard } from "./FeatureCard.client";

export function QuickStartSection({
teamSlug,
projectSlug,
}: {
teamSlug: string;
projectSlug: string;
}) {
return (
<section>
<div className="mb-4">
<h2 className="font-semibold text-xl tracking-tight">Quick Start</h2>
<p className="text-muted-foreground text-sm">
Choose how to integrate payments into your project.
</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<FeatureCard
title="Create Payment Links"
description="Create shareable URLs to receive any token in seconds."
icon={LinkIcon}
id="payment_links"
color="violet"
badge={{
label: "New",
variant: "success",
}}
setupTime={1}
features={[
"No coding required",
"Get paid in any token",
"Send instantly",
]}
link={{
href: `/pay`,
label: "Create Link",
}}
/>
<FeatureCard
title="Earn Fees"
description="Setup fees to earn any time a user swaps or bridges funds."
icon={BadgeDollarSignIcon}
id="fees"
setupTime={1}
color="violet"
badge={{
label: "Recommended",
variant: "outline",
}}
features={[
"Fees on every purchase",
"Custom percentage",
"Directly to your wallet",
]}
link={{
href: `/team/${teamSlug}/${projectSlug}/payments/settings`,
label: "Configure Fees",
}}
/>
<FeatureCard
title="UI Components"
description="Instantly add payments to your React app with prebuild components."
icon={CodeIcon}
id="components"
color="violet"
setupTime={2}
badge={{
label: "Popular",
variant: "outline",
}}
features={[
"Drop-in components",
"Supports custom user data",
"Transactions, products, and direct payments",
]}
link={{
href: "https://portal.thirdweb.com/payments/products",
label: "Get Started",
target: "_blank",
}}
/>
</div>
</section>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
"use client";

import { useQuery } from "@tanstack/react-query";
import { ArrowRightIcon, CreditCardIcon } from "lucide-react";
import Link from "next/link";
import { useMemo } from "react";
import type { ThirdwebClient } from "thirdweb";
import {
getPayments,
type PaymentsResponse,
} from "@/api/universal-bridge/developer";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { TableHeading, TableHeadingRow } from "./common";
import { SkeletonTableRow, TableRow } from "./PaymentHistory";

export function RecentPaymentsSection(props: {
client: ThirdwebClient;
projectClientId: string;
teamId: string;
}) {
const { data: payPurchaseData, isLoading } = useQuery<
PaymentsResponse,
Error
>({
queryFn: async () => {
const res = await getPayments({
clientId: props.projectClientId,
limit: 10,
offset: 0,
teamId: props.teamId,
});
return res;
},
queryKey: ["recent-payments", props.projectClientId],
refetchInterval: 10_000,
});
const isEmpty = useMemo(
() => !payPurchaseData?.data.length,
[payPurchaseData],
);
return (
<section>
<div className="mb-4">
<h2 className="font-semibold text-xl tracking-tight">
Recent Payments
</h2>
<p className="text-muted-foreground text-sm">
The latest payments from your project.
</p>
</div>
{!isEmpty || isLoading ? (
<Card className="overflow-hidden">
<table className="w-full selection:bg-inverted selection:text-inverted-foreground ">
<thead>
<TableHeadingRow>
<TableHeading> Sent </TableHeading>
<TableHeading> Received </TableHeading>
<TableHeading>Type</TableHeading>
<TableHeading>Status</TableHeading>
<TableHeading>Recipient</TableHeading>
<TableHeading>Date</TableHeading>
</TableHeadingRow>
</thead>
<tbody>
{payPurchaseData && !isLoading
? payPurchaseData.data.map((purchase) => {
return (
<TableRow
client={props.client}
key={purchase.id}
purchase={purchase}
/>
);
})
: new Array(10).fill(0).map((_, i) => (
// biome-ignore lint/suspicious/noArrayIndexKey: ok
<SkeletonTableRow key={i} />
))}
</tbody>
</table>
</Card>
) : (
<Card className="flex flex-col p-16 gap-8 items-center justify-center">
<div className="bg-violet-800/25 text-muted-foreground rounded-full size-16 flex items-center justify-center">
<CreditCardIcon className="size-8 text-violet-500" />
</div>
<div className="flex flex-col gap-1 items-center text-center">
<h3 className="text-foreground font-medium text-xl">
No payments yet
</h3>
<p className="text-muted-foreground text-sm max-w-md">
Start accepting crypto payments with payment links, prebuilt
components, or custom branded experiences.
</p>
</div>
<div className="flex gap-4">
<Button
variant="default"
size="sm"
className="flex items-center gap-2"
asChild
>
<Link href="/pay" target="_blank">
Create Payment Link
<ArrowRightIcon className="size-4" />
</Link>
</Button>
<Button asChild variant="outline" size="sm">
<Link href="https://portal.thirdweb.com/payments" target="_blank">
View Documentation
</Link>
</Button>
</div>
</Card>
)}
</section>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ export default async function Layout(props: {
Payments
</h1>
<p className="max-w-3xl text-muted-foreground text-sm leading-relaxed">
Payments allows your users to bridge, swap, and purchase
cryptocurrencies and execute transactions with any fiat options or
tokens via cross-chain routing.{" "}
Send and accept payments with cross-chain token routing.{" "}
<UnderlineLink
href="https://portal.thirdweb.com/payments"
rel="noopener noreferrer"
Expand Down
Loading
Loading