Skip to content

[Dashboard] add empty state for Pay analytics #7206

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 30, 2025
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
2 changes: 1 addition & 1 deletion apps/dashboard/src/@/api/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ export async function getUniversalBridgeWalletUsage(args: {
console.error(
`Failed to fetch universal bridge wallet stats: ${res?.status} - ${res.statusText} - ${reason}`,
);
return null;
return [];
}

const json = await res.json();
Expand Down
6 changes: 5 additions & 1 deletion apps/dashboard/src/@/components/ui/code/code.server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ export type CodeProps = {
code: string;
lang: BundledLanguage;
className?: string;
ignoreFormattingErrors?: boolean;
};

export const CodeServer: React.FC<CodeProps> = async ({
code,
lang,
className,
ignoreFormattingErrors,
}) => {
const { html, formattedCode } = await getCodeHtml(code, lang);
const { html, formattedCode } = await getCodeHtml(code, lang, {
ignoreFormattingErrors,
});
return <RenderCode code={formattedCode} html={html} className={className} />;
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
getUniversalBridgeWalletUsage,
} from "@/api/analytics";
import type { Range } from "../../analytics/date-range-selector";
import { PayEmbedFTUX } from "./PayEmbedFTUX";
import { PayCustomersTable } from "./components/PayCustomersTable";
import { PayNewCustomers } from "./components/PayNewCustomers";
import { PaymentHistory } from "./components/PaymentHistory";
Expand Down Expand Up @@ -54,6 +55,12 @@ export async function PayAnalytics(props: {
walletDataPromise,
]);

const hasVolume = volumeData.some((d) => d.amountUsdCents > 0);
const hasWallet = walletData.some((d) => d.count > 0);
if (!hasVolume && !hasWallet) {
return <PayEmbedFTUX clientId={props.clientId} />;
}

return (
<div className="flex flex-col gap-10 lg:gap-6">
<GridWithSeparator>
Expand Down
120 changes: 120 additions & 0 deletions apps/dashboard/src/components/pay/PayAnalytics/PayEmbedFTUX.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"use client";
import { Button } from "@/components/ui/button";
import { CodeServer } from "@/components/ui/code/code.server";
import { TabButtons } from "@/components/ui/tabs";
import { TrackedLinkTW } from "@/components/ui/tracked-link";
import { ExternalLinkIcon } from "lucide-react";
import { useState } from "react";

export function PayEmbedFTUX(props: { clientId: string }) {
const [tab, setTab] = useState("embed");
return (
<div className="rounded-lg border bg-card">
<div className="border-b px-4 py-4 lg:px-6">
<h2 className="font-semibold text-xl tracking-tight">
Start Monetizing Your App
</h2>
</div>

<div className="px-4 py-6 lg:p-6">
<TabButtons
tabClassName="!text-sm"
tabs={[
{
name: "Embed",
onClick: () => setTab("embed"),
isActive: tab === "embed",
},
{
name: "SDK",
onClick: () => setTab("sdk"),
isActive: tab === "sdk",
},
{
name: "API",
onClick: () => setTab("api"),
isActive: tab === "api",
},
]}
/>
<div className="h-2" />
{tab === "embed" && (
<CodeServer
code={embedCode(props.clientId)}
lang="tsx"
className="bg-background"
/>
)}
{tab === "sdk" && (
<CodeServer
code={sdkCode(props.clientId)}
lang="ts"
className="bg-background"
ignoreFormattingErrors
/>
)}
{tab === "api" && (
<CodeServer
code={apiCode(props.clientId)}
lang="bash"
className="bg-background"
ignoreFormattingErrors
/>
)}
</div>

<div className="flex flex-col gap-3 border-t p-4 lg:flex-row lg:items-center lg:justify-between lg:p-6">
<div className="flex gap-3">
<Button asChild variant="outline" size="sm">
<TrackedLinkTW
href="https://portal.thirdweb.com/pay"
target="_blank"
className="gap-2"
category="pay-ftux"
label="docs"
>
View Docs
<ExternalLinkIcon className="size-4 text-muted-foreground" />
</TrackedLinkTW>
</Button>
</div>
</div>
</div>
);
}

const embedCode = (clientId: string) => `\
import { createThirdwebClient } from "thirdweb";
import { PayEmbed } from "thirdweb/react";

const client = createThirdwebClient({
clientId: "${clientId}",
});

export default function App() {
return <PayEmbed client={client} />;
}`;

const sdkCode = (clientId: string) => `\
import { Bridge, NATIVE_TOKEN_ADDRESS, createThirdwebClient, toWei } from "thirdweb";

const client = createThirdwebClient({
clientId: "${clientId}",
});

const quote = await Bridge.Buy.prepare({
originChainId: 1,
originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
destinationChainId: 10,
destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
amount: toWei("0.01"),
sender: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709",
receiver: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709",
client,
});`;

const apiCode = (clientId: string) => `\
curl -X POST https://pay.thirdweb.com/v1/buy/prepare
-H "Content-Type: application/json"
-H "x-client-id: ${clientId}"
-d '{"originChainId":"1","originTokenAddress":"0x...","destinationChainId":"10","destinationTokenAddress":"0x...","amount":"0.01"}'`;
8 changes: 7 additions & 1 deletion packages/thirdweb/src/bridge/Buy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ export declare namespace quote {
* destinationChainId: 10,
* destinationTokenAddress: NATIVE_TOKEN_ADDRESS,
* amount: toWei("0.01"),
* sender: "0x...",
* receiver: "0x...",
* client: thirdwebClient,
* });
* ```
Expand Down Expand Up @@ -282,6 +284,8 @@ export declare namespace quote {
* destinationChainId: 10,
* destinationTokenAddress: NATIVE_TOKEN_ADDRESS,
* amount: toWei("0.01"),
* sender: "0x...",
* receiver: "0x...",
* purchaseData: {
* size: "large",
* shippingAddress: "123 Main St, New York, NY 10001",
Expand All @@ -299,6 +303,8 @@ export declare namespace quote {
* destinationChainId: 10,
* destinationTokenAddress: NATIVE_TOKEN_ADDRESS,
* amount: toWei("0.01"),
* sender: "0x...",
* receiver: "0x...",
* maxSteps: 2, // Will only return a quote for routes with 2 or fewer steps
* client: thirdwebClient,
* });
Expand All @@ -312,7 +318,7 @@ export declare namespace quote {
* @param options.amount - The amount of the destination token to receive.
* @param options.sender - The address of the sender.
* @param options.receiver - The address of the recipient.
* @param options.purchaseData - Arbitrary data to be passed to the purchase function and included with any webhooks or status calls.
* @param [options.purchaseData] - Arbitrary data to be passed to the purchase function and included with any webhooks or status calls.
* @param [options.maxSteps] - Limit the number of total steps in the route.
* @param options.client - Your thirdweb client.
*
Expand Down
Loading