From c6849a873410c7ae10e9e3f2c13c49e590b3c86f Mon Sep 17 00:00:00 2001 From: MananTank Date: Fri, 1 Aug 2025 20:08:59 +0000 Subject: [PATCH] [MNY-61] Dashboard: Tokens and Payments tracking updates (#7781) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ## PR-Codex overview This PR focuses on enhancing the payment link functionality and analytics reporting within the application. It introduces new hooks, modifies existing components, and refines analytics events to improve tracking and user experience. ### Detailed summary - Added `useEffectOnce` hook for single execution of effects. - Implemented `AssetPageView` component to report asset page views. - Updated `CreatePaymentLinkButton` to report link creation. - Enhanced payment link reporting with success and failure events. - Removed unused analytics functions related to asset creation. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` --- apps/dashboard/src/@/analytics/report.ts | 60 ++++++++++++------- .../src/@/api/universal-bridge/developer.ts | 6 +- apps/dashboard/src/@/hooks/useEffectOnce.ts | 14 +++++ .../_components/asset-page-view.tsx | 17 ++++++ .../public-pages/erc20/erc20.tsx | 2 + .../public-pages/nft/nft-page-layout.tsx | 2 + .../RecentPaymentsSection.client.tsx | 1 - .../CreatePaymentLinkButton.client.tsx | 13 +++- .../[project_slug]/(sidebar)/tokens/cards.tsx | 11 ---- .../team-onboarding/team-onboarding.tsx | 6 +- apps/dashboard/src/app/pay/[id]/page.tsx | 6 -- .../client/PayPageWidget.client.tsx | 16 ++++- 12 files changed, 105 insertions(+), 49 deletions(-) create mode 100644 apps/dashboard/src/@/hooks/useEffectOnce.ts create mode 100644 apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/_components/asset-page-view.tsx diff --git a/apps/dashboard/src/@/analytics/report.ts b/apps/dashboard/src/@/analytics/report.ts index dcc55ad4fb1..1f3352e90ea 100644 --- a/apps/dashboard/src/@/analytics/report.ts +++ b/apps/dashboard/src/@/analytics/report.ts @@ -1,3 +1,4 @@ +"use client"; import posthog from "posthog-js"; import type { Team } from "@/api/team"; @@ -269,22 +270,6 @@ export function reportAssetBuyFailed(properties: { // Assets Landing Page ---------------------------- -/** - * ### Why do we need to report this event? - * - To track number of asset creation started from the assets page - * - To track which asset types are being created the most - * - * ### Who is responsible for this event? - * @MananTank - */ -export function reportAssetCreationStarted(properties: { - assetType: "nft" | "coin"; -}) { - posthog.capture("asset creation started", { - assetType: properties.assetType, - }); -} - /** * ### Why do we need to report this event? * - To track number of assets imported successfully from the assets page @@ -431,28 +416,59 @@ export function reportPaymentCardClick(properties: { id: string }) { /** * ### Why do we need to report this event? - * - To track payment link usage + * - To create a funnel "create payment link pageview" -> "payment link created" to understand the conversion rate + * + * ### Who is responsible for this event? + * @greg + */ +export function reportPaymentLinkCreated(properties: { + linkId: string; + clientId: string; +}) { + posthog.capture("payment link created", properties); +} + +/** + * ### Why do we need to report this event? + * - To track funnel "payment link pageview" -> "payment link buy successful" to understand the conversion rate * * ### Who is responsible for this event? * @greg */ -export function reportPaymentLinkVisited(properties: { +export function reportPaymentLinkBuySuccessful(properties: { linkId: string; clientId: string; }) { - posthog.capture("payment link visited", properties); + posthog.capture("payment link buy successful", properties); } /** * ### Why do we need to report this event? - * - To track payment link usage + * - To track the number of failed payment link buys + * - To track what errors users encounter when trying to buy from a payment link * * ### Who is responsible for this event? * @greg */ -export function reportPaymentLinkCompleted(properties: { +export function reportPaymentLinkBuyFailed(properties: { linkId: string; clientId: string; + errorMessage: string; +}) { + posthog.capture("payment link buy failed", properties); +} + +/** + * ### Why do we need to report this event? + * - To create a funnel for "asset pageview" -> "asset purchase successful" to understand the conversion rate + * - To understand which asset types are being viewed the most + * + * ### Who is responsible for this event? + * @MananTank + */ +export function reportAssetPageview(properties: { + assetType: "nft" | "coin"; + chainId: number; }) { - posthog.capture("payment link completed", properties); + posthog.capture("asset pageview", properties); } diff --git a/apps/dashboard/src/@/api/universal-bridge/developer.ts b/apps/dashboard/src/@/api/universal-bridge/developer.ts index 05a4c9709be..f5987f3ef8e 100644 --- a/apps/dashboard/src/@/api/universal-bridge/developer.ts +++ b/apps/dashboard/src/@/api/universal-bridge/developer.ts @@ -200,7 +200,11 @@ export async function createPaymentLink(props: { throw new Error(text); } - return; + const response = (await res.json()) as { + data: PaymentLink; + }; + + return response.data; } export async function deletePaymentLink(props: { diff --git a/apps/dashboard/src/@/hooks/useEffectOnce.ts b/apps/dashboard/src/@/hooks/useEffectOnce.ts new file mode 100644 index 00000000000..6e7d37629b4 --- /dev/null +++ b/apps/dashboard/src/@/hooks/useEffectOnce.ts @@ -0,0 +1,14 @@ +/** biome-ignore-all lint/correctness/useExhaustiveDependencies: we only want to run effect once */ +"use client"; +import { useEffect, useRef } from "react"; + +export function useEffectOnce(effect: () => void) { + const hasCalledEffect = useRef(false); + + // eslint-disable-next-line no-restricted-syntax + useEffect(() => { + if (hasCalledEffect.current) return; + hasCalledEffect.current = true; + effect(); + }, []); +} diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/_components/asset-page-view.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/_components/asset-page-view.tsx new file mode 100644 index 00000000000..f08dfcf594e --- /dev/null +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/_components/asset-page-view.tsx @@ -0,0 +1,17 @@ +"use client"; +import { reportAssetPageview } from "@/analytics/report"; +import { useEffectOnce } from "@/hooks/useEffectOnce"; + +export function AssetPageView(props: { + assetType: "nft" | "coin"; + chainId: number; +}) { + useEffectOnce(() => { + reportAssetPageview({ + assetType: props.assetType, + chainId: props.chainId, + }); + }); + + return null; +} diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx index e7534a4dd56..2fbd519762e 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx @@ -4,6 +4,7 @@ import { getContractMetadata } from "thirdweb/extensions/common"; import { decimals, getActiveClaimCondition } from "thirdweb/extensions/erc20"; import { GridPattern } from "@/components/ui/background-patterns"; import { resolveFunctionSelectors } from "@/lib/selectors"; +import { AssetPageView } from "../_components/asset-page-view"; import { getContractCreator } from "../_components/getContractCreator"; import { PageHeader } from "../_components/PageHeader"; import { getTokenPriceData } from "./_apis/token-price-data"; @@ -85,6 +86,7 @@ export async function ERC20PublicPage(props: { return (
+
diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page-layout.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page-layout.tsx index 19270782bb8..17fd4760a33 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page-layout.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page-layout.tsx @@ -1,5 +1,6 @@ import type { ThirdwebContract } from "thirdweb"; import type { ChainMetadata } from "thirdweb/chains"; +import { AssetPageView } from "../_components/asset-page-view"; import { PageHeader } from "../_components/PageHeader"; import { ContractHeaderUI } from "../erc20/_components/ContractHeader"; @@ -16,6 +17,7 @@ export function NFTPublicPageLayout(props: { }) { return (
+
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/components/RecentPaymentsSection.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/components/RecentPaymentsSection.client.tsx index eb2ac17bb6f..42604d1b1b3 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/components/RecentPaymentsSection.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/components/RecentPaymentsSection.client.tsx @@ -99,7 +99,6 @@ export function RecentPaymentsSection(props: { > Create Payment Link diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/links/components/CreatePaymentLinkButton.client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/links/components/CreatePaymentLinkButton.client.tsx index 76db422e868..490e4640c91 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/links/components/CreatePaymentLinkButton.client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/links/components/CreatePaymentLinkButton.client.tsx @@ -10,6 +10,7 @@ import { toast } from "sonner"; import { Bridge, toUnits } from "thirdweb"; import { checksumAddress } from "thirdweb/utils"; import z from "zod"; +import { reportPaymentLinkCreated } from "@/analytics/report"; import { createPaymentLink } from "@/api/universal-bridge/developer"; import { getUniversalBridgeTokens } from "@/api/universal-bridge/tokens"; import { SingleNetworkSelector } from "@/components/blocks/NetworkSelectors"; @@ -91,7 +92,7 @@ export function CreatePaymentLinkButton( throw new Error("Invalid recipient address."); } - await createPaymentLink({ + const result = await createPaymentLink({ clientId: props.clientId, teamId: props.teamId, intent: { @@ -102,7 +103,8 @@ export function CreatePaymentLinkButton( }, title: values.title, }); - return null; + + return result; }, onSuccess: () => { toast.success("Payment link created successfully."); @@ -153,7 +155,12 @@ export function CreatePaymentLinkButton( className="flex flex-col gap-6" onSubmit={form.handleSubmit((values) => createMutation.mutateAsync(values, { - onSuccess: () => { + onSuccess: (result) => { + reportPaymentLinkCreated({ + linkId: result.id, + clientId: props.clientId, + }); + setOpen(false); form.reset(); form.clearErrors(); diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/cards.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/cards.tsx index a9d221899fd..c878006dc85 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/cards.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/cards.tsx @@ -5,7 +5,6 @@ import Link from "next/link"; import { useState } from "react"; import type { ThirdwebClient } from "thirdweb"; import { - reportAssetCreationStarted, reportAssetImportStarted, reportAssetImportSuccessful, } from "@/analytics/report"; @@ -43,11 +42,6 @@ export function Cards(props: { description="Launch your own ERC-20 coin" href={`/team/${props.teamSlug}/${props.projectSlug}/tokens/create/token`} icon={CoinsIcon} - onClick={() => { - reportAssetCreationStarted({ - assetType: "coin", - }); - }} title="Create Coin" /> @@ -55,11 +49,6 @@ export function Cards(props: { description="Launch your own NFT collection" href={`/team/${props.teamSlug}/${props.projectSlug}/tokens/create/nft`} icon={ImagesIcon} - onClick={() => { - reportAssetCreationStarted({ - assetType: "nft", - }); - }} title="Create NFT Collection" /> diff --git a/apps/dashboard/src/app/login/onboarding/team-onboarding/team-onboarding.tsx b/apps/dashboard/src/app/login/onboarding/team-onboarding/team-onboarding.tsx index ceaf42e8a4c..287ab4a2009 100644 --- a/apps/dashboard/src/app/login/onboarding/team-onboarding/team-onboarding.tsx +++ b/apps/dashboard/src/app/login/onboarding/team-onboarding/team-onboarding.tsx @@ -1,5 +1,4 @@ "use client"; -import { useEffect } from "react"; import { toast } from "sonner"; import type { ThirdwebClient } from "thirdweb"; import { upload } from "thirdweb/storage"; @@ -10,6 +9,7 @@ import { reportOnboardingStarted, } from "@/analytics/report"; import type { Team } from "@/api/team"; +import { useEffectOnce } from "@/hooks/useEffectOnce"; import { useDashboardRouter } from "@/lib/DashboardRouter"; import { updateTeam } from "../../../(app)/team/[team_slug]/(team)/~/settings/general/updateTeam"; import { InviteTeamMembersUI } from "./InviteTeamMembers"; @@ -23,9 +23,9 @@ export function TeamInfoForm(props: { const router = useDashboardRouter(); // eslint-disable-next-line no-restricted-syntax - useEffect(() => { + useEffectOnce(() => { reportOnboardingStarted(); - }, []); + }); return ( { + if (paymentLinkId && clientId) { + reportPaymentLinkBuyFailed({ + linkId: paymentLinkId, + clientId: clientId, + errorMessage: error.message, + }); + } + }} paymentLinkId={paymentLinkId} purchaseData={purchaseData} seller={checksumAddress(recipientAddress)}