Skip to content

Commit 551ec68

Browse files
[SDK] Add payment selection tracking (#8490)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on enhancing payment selection tracking throughout the `thirdweb` package by replacing `useQuery` calls with `useEffect` hooks to ensure that payment events are tracked only once per component lifecycle. ### Detailed summary - Replaced `useQuery` for tracking payment events with `useEffect` in multiple components. - Added `useRef` to prevent multiple event firings. - Updated tracking events in: - `UnsupportedTokenScreen` - `BridgeWidget` - `ErrorBanner` - `QuoteLoader` - `PaymentSelection` - `PaymentDetails` - `SwapWidget` - `SuccessScreen` - `CheckoutWidget` - `TransactionWidget` - `BuyWidget` - `BridgeWidget` > ✨ 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 * **Chores** * Refined Bridge telemetry: replaced reactive query-based tracking with guarded one-time render and interaction events to reduce duplicates. * Expanded analytics coverage for widgets, payment selection, loading/quote stages, payment details, swaps, unsupported-token and success flows. * Cleaned up and simplified tracking behavior: some legacy query-driven trackers were removed or consolidated for clearer, more reliable events. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 7b9ce49 commit 551ec68

File tree

12 files changed

+290
-204
lines changed

12 files changed

+290
-204
lines changed

.changeset/large-cats-judge.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
Payment selection tracking

packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx

Lines changed: 63 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
"use client";
22

3-
import { useQuery } from "@tanstack/react-query";
4-
import { useCallback, useMemo, useState } from "react";
3+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
54
import { trackPayEvent } from "../../../../analytics/track/pay.js";
65
import type { TokenWithPrices } from "../../../../bridge/index.js";
76
import type { Chain } from "../../../../chains/types.js";
@@ -327,18 +326,17 @@ export type BuyWidgetProps = {
327326
* @bridge
328327
*/
329328
export function BuyWidget(props: BuyWidgetProps) {
330-
useQuery({
331-
queryFn: () => {
332-
trackPayEvent({
333-
client: props.client,
334-
event: "ub:ui:buy_widget:render",
335-
toChainId: props.chain?.id,
336-
toToken: props.tokenAddress,
337-
});
338-
return true;
339-
},
340-
queryKey: ["buy_widget:render"],
341-
});
329+
const hasFiredRenderEvent = useRef(false);
330+
useEffect(() => {
331+
if (hasFiredRenderEvent.current) return;
332+
hasFiredRenderEvent.current = true;
333+
trackPayEvent({
334+
client: props.client,
335+
event: "ub:ui:buy_widget:render",
336+
toChainId: props.chain?.id,
337+
toToken: props.tokenAddress,
338+
});
339+
}, [props.client, props.chain?.id, props.tokenAddress]);
342340

343341
// if branding is disabled for widget, disable it for connect options too
344342
const connectOptions = useMemo(() => {
@@ -500,6 +498,12 @@ function BridgeWidgetContent(
500498
client={props.client}
501499
connectOptions={props.connectOptions}
502500
onContinue={(destinationAmount, destinationToken, receiverAddress) => {
501+
trackPayEvent({
502+
client: props.client,
503+
event: "payment_selection",
504+
toChainId: destinationToken.chainId,
505+
toToken: destinationToken.address,
506+
});
503507
setScreen({
504508
id: "2:methodSelection",
505509
destinationAmount,
@@ -548,6 +552,20 @@ function BridgeWidgetContent(
548552
handleError(error, undefined);
549553
}}
550554
onPaymentMethodSelected={(paymentMethod) => {
555+
trackPayEvent({
556+
chainId:
557+
paymentMethod.type === "wallet"
558+
? paymentMethod.originToken.chainId
559+
: undefined,
560+
client: props.client,
561+
event: "ub:ui:loading_quote:fund_wallet",
562+
fromToken:
563+
paymentMethod.type === "wallet"
564+
? paymentMethod.originToken.address
565+
: undefined,
566+
toChainId: screen.destinationToken.chainId,
567+
toToken: screen.destinationToken.address,
568+
});
551569
setScreen({
552570
...screen,
553571
id: "3:load-quote",
@@ -581,6 +599,37 @@ function BridgeWidgetContent(
581599
handleError(error, undefined);
582600
}}
583601
onQuoteReceived={(preparedQuote, request) => {
602+
trackPayEvent({
603+
chainId:
604+
preparedQuote.type === "transfer"
605+
? preparedQuote.intent.chainId
606+
: preparedQuote.type === "onramp"
607+
? preparedQuote.intent.chainId
608+
: preparedQuote.intent.originChainId,
609+
client: props.client,
610+
event: "payment_details",
611+
fromToken:
612+
preparedQuote.type === "transfer"
613+
? preparedQuote.intent.tokenAddress
614+
: preparedQuote.type === "onramp"
615+
? preparedQuote.intent.tokenAddress
616+
: preparedQuote.intent.originTokenAddress,
617+
toChainId:
618+
preparedQuote.type === "transfer"
619+
? preparedQuote.intent.chainId
620+
: preparedQuote.type === "onramp"
621+
? preparedQuote.intent.chainId
622+
: preparedQuote.intent.destinationChainId,
623+
toToken:
624+
preparedQuote.type === "transfer"
625+
? preparedQuote.intent.tokenAddress
626+
: preparedQuote.type === "onramp"
627+
? preparedQuote.intent.tokenAddress
628+
: preparedQuote.intent.destinationTokenAddress,
629+
walletAddress:
630+
screen.paymentMethod.payerWallet?.getAccount()?.address,
631+
walletType: screen.paymentMethod.payerWallet?.id,
632+
});
584633
setScreen({
585634
...screen,
586635
id: "4:preview",

packages/thirdweb/src/react/web/ui/Bridge/CheckoutWidget.tsx

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
"use client";
22

3-
import { useQuery } from "@tanstack/react-query";
4-
import { useCallback, useMemo, useState } from "react";
3+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
54
import { trackPayEvent } from "../../../../analytics/track/pay.js";
65
import type { TokenWithPrices } from "../../../../bridge/index.js";
76
import type { Chain } from "../../../../chains/types.js";
@@ -335,18 +334,17 @@ function CheckoutWidgetContentWrapper(props: CheckoutWidgetProps) {
335334
client: props.client,
336335
});
337336

338-
useQuery({
339-
queryFn: () => {
340-
trackPayEvent({
341-
client: props.client,
342-
event: "ub:ui:checkout_widget:render",
343-
toChainId: props.chain.id,
344-
toToken: props.tokenAddress,
345-
});
346-
return true;
347-
},
348-
queryKey: ["checkout_widget:render"],
349-
});
337+
const hasFiredRenderEvent = useRef(false);
338+
useEffect(() => {
339+
if (hasFiredRenderEvent.current) return;
340+
hasFiredRenderEvent.current = true;
341+
trackPayEvent({
342+
client: props.client,
343+
event: "ub:ui:checkout_widget:render",
344+
toChainId: props.chain.id,
345+
toToken: props.tokenAddress,
346+
});
347+
}, [props.client, props.chain.id, props.tokenAddress]);
350348

351349
// if branding is disabled for widget, disable it for connect options too
352350
const connectOptions = useMemo(() => {
@@ -522,6 +520,12 @@ function CheckoutWidgetContent(
522520
buttonLabel={props.buttonLabel}
523521
// others
524522
onContinue={(destinationAmount, destinationToken, receiverAddress) => {
523+
trackPayEvent({
524+
client: props.client,
525+
event: "payment_selection",
526+
toChainId: destinationToken.chainId,
527+
toToken: destinationToken.address,
528+
});
525529
setScreen({
526530
id: "2:methodSelection",
527531
destinationAmount,
@@ -556,6 +560,20 @@ function CheckoutWidgetContent(
556560
handleError(error, undefined);
557561
}}
558562
onPaymentMethodSelected={(paymentMethod) => {
563+
trackPayEvent({
564+
chainId:
565+
paymentMethod.type === "wallet"
566+
? paymentMethod.originToken.chainId
567+
: undefined,
568+
client: props.client,
569+
event: "ub:ui:loading_quote:direct_payment",
570+
fromToken:
571+
paymentMethod.type === "wallet"
572+
? paymentMethod.originToken.address
573+
: undefined,
574+
toChainId: screen.destinationToken.chainId,
575+
toToken: screen.destinationToken.address,
576+
});
559577
setScreen({
560578
...screen,
561579
id: "3:load-quote",
@@ -589,6 +607,35 @@ function CheckoutWidgetContent(
589607
handleError(error, undefined);
590608
}}
591609
onQuoteReceived={(preparedQuote, request) => {
610+
if (
611+
preparedQuote.type === "buy" ||
612+
preparedQuote.type === "sell" ||
613+
preparedQuote.type === "transfer"
614+
) {
615+
trackPayEvent({
616+
chainId:
617+
preparedQuote.type === "transfer"
618+
? preparedQuote.intent.chainId
619+
: preparedQuote.intent.originChainId,
620+
client: props.client,
621+
event: "payment_details",
622+
fromToken:
623+
preparedQuote.type === "transfer"
624+
? preparedQuote.intent.tokenAddress
625+
: preparedQuote.intent.originTokenAddress,
626+
toChainId:
627+
preparedQuote.type === "transfer"
628+
? preparedQuote.intent.chainId
629+
: preparedQuote.intent.destinationChainId,
630+
toToken:
631+
preparedQuote.type === "transfer"
632+
? preparedQuote.intent.tokenAddress
633+
: preparedQuote.intent.destinationTokenAddress,
634+
walletAddress:
635+
screen.paymentMethod.payerWallet?.getAccount()?.address,
636+
walletType: screen.paymentMethod.payerWallet?.id,
637+
});
638+
}
592639
setScreen({
593640
...screen,
594641
id: "4:preview",

packages/thirdweb/src/react/web/ui/Bridge/ErrorBanner.tsx

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22
import { CrossCircledIcon } from "@radix-ui/react-icons";
3-
import { useQuery } from "@tanstack/react-query";
3+
import { useEffect, useRef } from "react";
44
import { trackPayEvent } from "../../../../analytics/track/pay.js";
55
import type { ThirdwebClient } from "../../../../client/client.js";
66
import { useCustomTheme } from "../../../core/design-system/CustomThemeProvider.js";
@@ -38,17 +38,16 @@ export function ErrorBanner({
3838

3939
const { userMessage } = useBridgeError({ error });
4040

41-
useQuery({
42-
queryFn: () => {
43-
trackPayEvent({
44-
client,
45-
error: error.message,
46-
event: "ub:ui:error",
47-
});
48-
return true;
49-
},
50-
queryKey: ["error_banner", userMessage],
51-
});
41+
const hasFiredErrorEvent = useRef(false);
42+
useEffect(() => {
43+
if (hasFiredErrorEvent.current) return;
44+
hasFiredErrorEvent.current = true;
45+
trackPayEvent({
46+
client,
47+
error: error.message,
48+
event: "ub:ui:error",
49+
});
50+
}, [client, error.message]);
5251

5352
return (
5453
<Container flex="column" fullHeight gap="md" p="md">

packages/thirdweb/src/react/web/ui/Bridge/QuoteLoader.tsx

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
"use client";
2-
import { useQuery } from "@tanstack/react-query";
32
import { useEffect } from "react";
4-
import { trackPayEvent } from "../../../../analytics/track/pay.js";
53
import type { Token } from "../../../../bridge/types/Token.js";
64
import type { ThirdwebClient } from "../../../../client/client.js";
75
import type { PurchaseData } from "../../../../pay/types.js";
@@ -92,7 +90,7 @@ export function QuoteLoader({
9290
purchaseData,
9391
paymentLinkId,
9492
feePayer,
95-
mode,
93+
mode: _mode,
9694
}: QuoteLoaderProps) {
9795
const request: BridgePrepareRequest = getBridgeParams({
9896
amount,
@@ -107,28 +105,6 @@ export function QuoteLoader({
107105
});
108106
const prepareQuery = useBridgePrepare(request);
109107

110-
useQuery({
111-
queryFn: () => {
112-
trackPayEvent({
113-
chainId:
114-
paymentMethod.type === "wallet"
115-
? paymentMethod.originToken.chainId
116-
: undefined,
117-
client,
118-
event: `ub:ui:loading_quote:${mode}`,
119-
fromToken:
120-
paymentMethod.type === "wallet"
121-
? paymentMethod.originToken.address
122-
: undefined,
123-
toChainId: destinationToken.chainId,
124-
toToken: destinationToken.address,
125-
walletAddress: sender,
126-
});
127-
return true;
128-
},
129-
queryKey: ["loading_quote", paymentMethod.type],
130-
});
131-
132108
// Handle successful quote
133109
useEffect(() => {
134110
if (prepareQuery.data) {

0 commit comments

Comments
 (0)