From e2eda0510f7d1775f1b0692a8805afe2a73a67ac Mon Sep 17 00:00:00 2001 From: gregfromstl Date: Fri, 30 May 2025 00:25:04 +0000 Subject: [PATCH] [Dashboard] add empty state for Pay analytics (#7206) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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)* --- ## 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}` ## 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. --- apps/dashboard/src/@/api/analytics.ts | 2 +- .../src/@/components/ui/code/code.server.tsx | 6 +- .../pay/PayAnalytics/PayAnalytics.tsx | 7 + .../pay/PayAnalytics/PayEmbedFTUX.tsx | 120 ++++++++++++++++++ packages/thirdweb/src/bridge/Buy.ts | 8 +- 5 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 apps/dashboard/src/components/pay/PayAnalytics/PayEmbedFTUX.tsx diff --git a/apps/dashboard/src/@/api/analytics.ts b/apps/dashboard/src/@/api/analytics.ts index 0bed8ec8eff..62b0c1d239f 100644 --- a/apps/dashboard/src/@/api/analytics.ts +++ b/apps/dashboard/src/@/api/analytics.ts @@ -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(); diff --git a/apps/dashboard/src/@/components/ui/code/code.server.tsx b/apps/dashboard/src/@/components/ui/code/code.server.tsx index 12446f1a0ed..ce6f037a496 100644 --- a/apps/dashboard/src/@/components/ui/code/code.server.tsx +++ b/apps/dashboard/src/@/components/ui/code/code.server.tsx @@ -6,13 +6,17 @@ export type CodeProps = { code: string; lang: BundledLanguage; className?: string; + ignoreFormattingErrors?: boolean; }; export const CodeServer: React.FC = async ({ code, lang, className, + ignoreFormattingErrors, }) => { - const { html, formattedCode } = await getCodeHtml(code, lang); + const { html, formattedCode } = await getCodeHtml(code, lang, { + ignoreFormattingErrors, + }); return ; }; diff --git a/apps/dashboard/src/components/pay/PayAnalytics/PayAnalytics.tsx b/apps/dashboard/src/components/pay/PayAnalytics/PayAnalytics.tsx index 56beaa1a8ee..feed5f36fc2 100644 --- a/apps/dashboard/src/components/pay/PayAnalytics/PayAnalytics.tsx +++ b/apps/dashboard/src/components/pay/PayAnalytics/PayAnalytics.tsx @@ -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"; @@ -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 ; + } + return (
diff --git a/apps/dashboard/src/components/pay/PayAnalytics/PayEmbedFTUX.tsx b/apps/dashboard/src/components/pay/PayAnalytics/PayEmbedFTUX.tsx new file mode 100644 index 00000000000..1624f1949ba --- /dev/null +++ b/apps/dashboard/src/components/pay/PayAnalytics/PayEmbedFTUX.tsx @@ -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 ( +
+
+

+ Start Monetizing Your App +

+
+ +
+ setTab("embed"), + isActive: tab === "embed", + }, + { + name: "SDK", + onClick: () => setTab("sdk"), + isActive: tab === "sdk", + }, + { + name: "API", + onClick: () => setTab("api"), + isActive: tab === "api", + }, + ]} + /> +
+ {tab === "embed" && ( + + )} + {tab === "sdk" && ( + + )} + {tab === "api" && ( + + )} +
+ +
+
+ +
+
+
+ ); +} + +const embedCode = (clientId: string) => `\ +import { createThirdwebClient } from "thirdweb"; +import { PayEmbed } from "thirdweb/react"; + +const client = createThirdwebClient({ + clientId: "${clientId}", +}); + +export default function App() { + return ; +}`; + +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"}'`; diff --git a/packages/thirdweb/src/bridge/Buy.ts b/packages/thirdweb/src/bridge/Buy.ts index 78bafd8b23f..07d1b8c79a1 100644 --- a/packages/thirdweb/src/bridge/Buy.ts +++ b/packages/thirdweb/src/bridge/Buy.ts @@ -197,6 +197,8 @@ export declare namespace quote { * destinationChainId: 10, * destinationTokenAddress: NATIVE_TOKEN_ADDRESS, * amount: toWei("0.01"), + * sender: "0x...", + * receiver: "0x...", * client: thirdwebClient, * }); * ``` @@ -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", @@ -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, * }); @@ -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. *