diff --git a/.changeset/wild-cases-ask.md b/.changeset/wild-cases-ask.md new file mode 100644 index 00000000000..eae1ca2dfd9 --- /dev/null +++ b/.changeset/wild-cases-ask.md @@ -0,0 +1,5 @@ +--- +"thirdweb": patch +--- + +Deprecate PayEmbed diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/components/client/BuyFundsSection.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/components/client/BuyFundsSection.tsx new file mode 100644 index 00000000000..5943195dcc0 --- /dev/null +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/components/client/BuyFundsSection.tsx @@ -0,0 +1,20 @@ +"use client"; +import { defineChain, type ThirdwebClient } from "thirdweb"; +import type { ChainMetadata } from "thirdweb/chains"; +import { BuyWidget } from "thirdweb/react"; + +export function BuyFundsSection(props: { + chain: ChainMetadata; + client: ThirdwebClient; +}) { + return ( + + + + ); +} diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/components/client/PayModal.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/components/client/PayModal.tsx deleted file mode 100644 index 890519d706b..00000000000 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/components/client/PayModal.tsx +++ /dev/null @@ -1,49 +0,0 @@ -"use client"; -import { useTheme } from "next-themes"; -import type { ThirdwebClient } from "thirdweb"; -import { PayEmbed } from "thirdweb/react"; -import { Button } from "@/components/ui/button"; -import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; -import { defineDashboardChain } from "@/lib/defineDashboardChain"; -import { getSDKTheme } from "../../../../../../../../@/utils/sdk-component-theme"; - -export function PayModalButton(props: { - chainId: number; - label: string; - client: ThirdwebClient; -}) { - const { theme } = useTheme(); - - return ( - - - - {props.label} - - - - - - - ); -} diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/components/icons/CreditCardIcon.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/components/icons/CreditCardIcon.tsx deleted file mode 100644 index 0879e1b75d7..00000000000 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/components/icons/CreditCardIcon.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { useId } from "react"; - -export function CreditCardIcon(props: { bg: string; className?: string }) { - const linearGradientId = useId(); - const linearGradientId2 = useId(); - const linearGradientId3 = useId(); - - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -} diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/components/server/BuyFundsSection.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/components/server/BuyFundsSection.tsx deleted file mode 100644 index a1080462cda..00000000000 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/components/server/BuyFundsSection.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { ExternalLinkIcon } from "lucide-react"; -import Link from "next/link"; -import type { ThirdwebClient } from "thirdweb"; -import type { ChainMetadata } from "thirdweb/chains"; -import { ChainIcon } from "../../../../components/server/chain-icon"; -import { PayModalButton } from "../client/PayModal"; -import { CreditCardIcon } from "../icons/CreditCardIcon"; -import { SectionTitle } from "./SectionTitle"; - -export function BuyFundsSection(props: { - chain: ChainMetadata; - client: ThirdwebClient; -}) { - const sanitizedChainName = props.chain.name.replace("Mainnet", "").trim(); - - return ( - - - - - - - - - - - - - Bridge Funds to {sanitizedChainName} - - - - - - Get {props.chain.nativeCurrency.symbol} on {sanitizedChainName} with - fiat or any token on another chain. - - - - - - - - - - Learn more about Universal Bridge - - - - - - ); -} diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/page.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/page.tsx index d9915174260..ea827413f69 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/page.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/page.tsx @@ -2,8 +2,8 @@ import { CircleAlertIcon } from "lucide-react"; import { getClientThirdwebClient } from "@/constants/thirdweb-client.client"; import { getRawAccount } from "../../../../account/settings/getAccount"; import { getChain, getChainMetadata } from "../../utils"; +import { BuyFundsSection } from "./components/client/BuyFundsSection"; import NextSteps from "./components/client/NextSteps"; -import { BuyFundsSection } from "./components/server/BuyFundsSection"; import { ChainOverviewSection } from "./components/server/ChainOverviewSection"; import { ClaimChainSection } from "./components/server/ClaimChainSection"; import { ChainCTA } from "./components/server/cta-card"; diff --git a/apps/portal/public/llms-full.txt b/apps/portal/public/llms-full.txt index cdb88df40da..0b2c8a3df62 100644 --- a/apps/portal/public/llms-full.txt +++ b/apps/portal/public/llms-full.txt @@ -1603,164 +1603,287 @@ let returnType: Element; # Buy Crypto --- -## PayEmbed +## BuyWidget -Embed a prebuilt UI for funding wallets, purchases or transactions with crypto or fiat. +Widget is a prebuilt UI for purchasing a specific token. ### Example -#### Default configuration +#### Basic usage -By default, the `PayEmbed` component will allows users to fund their wallets with crypto or fiat on any of the supported chains.. +The `BuyWidget` component requires `client`, `chain`, and `amount` props to function. ```tsx -; +import { ethereum } from "thirdweb/chains"; + + ``` -#### Top up wallets +#### Buy a specific token -You can set the `mode` option to `"fund_wallet"` to allow users to top up their wallets with crypto or fiat. +You can specify a token to purchase by passing the `tokenAddress` prop. ```tsx -; + chain={ethereum} + amount="100" + tokenAddress="0xA0b86a33E6417E4df2057B2d3C6d9F7cc11b0a70" +/> ``` -#### Direct Payments +#### Customize the UI -You can set the `mode` option to `"direct_payment"` to allow users to make a direct payment to a wallet address. +You can customize the UI of the `BuyWidget` component by passing a custom theme object to the `theme` prop. ```tsx -; + })} +/> ``` -#### Transactions +Refer to the [Theme](https://portal.thirdweb.com/references/typescript/v5/Theme) type for more details. + +#### Update the Title -You can set the `mode` option to `"transaction"` to allow users to execute a transaction with a different wallet, chain or token. +You can update the title of the widget by passing a `title` prop to the `BuyWidget` component. ```tsx -; + chain={ethereum} + amount="0.1" + title="Buy ETH" +/> ``` -You can also handle ERC20 payments by passing `erc20value` to your transaction: +#### Configure the wallet connection + +You can customize the wallet connection flow by passing a `connectOptions` object to the `BuyWidget` component. ```tsx - +/> +``` + +Refer to the [BuyWidgetConnectOptions](https://portal.thirdweb.com/references/typescript/v5/BuyWidgetConnectOptions) type for more details. + +```ts +function BuyWidget(props: BuyWidgetProps): Element; +``` + +### Parameters + +Props of type [BuyWidgetProps](https://portal.thirdweb.com/references/typescript/v5/BuyWidgetProps) to configure the BuyWidget component. + +#### Type + +```ts +let props: { + activeWallet?: Wallet; + amount: string; + chain: Chain; + className?: string; + client: ThirdwebClient; + connectOptions?: BuyWidgetConnectOptions; + description?: string; + hiddenWallets?: Array; + image?: string; + locale?: LocaleId; + onCancel?: () => void; + onError?: (error: Error) => void; + onSuccess?: () => void; + paymentLinkId?: string; + presetOptions?: [number, number, number]; + purchaseData?: Record; + style?: React.CSSProperties; + supportedTokens?: SupportedTokens; + theme?: "light" | "dark" | Theme; + title?: string; + tokenAddress?: Address; +}; +``` + +### Returns + +```ts +let returnType: Element; +``` + +## CheckoutWidget + +Widget is a prebuilt UI for making direct payments to a wallet address. + +### Example + +#### Basic usage + +The `CheckoutWidget` component requires `client`, `chain`, `amount`, `tokenAddress`, and `sellerAddress` props to function. + +```tsx +import { base } from "thirdweb/chains"; +import { getDefaultToken } from "thirdweb/utils"; + + ``` -#### Enable/Disable payment methods +#### With metadata -You can disable the use of crypto or fiat by setting the `buyWithCrypto` or `buyWithFiat` options to `false`. +You can add metadata to display product information. ```tsx -; + chain={base} + amount="35" + tokenAddress={getDefaultToken(base, "USDC")} + sellerAddress="0x..." + title="Black Hoodie (Size L)" + description="Premium cotton hoodie" + image="/drip-hoodie.png" +/> ``` -#### Customize the UI +```ts +function CheckoutWidget(props: CheckoutWidgetProps): Element; +``` + +### Parameters + +Props of type [CheckoutWidgetProps](https://portal.thirdweb.com/references/typescript/v5/CheckoutWidgetProps) to configure the CheckoutWidget component. + +#### Type + +```ts +let props: { + activeWallet?: Wallet; + amount: string; + chain: Chain; + className?: string; + client: ThirdwebClient; + connectOptions?: CheckoutWidgetConnectOptions; + description?: string; + hiddenWallets?: Array; + image?: string; + locale?: LocaleId; + onCancel?: () => void; + onError?: (error: Error) => void; + onSuccess?: () => void; + paymentLinkId?: string; + purchaseData?: Record; + sellerAddress: Address; + style?: React.CSSProperties; + supportedTokens?: SupportedTokens; + theme?: "light" | "dark" | Theme; + title?: string; + tokenAddress: Address; +}; +``` + +### Returns + +```ts +let returnType: Element; +``` + +## TransactionWidget -You can customize the UI of the `PayEmbed` component by passing a custom theme object to the `theme` prop. +Widget is a prebuilt UI for executing a transaction with a different wallet, chain or token. + +### Example + +#### Basic usage + +The `TransactionWidget` component requires `client` and `transaction` props to function. ```tsx -; +/> ``` -Refer to the [Theme](https://portal.thirdweb.com/references/typescript/v5/Theme) type for more details. +#### With metadata -#### Configure the wallet connection +You can add metadata to display transaction information. -You can customize the wallet connection flow by passing a `connectOptions` object to the `PayEmbed` component. +```tsx + +``` + +#### ERC20 payments + +You can handle ERC20 payments by passing `erc20value` to your transaction: ```tsx -; + })} +/> ``` -Refer to the [PayEmbedConnectOptions](https://portal.thirdweb.com/references/typescript/v5/PayEmbedConnectOptions) type for more details. - ```ts -function PayEmbed(props: PayEmbedProps): Element; +function TransactionWidget(props: TransactionWidgetProps): Element; ``` ### Parameters -Props of type [PayEmbedProps](https://portal.thirdweb.com/references/typescript/v5/PayEmbedProps) to configure the PayEmbed component. +Props of type [TransactionWidgetProps](https://portal.thirdweb.com/references/typescript/v5/TransactionWidgetProps) to configure the TransactionWidget component. #### Type @@ -1769,14 +1892,21 @@ let props: { activeWallet?: Wallet; className?: string; client: ThirdwebClient; - connectOptions?: PayEmbedConnectOptions; + connectOptions?: TransactionWidgetConnectOptions; + description?: string; hiddenWallets?: Array; + image?: string; locale?: LocaleId; + onCancel?: () => void; + onError?: (error: Error) => void; + onSuccess?: () => void; paymentLinkId?: string; - payOptions?: PayUIOptions; + purchaseData?: Record; style?: React.CSSProperties; supportedTokens?: SupportedTokens; theme?: "light" | "dark" | Theme; + title?: string; + transaction: PreparedTransaction; }; ``` diff --git a/apps/portal/src/app/pay/customization/payembed/page.mdx b/apps/portal/src/app/pay/customization/payembed/page.mdx deleted file mode 100644 index c9d2601ce9c..00000000000 --- a/apps/portal/src/app/pay/customization/payembed/page.mdx +++ /dev/null @@ -1,154 +0,0 @@ -import { createMetadata, DocImage } from "@doc"; -export const metadata = createMetadata({ - image: { - title: "thirdweb Universal Bridge - Customize PayEmbed", - icon: "thirdweb", - }, - title: "thirdweb Universal Bridge - Customize PayEmbed - thirdweb", - description: - "thirdweb Universal Bridge - Customizing embeddable onramps and crypto purchase flows", -}); - -# PayEmbed Customization - -Learn how to customize the `PayEmbed`. You can find a selection of popular customizations below. For the full list of props, you can [view the full reference](/references/typescript/v5/PayEmbedProps). - ---- - -## Customize Supported Tokens - -You can enable users to select your token on a given chain by passing an array of `SupportedTokens`. Note that this array overrides all default tokens listed for that chain. - -```tsx - -``` - ---- - -## Prefill Destination Token - -In the case you want your users to purchase your token by default in your application, you can choose to pre-fill the Pay purchase flow with `prefillBuy` . - -For example, if you wanted users to only purchase Ethereum on Base network, you could do as follows: - -```tsx -import { base } from "thirdweb/chains"; - -; -``` - -If you'd like to prefill a purchase with a native token, you can set the chain without passing a token: - -```tsx - -``` - ---- - -## Preferred Provider - -You can specify which onramp provider to present to your users. By default, we choose a recommended provider based on the location of the user, KYC status, and currency. Please make sure your set provider is [available in your country](../onramp-providers). - -```tsx - -``` - ---- - -## Disable Payment Methods - -In some cases, you may only want to show users fiat or crypto payment options for your onchain goods or services. You can do this by setting either `buyWithCrypto` or `buyWithFiat` to `false`. - -#### Disable Buy With Crypto - -```tsx - -``` - -#### Disable Buy With Fiat - -```tsx - -``` - ---- - -## Theme - -You can set the theme for the component, which is set to `"dark"` by default. theme can be set to either `"dark"` , `"light"` or a custom theme object. - -We have [`lightTheme`](/references/typescript/v5/lightTheme) or [`darkTheme`](/references/typescript/v5/darkTheme) providers that you can override to kickstart customization. - -You can refer to our [`Theme`](/references/typescript/v5/Theme) page for a full view of customizable options if you’d prefer to create a custom theme from scratch. - -#### Provided Themes - -```tsx - - - -``` - -#### Custom Theme - -```tsx -import { darkTheme } from 'thirdweb/react'; - -// Using custom theme - - -``` diff --git a/apps/portal/src/app/react/v5/adapters/page.mdx b/apps/portal/src/app/react/v5/adapters/page.mdx index 56dc5b1b390..3e98a4c0111 100644 --- a/apps/portal/src/app/react/v5/adapters/page.mdx +++ b/apps/portal/src/app/react/v5/adapters/page.mdx @@ -84,7 +84,7 @@ const onClick = () => { ### Converting a wagmi wallet client to a thirdweb wallet -You can use the thirdweb SDK within a wagmi application by setting the wagmi connected account as the thirdweb 'active wallet'. After that, you can use all of the react components and hooks normally, including `PayEmbed`, `TransactionButton`, etc. +You can use the thirdweb SDK within a wagmi application by setting the wagmi connected account as the thirdweb 'active wallet'. After that, you can use all of the react components and hooks normally, including `BuyWidget`, `TransactionButton`, etc. ```ts // Assumes you've wrapped your application in a `` @@ -126,7 +126,7 @@ You can view a fully functioning wagmi + thirdweb app in this [github repository ## Privy -Similarly, you can use the thirdweb SDK with privy by setting the privy wallet as the thirdweb 'active wallet'. After that, you can use all of the react components, functions and hooks normally, including `PayEmbed`, `TransactionButton`, etc. +Similarly, you can use the thirdweb SDK with privy by setting the privy wallet as the thirdweb 'active wallet'. After that, you can use all of the react components, functions and hooks normally, including `BuyWidget`, `TransactionButton`, etc. ```ts // Assumes you've wrapped your application in a `` diff --git a/apps/portal/src/app/react/v5/pay/fund-wallets/page.mdx b/apps/portal/src/app/react/v5/pay/fund-wallets/page.mdx index 981ae36e101..e5d3f336307 100644 --- a/apps/portal/src/app/react/v5/pay/fund-wallets/page.mdx +++ b/apps/portal/src/app/react/v5/pay/fund-wallets/page.mdx @@ -28,39 +28,33 @@ Try out the demo for yourself in the [buy crypto live playground](https://playgr ## Usage with UI Components -The easiest way to buy crypto is to use the `PayEmbed` prebuilt UI component. Note that the `ConnectButton` also has a built-in funding flow that can also be customised. +The easiest way to buy crypto is to use the `BuyWidget` prebuilt UI component. ```tsx -import { PayEmbed } from "thirdweb/react"; +import { BuyWidget } from "thirdweb/react"; +import { base } from "thirdweb/chains"; function App() { return ( - ); } ``` -Check out the [PayEmbed API reference](/references/typescript/v5/PayEmbed) for more information. +Check out the [BuyWidget API reference](/references/typescript/v5/BuyWidget) for more information. diff --git a/apps/portal/src/app/react/v5/pay/transaction/page.mdx b/apps/portal/src/app/react/v5/pay/transaction/page.mdx index d506c6bd1e9..a82e85715eb 100644 --- a/apps/portal/src/app/react/v5/pay/transaction/page.mdx +++ b/apps/portal/src/app/react/v5/pay/transaction/page.mdx @@ -26,55 +26,32 @@ Try out the demo for yourself in the [live playground](https://playground.thirdw /> -## Usage with PayEmbed +## Usage with regular transactions -You can use the `PayEmbed` component to let users execute a transaction from any chain, paying with any token. +Any transaction sent with `useSendTransaction` or with `TransactionButton` will prompt the user to choose a different payment method if they don't have enough balance for the transaction. -```tsx -import { claimTo } from "thirdweb/extensions/erc1155"; -import { PayEmbed, useActiveAccount } from "thirdweb/react"; +You can turn off this behaviour by setting the `payModal` option to `false`. + +## Usage with TransactionWidget + +The `TransactionWidget` component provides a complete UI for executing transactions with built-in cross-chain payment support. -function App() { - const account = useActiveAccount(); - const { data: nft } = useReadContract(getNFT, { - contract: nftContract, - tokenId: 0n, - }); +```tsx +import { TransactionWidget } from "thirdweb/react"; +import { prepareContractCall } from "thirdweb"; +function MyComponent() { return ( - ); } -``` - -Check out the [PayEmbed API reference](/references/typescript/v5/PayEmbed) for more information. - - - - - - - - -## Usage with regular transactions - -Any transaction sent with `useSendTransaction` or with `TransactionButton` will prompt the user to choose a different payment method if they don't have enough balance for the transaction. - -You can turn off this behaviour by setting the `payModal` option to `false`. \ No newline at end of file +``` \ No newline at end of file diff --git a/apps/portal/src/app/references/components/TDoc/utils/getSidebarLinkgroups.ts b/apps/portal/src/app/references/components/TDoc/utils/getSidebarLinkgroups.ts index d2e2808b283..1e35f1a0edd 100644 --- a/apps/portal/src/app/references/components/TDoc/utils/getSidebarLinkgroups.ts +++ b/apps/portal/src/app/references/components/TDoc/utils/getSidebarLinkgroups.ts @@ -13,7 +13,6 @@ const tagsToGroup = { "@appURI": "App URI", "@auth": "Auth", "@bridge": "Universal Bridge", - "@buyCrypto": "Buy Crypto", "@chain": "Chain", "@claimConditions": "Claim Conditions", "@client": "Client", @@ -70,7 +69,6 @@ const sidebarGroupOrder: TagKey[] = [ "@social", "@auth", "@nft", - "@buyCrypto", "@nftDrop", "@claimConditions", "@delayedReveal", diff --git a/packages/thirdweb/src/bridge/Buy.ts b/packages/thirdweb/src/bridge/Buy.ts index 5c383db576b..b213a22e53d 100644 --- a/packages/thirdweb/src/bridge/Buy.ts +++ b/packages/thirdweb/src/bridge/Buy.ts @@ -99,7 +99,6 @@ import type { PreparedQuote, Quote } from "./types/Quote.js"; * * @throws Will throw an error if there is an issue fetching the quote. * @bridge Buy - * @beta */ export async function quote(options: quote.Options): Promise { const { @@ -326,7 +325,6 @@ export declare namespace quote { * * @throws Will throw an error if there is an issue fetching the quote. * @bridge Buy - * @beta */ export async function prepare( options: prepare.Options, diff --git a/packages/thirdweb/src/react/web/ui/Bridge/BridgeOrchestrator.tsx b/packages/thirdweb/src/react/web/ui/Bridge/BridgeOrchestrator.tsx index 91470eb54d2..96ba777db88 100644 --- a/packages/thirdweb/src/react/web/ui/Bridge/BridgeOrchestrator.tsx +++ b/packages/thirdweb/src/react/web/ui/Bridge/BridgeOrchestrator.tsx @@ -217,6 +217,7 @@ export function BridgeOrchestrator({ {/* Error Banner */} {state.value === "error" && state.context.currentError && ( { send({ type: "BACK" }); }} @@ -332,6 +334,7 @@ export function BridgeOrchestrator({ state.context.quote && state.context.completedStatuses && ( { + trackPayEvent({ + client: props.client, + event: "ub:ui:buy_widget:render", + toChainId: props.chain.id, + toToken: props.tokenAddress, + }); + }, + queryKey: ["buy_widget:render"], + }); + const bridgeDataQuery = useQuery({ queryFn: async (): Promise => { if ( @@ -343,7 +354,13 @@ export function BuyWidget(props: BuyWidgetProps) { ); } else if (bridgeDataQuery.data?.type === "unsupported_token") { // Show unsupported token screen - content = ; + content = ( + + ); } else if (bridgeDataQuery.data?.type === "success") { // Show normal bridge orchestrator content = ( diff --git a/packages/thirdweb/src/react/web/ui/Bridge/CheckoutWidget.tsx b/packages/thirdweb/src/react/web/ui/Bridge/CheckoutWidget.tsx index 48b5facda41..b25c19f0618 100644 --- a/packages/thirdweb/src/react/web/ui/Bridge/CheckoutWidget.tsx +++ b/packages/thirdweb/src/react/web/ui/Bridge/CheckoutWidget.tsx @@ -1,6 +1,7 @@ "use client"; import { useQuery } from "@tanstack/react-query"; +import { trackPayEvent } from "../../../../analytics/track/pay.js"; import type { Token } from "../../../../bridge/index.js"; import type { Chain } from "../../../../chains/types.js"; import type { ThirdwebClient } from "../../../../client/client.js"; @@ -243,14 +244,24 @@ type UIOptionsResult = * * Refer to the [`CheckoutWidgetConnectOptions`](https://portal.thirdweb.com/references/typescript/v5/CheckoutWidgetConnectOptions) type for more details. * - * @bridge - * @beta - * @react + * @bridge Widgets */ export function CheckoutWidget(props: CheckoutWidgetProps) { const localeQuery = useConnectLocale(props.locale || "en_US"); const theme = props.theme || "dark"; + useQuery({ + queryFn: () => { + trackPayEvent({ + client: props.client, + event: "ub:ui:checkout_widget:render", + toChainId: props.chain.id, + toToken: props.tokenAddress, + }); + }, + queryKey: ["checkout_widget:render"], + }); + const bridgeDataQuery = useQuery({ queryFn: async (): Promise => { const token = await getToken( @@ -306,7 +317,13 @@ export function CheckoutWidget(props: CheckoutWidgetProps) { ); } else if (bridgeDataQuery.data?.type === "unsupported_token") { // Show unsupported token screen - content = ; + content = ( + + ); } else if (bridgeDataQuery.data?.type === "success") { // Show normal bridge orchestrator content = ( diff --git a/packages/thirdweb/src/react/web/ui/Bridge/ErrorBanner.tsx b/packages/thirdweb/src/react/web/ui/Bridge/ErrorBanner.tsx index ed40a5ce343..8cdc65efac5 100644 --- a/packages/thirdweb/src/react/web/ui/Bridge/ErrorBanner.tsx +++ b/packages/thirdweb/src/react/web/ui/Bridge/ErrorBanner.tsx @@ -1,5 +1,8 @@ "use client"; import { CrossCircledIcon } from "@radix-ui/react-icons"; +import { useQuery } from "@tanstack/react-query"; +import { trackPayEvent } from "../../../../analytics/track/pay.js"; +import type { ThirdwebClient } from "../../../../client/client.js"; import { useCustomTheme } from "../../../core/design-system/CustomThemeProvider.js"; import { iconSize } from "../../../core/design-system/index.js"; import { useBridgeError } from "../../../core/hooks/useBridgeError.js"; @@ -22,13 +25,30 @@ interface ErrorBannerProps { * Called when user wants to cancel */ onCancel?: () => void; + client: ThirdwebClient; } -export function ErrorBanner({ error, onRetry, onCancel }: ErrorBannerProps) { +export function ErrorBanner({ + error, + onRetry, + onCancel, + client, +}: ErrorBannerProps) { const theme = useCustomTheme(); const { userMessage } = useBridgeError({ error }); + useQuery({ + queryFn: () => { + trackPayEvent({ + client, + error: error.message, + event: "ub:ui:error", + }); + }, + queryKey: ["error_banner", userMessage], + }); + return ( {/* Error Icon and Message */} diff --git a/packages/thirdweb/src/react/web/ui/Bridge/QuoteLoader.tsx b/packages/thirdweb/src/react/web/ui/Bridge/QuoteLoader.tsx index b34c8e1682f..e71cc1ca6f7 100644 --- a/packages/thirdweb/src/react/web/ui/Bridge/QuoteLoader.tsx +++ b/packages/thirdweb/src/react/web/ui/Bridge/QuoteLoader.tsx @@ -1,5 +1,7 @@ "use client"; +import { useQuery } from "@tanstack/react-query"; import { useEffect } from "react"; +import { trackPayEvent } from "../../../../analytics/track/pay.js"; import type { Token } from "../../../../bridge/types/Token.js"; import type { ThirdwebClient } from "../../../../client/client.js"; import { NATIVE_TOKEN_ADDRESS } from "../../../../constants/addresses.js"; @@ -79,9 +81,11 @@ interface QuoteLoaderProps { * Fee payer for direct transfers (defaults to sender) */ feePayer?: "sender" | "receiver"; + mode: "fund_wallet" | "direct_payment" | "transaction"; } export function QuoteLoader({ + mode, destinationToken, paymentMethod, amount, @@ -109,6 +113,26 @@ export function QuoteLoader({ }); const prepareQuery = useBridgePrepare(request); + useQuery({ + queryFn: () => { + trackPayEvent({ + chainId: + paymentMethod.type === "wallet" + ? paymentMethod.originToken.chainId + : undefined, + client, + event: `ub:ui:loading_quote:${mode}`, + fromToken: + paymentMethod.type === "wallet" + ? paymentMethod.originToken.address + : undefined, + toChainId: destinationToken.chainId, + toToken: destinationToken.address, + }); + }, + queryKey: ["loading_quote", paymentMethod.type], + }); + // Handle successful quote useEffect(() => { if (prepareQuery.data) { diff --git a/packages/thirdweb/src/react/web/ui/Bridge/TransactionWidget.tsx b/packages/thirdweb/src/react/web/ui/Bridge/TransactionWidget.tsx index 0bb40d97a7c..d60856bdab4 100644 --- a/packages/thirdweb/src/react/web/ui/Bridge/TransactionWidget.tsx +++ b/packages/thirdweb/src/react/web/ui/Bridge/TransactionWidget.tsx @@ -1,6 +1,7 @@ "use client"; import { useQuery } from "@tanstack/react-query"; +import { trackPayEvent } from "../../../../analytics/track/pay.js"; import type { Token } from "../../../../bridge/index.js"; import type { Chain } from "../../../../chains/types.js"; import type { ThirdwebClient } from "../../../../client/client.js"; @@ -267,14 +268,24 @@ type UIOptionsResult = * * Refer to the [`TransactionWidgetConnectOptions`](https://portal.thirdweb.com/references/typescript/v5/TransactionWidgetConnectOptions) type for more details. * - * @bridge - * @beta - * @react + * @bridge Widgets */ export function TransactionWidget(props: TransactionWidgetProps) { const localeQuery = useConnectLocale(props.locale || "en_US"); const theme = props.theme || "dark"; + useQuery({ + queryFn: () => { + trackPayEvent({ + chainId: props.transaction.chain.id, + client: props.client, + event: "ub:ui:transaction_widget:render", + toToken: props.tokenAddress, + }); + }, + queryKey: ["transaction_widget:render"], + }); + const bridgeDataQuery = useQuery({ queryFn: async (): Promise => { let erc20Value = props.transaction.erc20Value; @@ -331,7 +342,13 @@ export function TransactionWidget(props: TransactionWidgetProps) { ); } else if (bridgeDataQuery.data?.type === "unsupported_token") { // Show unsupported token screen - content = ; + content = ( + + ); } else if (bridgeDataQuery.data?.type === "success") { // Show normal bridge orchestrator content = ( diff --git a/packages/thirdweb/src/react/web/ui/Bridge/UnsupportedTokenScreen.tsx b/packages/thirdweb/src/react/web/ui/Bridge/UnsupportedTokenScreen.tsx index 2a10feeed0c..260f4a171a2 100644 --- a/packages/thirdweb/src/react/web/ui/Bridge/UnsupportedTokenScreen.tsx +++ b/packages/thirdweb/src/react/web/ui/Bridge/UnsupportedTokenScreen.tsx @@ -1,4 +1,7 @@ +import { useQuery } from "@tanstack/react-query"; +import { trackPayEvent } from "../../../../analytics/track/pay.js"; import type { Chain } from "../../../../chains/types.js"; +import type { ThirdwebClient } from "../../../../client/client.js"; import { iconSize } from "../../../core/design-system/index.js"; import { useChainMetadata } from "../../../core/hooks/others/useChainQuery.js"; import { AccentFailIcon } from "../ConnectWallet/icons/AccentFailIcon.js"; @@ -11,6 +14,8 @@ export interface UnsupportedTokenScreenProps { * The chain the token is on */ chain: Chain; + client: ThirdwebClient; + tokenAddress?: string; } /** @@ -18,10 +23,22 @@ export interface UnsupportedTokenScreenProps { * @internal */ export function UnsupportedTokenScreen(props: UnsupportedTokenScreenProps) { - const { chain } = props; + const { chain, tokenAddress, client } = props; const { data: chainMetadata } = useChainMetadata(chain); + useQuery({ + queryFn: () => { + trackPayEvent({ + chainId: chain.id, + client, + event: "ub:ui:unsupported_token", + fromToken: tokenAddress, + }); + }, + queryKey: ["unsupported_token"], + }); + if (chainMetadata?.testnet) { return ( { + if ( + preparedQuote.type === "buy" || + preparedQuote.type === "sell" || + preparedQuote.type === "transfer" + ) { + trackPayEvent({ + chainId: + preparedQuote.type === "transfer" + ? preparedQuote.intent.chainId + : preparedQuote.intent.originChainId, + client, + event: "payment_details", + fromToken: + preparedQuote.type === "transfer" + ? preparedQuote.intent.tokenAddress + : preparedQuote.intent.originTokenAddress, + toChainId: + preparedQuote.type === "transfer" + ? preparedQuote.intent.chainId + : preparedQuote.intent.destinationChainId, + toToken: + preparedQuote.type === "transfer" + ? preparedQuote.intent.tokenAddress + : preparedQuote.intent.destinationTokenAddress, + }); + } + }, + queryKey: ["payment_details", preparedQuote.type], + }); + // Extract common data based on quote type const getDisplayData = () => { switch (preparedQuote.type) { diff --git a/packages/thirdweb/src/react/web/ui/Bridge/payment-selection/PaymentSelection.tsx b/packages/thirdweb/src/react/web/ui/Bridge/payment-selection/PaymentSelection.tsx index 353d5f9c327..e193d54cbdd 100644 --- a/packages/thirdweb/src/react/web/ui/Bridge/payment-selection/PaymentSelection.tsx +++ b/packages/thirdweb/src/react/web/ui/Bridge/payment-selection/PaymentSelection.tsx @@ -1,5 +1,7 @@ "use client"; +import { useQuery } from "@tanstack/react-query"; import { useEffect, useState } from "react"; +import { trackPayEvent } from "../../../../../analytics/track/pay.js"; import type { Token } from "../../../../../bridge/types/Token.js"; import { defineChain } from "../../../../../chains/utils.js"; import type { ThirdwebClient } from "../../../../../client/client.js"; @@ -96,6 +98,18 @@ export function PaymentSelection({ type: "walletSelection", }); + useQuery({ + queryFn: () => { + trackPayEvent({ + client, + event: "payment_selection", + toChainId: destinationToken.chainId, + toToken: destinationToken.address, + }); + }, + queryKey: ["payment_selection"], + }); + const payerWallet = currentStep.type === "tokenSelection" ? currentStep.selectedWallet diff --git a/packages/thirdweb/src/react/web/ui/Bridge/payment-success/SuccessScreen.tsx b/packages/thirdweb/src/react/web/ui/Bridge/payment-success/SuccessScreen.tsx index 6f7f9a3c3d6..f22732fd010 100644 --- a/packages/thirdweb/src/react/web/ui/Bridge/payment-success/SuccessScreen.tsx +++ b/packages/thirdweb/src/react/web/ui/Bridge/payment-success/SuccessScreen.tsx @@ -1,6 +1,9 @@ "use client"; import { CheckIcon } from "@radix-ui/react-icons"; +import { useQuery } from "@tanstack/react-query"; import { useState } from "react"; +import { trackPayEvent } from "../../../../../analytics/track/pay.js"; +import type { ThirdwebClient } from "../../../../../client/client.js"; import type { WindowAdapter } from "../../../../core/adapters/WindowAdapter.js"; import { useCustomTheme } from "../../../../core/design-system/CustomThemeProvider.js"; import { iconSize } from "../../../../core/design-system/index.js"; @@ -37,6 +40,8 @@ export interface SuccessScreenProps { * Window adapter for opening URLs */ windowAdapter: WindowAdapter; + + client: ThirdwebClient; } type ViewState = "success" | "detail"; @@ -47,10 +52,27 @@ export function SuccessScreen({ completedStatuses, onDone, windowAdapter, + client, }: SuccessScreenProps) { const theme = useCustomTheme(); const [viewState, setViewState] = useState("success"); + useQuery({ + queryFn: () => { + if (preparedQuote.type === "buy" || preparedQuote.type === "sell") { + trackPayEvent({ + chainId: preparedQuote.intent.originChainId, + client: client, + event: "ub:ui:success_screen", + fromToken: preparedQuote.intent.originTokenAddress, + toChainId: preparedQuote.intent.destinationChainId, + toToken: preparedQuote.intent.destinationTokenAddress, + }); + } + }, + queryKey: ["success_screen", preparedQuote.type], + }); + if (viewState === "detail") { return ( props.payOptions?.onPurchaseSuccess?.()} theme={theme} title={metadata?.name || "Buy"} tokenAddress={ @@ -374,6 +375,7 @@ export function PayEmbed(props: PayEmbedProps) { description={metadata?.description} image={metadata?.image} name={metadata?.name || "Checkout"} + onSuccess={() => props.payOptions?.onPurchaseSuccess?.()} seller={props.payOptions.paymentInfo.sellerAddress as Address} theme={theme} tokenAddress={ @@ -389,6 +391,7 @@ export function PayEmbed(props: PayEmbedProps) { client={props.client} description={metadata?.description} image={metadata?.image} + onSuccess={() => props.payOptions?.onPurchaseSuccess?.()} theme={theme} title={metadata?.name} transaction={props.payOptions.transaction} diff --git a/packages/thirdweb/src/stories/Bridge/ErrorBanner.stories.tsx b/packages/thirdweb/src/stories/Bridge/ErrorBanner.stories.tsx index b790ba84762..9575ae9f614 100644 --- a/packages/thirdweb/src/stories/Bridge/ErrorBanner.stories.tsx +++ b/packages/thirdweb/src/stories/Bridge/ErrorBanner.stories.tsx @@ -1,4 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react"; +import { createThirdwebClient } from "../../client/client.js"; import type { Theme } from "../../react/core/design-system/index.js"; import { ErrorBanner } from "../../react/web/ui/Bridge/ErrorBanner.js"; import { ModalThemeWrapper } from "../utils.js"; @@ -25,7 +26,10 @@ const ErrorBannerWithTheme = (props: ErrorBannerWithThemeProps) => { const { theme, ...componentProps } = props; return ( - + ); }; diff --git a/packages/thirdweb/src/stories/Bridge/SuccessScreen.stories.tsx b/packages/thirdweb/src/stories/Bridge/SuccessScreen.stories.tsx index dc2e24bb8a7..4d1cb5b38e1 100644 --- a/packages/thirdweb/src/stories/Bridge/SuccessScreen.stories.tsx +++ b/packages/thirdweb/src/stories/Bridge/SuccessScreen.stories.tsx @@ -7,7 +7,7 @@ import { SuccessScreen, type SuccessScreenProps, } from "../../react/web/ui/Bridge/payment-success/SuccessScreen.js"; -import { ModalThemeWrapper } from "../utils.js"; +import { ModalThemeWrapper, storyClient } from "../utils.js"; import { FUND_WALLET_UI_OPTIONS, simpleBuyQuote, @@ -126,6 +126,7 @@ type Story = StoryObj; export const Default: Story = { args: { + client: storyClient, theme: "dark", }, parameters: { @@ -135,6 +136,7 @@ export const Default: Story = { export const DefaultLight: Story = { args: { + client: storyClient, theme: "light", }, parameters: { @@ -144,6 +146,7 @@ export const DefaultLight: Story = { export const OnrampPayment: Story = { args: { + client: storyClient, completedStatuses: mockOnrampCompletedStatuses, preparedQuote: simpleOnrampQuote, theme: "dark", @@ -161,6 +164,7 @@ export const OnrampPayment: Story = { export const OnrampPaymentLight: Story = { args: { + client: storyClient, completedStatuses: mockOnrampCompletedStatuses, preparedQuote: simpleOnrampQuote, theme: "light", @@ -172,6 +176,7 @@ export const OnrampPaymentLight: Story = { export const ComplexPayment: Story = { args: { + client: storyClient, completedStatuses: [ ...mockOnrampCompletedStatuses, ...mockBuyCompletedStatuses, @@ -192,6 +197,7 @@ export const ComplexPayment: Story = { export const ComplexPaymentLight: Story = { args: { + client: storyClient, completedStatuses: [ ...mockOnrampCompletedStatuses, ...mockBuyCompletedStatuses, @@ -206,6 +212,7 @@ export const ComplexPaymentLight: Story = { export const TransactionPayment: Story = { args: { + client: storyClient, completedStatuses: mockBuyCompletedStatuses, preparedQuote: simpleBuyQuote, theme: "light", diff --git a/packages/thirdweb/src/stories/Bridge/UnsupportedTokenScreen.stories.tsx b/packages/thirdweb/src/stories/Bridge/UnsupportedTokenScreen.stories.tsx index 15989042387..23a1f226892 100644 --- a/packages/thirdweb/src/stories/Bridge/UnsupportedTokenScreen.stories.tsx +++ b/packages/thirdweb/src/stories/Bridge/UnsupportedTokenScreen.stories.tsx @@ -1,5 +1,6 @@ import type { Meta, StoryObj } from "@storybook/react"; import { defineChain } from "../../chains/utils.js"; +import { createThirdwebClient } from "../../client/client.js"; import type { Theme } from "../../react/core/design-system/index.js"; import { UnsupportedTokenScreen, @@ -7,6 +8,8 @@ import { } from "../../react/web/ui/Bridge/UnsupportedTokenScreen.js"; import { ModalThemeWrapper } from "../utils.js"; +const TEST_CLIENT = createThirdwebClient({ clientId: "test" }); + // Props interface for the wrapper component interface UnsupportedTokenScreenWithThemeProps extends UnsupportedTokenScreenProps { @@ -57,6 +60,7 @@ type Story = StoryObj; export const TokenNotSupported: Story = { args: { chain: defineChain(1), + client: TEST_CLIENT, theme: "dark", // Ethereum mainnet - will show indexing spinner }, parameters: { @@ -73,6 +77,7 @@ export const TokenNotSupported: Story = { export const TokenNotSupportedLight: Story = { args: { chain: defineChain(1), + client: TEST_CLIENT, theme: "light", // Ethereum mainnet - will show indexing spinner }, parameters: { @@ -89,6 +94,7 @@ export const TokenNotSupportedLight: Story = { export const TestnetNotSupported: Story = { args: { chain: defineChain(11155111), + client: TEST_CLIENT, theme: "dark", // Sepolia testnet - will show error state }, parameters: { @@ -105,6 +111,7 @@ export const TestnetNotSupported: Story = { export const TestnetNotSupportedLight: Story = { args: { chain: defineChain(11155111), + client: TEST_CLIENT, theme: "light", // Sepolia testnet - will show error state }, parameters: { diff --git a/packages/thirdweb/test/src/test-clients.js b/packages/thirdweb/test/src/test-clients.js new file mode 100644 index 00000000000..fc271a25f4a --- /dev/null +++ b/packages/thirdweb/test/src/test-clients.js @@ -0,0 +1,16 @@ +import { createThirdwebClient } from "../../src/client/client.js"; +const secretKey = process.env.TW_SECRET_KEY; +const clientId = process.env.TW_CLIENT_ID; +export const TEST_CLIENT = createThirdwebClient(secretKey + ? { + secretKey, + clientId: clientId ?? undefined, + } + : { + clientId: "TEST", + // if we don't have a secret key, we can use a public gateway for testing? + config: { + storage: { gatewayUrl: "https://gateway.pinata.cloud/ipfs/" }, + }, + }); +//# sourceMappingURL=test-clients.js.map \ No newline at end of file diff --git a/packages/thirdweb/test/src/test-clients.js.map b/packages/thirdweb/test/src/test-clients.js.map new file mode 100644 index 00000000000..e779ff95d7d --- /dev/null +++ b/packages/thirdweb/test/src/test-clients.js.map @@ -0,0 +1 @@ +{"version":3,"file":"test-clients.js","sourceRoot":"","sources":["test-clients.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAElE,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;AAC5C,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;AAE1C,MAAM,CAAC,MAAM,WAAW,GAAG,oBAAoB,CAC7C,SAAS;IACP,CAAC,CAAC;QACE,SAAS;QACT,QAAQ,EAAE,QAAQ,IAAI,SAAS;KAChC;IACH,CAAC,CAAC;QACE,QAAQ,EAAE,MAAM;QAChB,0EAA0E;QAC1E,MAAM,EAAE;YACN,OAAO,EAAE,EAAE,UAAU,EAAE,oCAAoC,EAAE;SAC9D;KACF,CACN,CAAC"} \ No newline at end of file diff --git a/packages/thirdweb/tsdoc.json b/packages/thirdweb/tsdoc.json index 80973ced3e5..d9524f9b198 100644 --- a/packages/thirdweb/tsdoc.json +++ b/packages/thirdweb/tsdoc.json @@ -5,7 +5,6 @@ "@auth": true, "@beta": true, "@bridge": true, - "@buyCrypto": true, "@chain": true, "@client": true, "@component": true, @@ -82,11 +81,7 @@ "syntaxKind": "block", "tagName": "@walletUtils" }, - { - "syntaxKind": "block", - "tagName": "@buyCrypto" - }, - { + { "syntaxKind": "block", "tagName": "@bridge" },
- Get {props.chain.nativeCurrency.symbol} on {sanitizedChainName} with - fiat or any token on another chain. -