Skip to content

Commit e2eda05

Browse files
committed
[Dashboard] add empty state for Pay analytics (#7206)
## Summary - implement PayEmbedFTUX with Embed, SDK, and API tabs - show new PayEmbedFTUX when analytics have no data ## Checklist - [x] `pnpm biome check apps/dashboard/src/components/pay/PayAnalytics/PayEmbedFTUX.tsx apps/dashboard/src/components/pay/PayAnalytics/PayAnalytics.tsx --apply` - [x] `pnpm test` *(fails: spawn anvil ENOENT)* <!-- start pr-codex --> --- ## PR-Codex overview This PR primarily focuses on enhancing the `PayAnalytics` and `PayEmbedFTUX` components by adding new features and improving error handling. It also updates the `CodeServer` to support formatting options and modifies the analytics API response handling. ### Detailed summary - Updated error handling in `analytics.ts` to return an empty array instead of `null`. - Added `ignoreFormattingErrors` prop to `CodeServer` in `code.server.tsx`. - Integrated `PayEmbedFTUX` in `PayAnalytics` to display when no volume or wallet data is available. - Introduced `sender` and `receiver` fields in the bridge purchase options in `Buy.ts`. - Created `PayEmbedFTUX` component with tabs for "Embed", "SDK", and "API" code examples. - Added code examples for embedding, SDK usage, and API calls in `PayEmbedFTUX`. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced a first-time user experience (FTUX) interface in the Pay Analytics dashboard, providing integration guides and code examples when no analytics data is available. - **Improvements** - Enhanced code example component to support ignoring formatting errors. - Updated analytics error handling for more consistent data responses. - **Documentation** - Expanded usage examples and parameter descriptions for Pay SDK functions, clarifying required fields and optional parameters. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 7c95211 commit e2eda05

File tree

5 files changed

+140
-3
lines changed

5 files changed

+140
-3
lines changed

apps/dashboard/src/@/api/analytics.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ export async function getUniversalBridgeWalletUsage(args: {
425425
console.error(
426426
`Failed to fetch universal bridge wallet stats: ${res?.status} - ${res.statusText} - ${reason}`,
427427
);
428-
return null;
428+
return [];
429429
}
430430

431431
const json = await res.json();

apps/dashboard/src/@/components/ui/code/code.server.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@ export type CodeProps = {
66
code: string;
77
lang: BundledLanguage;
88
className?: string;
9+
ignoreFormattingErrors?: boolean;
910
};
1011

1112
export const CodeServer: React.FC<CodeProps> = async ({
1213
code,
1314
lang,
1415
className,
16+
ignoreFormattingErrors,
1517
}) => {
16-
const { html, formattedCode } = await getCodeHtml(code, lang);
18+
const { html, formattedCode } = await getCodeHtml(code, lang, {
19+
ignoreFormattingErrors,
20+
});
1721
return <RenderCode code={formattedCode} html={html} className={className} />;
1822
};

apps/dashboard/src/components/pay/PayAnalytics/PayAnalytics.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
getUniversalBridgeWalletUsage,
44
} from "@/api/analytics";
55
import type { Range } from "../../analytics/date-range-selector";
6+
import { PayEmbedFTUX } from "./PayEmbedFTUX";
67
import { PayCustomersTable } from "./components/PayCustomersTable";
78
import { PayNewCustomers } from "./components/PayNewCustomers";
89
import { PaymentHistory } from "./components/PaymentHistory";
@@ -54,6 +55,12 @@ export async function PayAnalytics(props: {
5455
walletDataPromise,
5556
]);
5657

58+
const hasVolume = volumeData.some((d) => d.amountUsdCents > 0);
59+
const hasWallet = walletData.some((d) => d.count > 0);
60+
if (!hasVolume && !hasWallet) {
61+
return <PayEmbedFTUX clientId={props.clientId} />;
62+
}
63+
5764
return (
5865
<div className="flex flex-col gap-10 lg:gap-6">
5966
<GridWithSeparator>
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
"use client";
2+
import { Button } from "@/components/ui/button";
3+
import { CodeServer } from "@/components/ui/code/code.server";
4+
import { TabButtons } from "@/components/ui/tabs";
5+
import { TrackedLinkTW } from "@/components/ui/tracked-link";
6+
import { ExternalLinkIcon } from "lucide-react";
7+
import { useState } from "react";
8+
9+
export function PayEmbedFTUX(props: { clientId: string }) {
10+
const [tab, setTab] = useState("embed");
11+
return (
12+
<div className="rounded-lg border bg-card">
13+
<div className="border-b px-4 py-4 lg:px-6">
14+
<h2 className="font-semibold text-xl tracking-tight">
15+
Start Monetizing Your App
16+
</h2>
17+
</div>
18+
19+
<div className="px-4 py-6 lg:p-6">
20+
<TabButtons
21+
tabClassName="!text-sm"
22+
tabs={[
23+
{
24+
name: "Embed",
25+
onClick: () => setTab("embed"),
26+
isActive: tab === "embed",
27+
},
28+
{
29+
name: "SDK",
30+
onClick: () => setTab("sdk"),
31+
isActive: tab === "sdk",
32+
},
33+
{
34+
name: "API",
35+
onClick: () => setTab("api"),
36+
isActive: tab === "api",
37+
},
38+
]}
39+
/>
40+
<div className="h-2" />
41+
{tab === "embed" && (
42+
<CodeServer
43+
code={embedCode(props.clientId)}
44+
lang="tsx"
45+
className="bg-background"
46+
/>
47+
)}
48+
{tab === "sdk" && (
49+
<CodeServer
50+
code={sdkCode(props.clientId)}
51+
lang="ts"
52+
className="bg-background"
53+
ignoreFormattingErrors
54+
/>
55+
)}
56+
{tab === "api" && (
57+
<CodeServer
58+
code={apiCode(props.clientId)}
59+
lang="bash"
60+
className="bg-background"
61+
ignoreFormattingErrors
62+
/>
63+
)}
64+
</div>
65+
66+
<div className="flex flex-col gap-3 border-t p-4 lg:flex-row lg:items-center lg:justify-between lg:p-6">
67+
<div className="flex gap-3">
68+
<Button asChild variant="outline" size="sm">
69+
<TrackedLinkTW
70+
href="https://portal.thirdweb.com/pay"
71+
target="_blank"
72+
className="gap-2"
73+
category="pay-ftux"
74+
label="docs"
75+
>
76+
View Docs
77+
<ExternalLinkIcon className="size-4 text-muted-foreground" />
78+
</TrackedLinkTW>
79+
</Button>
80+
</div>
81+
</div>
82+
</div>
83+
);
84+
}
85+
86+
const embedCode = (clientId: string) => `\
87+
import { createThirdwebClient } from "thirdweb";
88+
import { PayEmbed } from "thirdweb/react";
89+
90+
const client = createThirdwebClient({
91+
clientId: "${clientId}",
92+
});
93+
94+
export default function App() {
95+
return <PayEmbed client={client} />;
96+
}`;
97+
98+
const sdkCode = (clientId: string) => `\
99+
import { Bridge, NATIVE_TOKEN_ADDRESS, createThirdwebClient, toWei } from "thirdweb";
100+
101+
const client = createThirdwebClient({
102+
clientId: "${clientId}",
103+
});
104+
105+
const quote = await Bridge.Buy.prepare({
106+
originChainId: 1,
107+
originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
108+
destinationChainId: 10,
109+
destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
110+
amount: toWei("0.01"),
111+
sender: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709",
112+
receiver: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709",
113+
client,
114+
});`;
115+
116+
const apiCode = (clientId: string) => `\
117+
curl -X POST https://pay.thirdweb.com/v1/buy/prepare
118+
-H "Content-Type: application/json"
119+
-H "x-client-id: ${clientId}"
120+
-d '{"originChainId":"1","originTokenAddress":"0x...","destinationChainId":"10","destinationTokenAddress":"0x...","amount":"0.01"}'`;

packages/thirdweb/src/bridge/Buy.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,8 @@ export declare namespace quote {
197197
* destinationChainId: 10,
198198
* destinationTokenAddress: NATIVE_TOKEN_ADDRESS,
199199
* amount: toWei("0.01"),
200+
* sender: "0x...",
201+
* receiver: "0x...",
200202
* client: thirdwebClient,
201203
* });
202204
* ```
@@ -282,6 +284,8 @@ export declare namespace quote {
282284
* destinationChainId: 10,
283285
* destinationTokenAddress: NATIVE_TOKEN_ADDRESS,
284286
* amount: toWei("0.01"),
287+
* sender: "0x...",
288+
* receiver: "0x...",
285289
* purchaseData: {
286290
* size: "large",
287291
* shippingAddress: "123 Main St, New York, NY 10001",
@@ -299,6 +303,8 @@ export declare namespace quote {
299303
* destinationChainId: 10,
300304
* destinationTokenAddress: NATIVE_TOKEN_ADDRESS,
301305
* amount: toWei("0.01"),
306+
* sender: "0x...",
307+
* receiver: "0x...",
302308
* maxSteps: 2, // Will only return a quote for routes with 2 or fewer steps
303309
* client: thirdwebClient,
304310
* });
@@ -312,7 +318,7 @@ export declare namespace quote {
312318
* @param options.amount - The amount of the destination token to receive.
313319
* @param options.sender - The address of the sender.
314320
* @param options.receiver - The address of the recipient.
315-
* @param options.purchaseData - Arbitrary data to be passed to the purchase function and included with any webhooks or status calls.
321+
* @param [options.purchaseData] - Arbitrary data to be passed to the purchase function and included with any webhooks or status calls.
316322
* @param [options.maxSteps] - Limit the number of total steps in the route.
317323
* @param options.client - Your thirdweb client.
318324
*

0 commit comments

Comments
 (0)