diff --git a/apps/dashboard/.env.example b/apps/dashboard/.env.example index 3f719125270..ec5fd6a4db5 100644 --- a/apps/dashboard/.env.example +++ b/apps/dashboard/.env.example @@ -35,10 +35,6 @@ NEXT_PUBLIC_DASHBOARD_UPLOAD_SERVER="https://storage.thirdweb-dev.com" # - not required to build (unless testing contract search) NEXT_PUBLIC_TYPESENSE_CONTRACT_API_KEY= -# posthog API key -# - not required for prod/staging -NEXT_PUBLIC_POSTHOG_API_KEY="ignored" - # Stripe Customer portal NEXT_PUBLIC_STRIPE_KEY= @@ -108,4 +104,8 @@ STRIPE_SECRET_KEY="" # required for server wallet management NEXT_PUBLIC_THIRDWEB_VAULT_URL="" -NEXT_PUBLIC_ENGINE_CLOUD_URL="" \ No newline at end of file +NEXT_PUBLIC_ENGINE_CLOUD_URL="" + +# posthog setup +NEXT_PUBLIC_POSTHOG_KEY="" +NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com diff --git a/apps/dashboard/.eslintrc.js b/apps/dashboard/.eslintrc.js index d19656a5eb7..3c12095ad34 100644 --- a/apps/dashboard/.eslintrc.js +++ b/apps/dashboard/.eslintrc.js @@ -110,6 +110,11 @@ module.exports = { message: 'This is likely a mistake. If you really want to import this - postfix the imported name with Icon. Example - "LinkIcon"', }, + { + name: "posthog-js", + message: + 'Import "posthog-js" directly only within the analytics helpers ("src/@/analytics/*"). Use the exported helpers from "@/analytics/track" elsewhere.', + }, ], }, ], @@ -139,6 +144,13 @@ module.exports = { "no-restricted-imports": ["off"], }, }, + // allow direct PostHog imports inside analytics helpers + { + files: "src/@/analytics/**/*", + rules: { + "no-restricted-imports": ["off"], + }, + }, // enable rule specifically for TypeScript files { files: ["*.ts", "*.tsx"], diff --git a/apps/dashboard/instrumentation-client.ts b/apps/dashboard/instrumentation-client.ts new file mode 100644 index 00000000000..fd21d85797c --- /dev/null +++ b/apps/dashboard/instrumentation-client.ts @@ -0,0 +1,17 @@ +import posthog from "posthog-js"; + +const NEXT_PUBLIC_POSTHOG_KEY = process.env.NEXT_PUBLIC_POSTHOG_KEY; + +if (NEXT_PUBLIC_POSTHOG_KEY) { + posthog.init(NEXT_PUBLIC_POSTHOG_KEY, { + api_host: "/_ph", + ui_host: "https://us.posthog.com", + capture_pageview: "history_change", + capture_pageleave: "if_capture_pageview", + // disable exception capture (for now) + capture_exceptions: false, + // specifically disable autocapture (does not affect pageview capture) + autocapture: false, + debug: process.env.NODE_ENV === "development", + }); +} diff --git a/apps/dashboard/knip.json b/apps/dashboard/knip.json index 4e487ef46d3..5ad7a8ce70a 100644 --- a/apps/dashboard/knip.json +++ b/apps/dashboard/knip.json @@ -13,6 +13,7 @@ "@thirdweb-dev/service-utils", "@thirdweb-dev/vault-sdk", "@types/color", - "fast-xml-parser" + "fast-xml-parser", + "posthog-node" ] } diff --git a/apps/dashboard/next.config.ts b/apps/dashboard/next.config.ts index cb495b753be..a4218cd681d 100644 --- a/apps/dashboard/next.config.ts +++ b/apps/dashboard/next.config.ts @@ -146,6 +146,18 @@ const baseNextConfig: NextConfig = { }, async rewrites() { return [ + { + source: "/_ph/static/:path*", + destination: "https://us-assets.i.posthog.com/static/:path*", + }, + { + source: "/_ph/:path*", + destination: "https://us.i.posthog.com/:path*", + }, + { + source: "/_ph/decide", + destination: "https://us.i.posthog.com/decide", + }, { source: "/thirdweb.eth", destination: "/deployer.thirdweb.eth", @@ -173,6 +185,8 @@ const baseNextConfig: NextConfig = { ]), ]; }, + // This is required to support PostHog trailing slash API requests + skipTrailingSlashRedirect: true, images: { dangerouslyAllowSVG: true, contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;", diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index 0b194ad1421..5e7255bf23e 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -63,7 +63,6 @@ "date-fns": "4.1.0", "fast-xml-parser": "^5.2.5", "fetch-event-stream": "0.1.5", - "flat": "^6.0.1", "framer-motion": "12.17.0", "fuse.js": "7.1.0", "input-otp": "^1.4.1", @@ -78,7 +77,8 @@ "p-limit": "^6.2.0", "papaparse": "^5.5.3", "pluralize": "^8.0.0", - "posthog-js": "1.67.1", + "posthog-js": "1.252.0", + "posthog-node": "5.1.0", "prettier": "3.5.3", "qrcode": "^1.5.3", "react": "19.1.0", diff --git a/apps/dashboard/src/@/analytics/dashboard.client.tsx b/apps/dashboard/src/@/analytics/dashboard.client.tsx new file mode 100644 index 00000000000..54d8e3ec59b --- /dev/null +++ b/apps/dashboard/src/@/analytics/dashboard.client.tsx @@ -0,0 +1,56 @@ +"use client"; + +import posthog from "posthog-js"; +import { useEffect } from "react"; +import type { Account } from "../../@3rdweb-sdk/react/hooks/useApi"; + +const warnedMessages = new Set(); +function warnOnce(message: string) { + if (warnedMessages.has(message)) { + return; + } + warnedMessages.add(message); + console.warn(message); +} + +export function AccountIdentifier(props: { + account: Pick; +}) { + // eslint-disable-next-line no-restricted-syntax + useEffect(() => { + if (!posthog.__loaded) { + warnOnce( + "[DASHBOARD_ANALYTICS] is not initialized, cannot identify user", + ); + return; + } + posthog.identify(props.account.id, { + ...(props.account.email ? { email: props.account.email } : {}), + }); + }, [props.account.id, props.account.email]); + return null; +} + +export function TeamIdentifier(props: { + teamId: string; +}) { + // eslint-disable-next-line no-restricted-syntax + useEffect(() => { + if (!posthog.__loaded) { + warnOnce( + "[DASHBOARD_ANALYTICS] is not initialized, cannot identify team", + ); + return; + } + posthog.group("team", props.teamId); + }, [props.teamId]); + return null; +} + +export function reset() { + if (!posthog.__loaded) { + warnOnce("[DASHBOARD_ANALYTICS] is not initialized, cannot reset"); + return; + } + posthog.reset(); +} diff --git a/apps/dashboard/src/@/analytics/track.ts b/apps/dashboard/src/@/analytics/track.ts new file mode 100644 index 00000000000..18f25c2fe41 --- /dev/null +++ b/apps/dashboard/src/@/analytics/track.ts @@ -0,0 +1,33 @@ +/** + * Central entry point for analytics tracking helpers. + * + * This file re-exports individual event *categories* located under + * `./track/*`. Each category file in turn exposes small helper functions that + * call PostHog with type-safe payloads. + * + * Example – reporting a contract deployment: + * ```ts + * import { reportContractDeployed } from "@/analytics/track"; + * + * reportContractDeployed({ + * address: "0x…", + * chainId: 1, + * }); + * ``` + * + * Adding a new event *category*: + * 1. Create a new file under `./track/.ts`. + * 2. Inside that file, follow the pattern shown in `contract.ts` to define + * Zod schemas + `reportWhatever` helpers. + * 3. Add a star-export below so the helpers are surfaced at the package root: + * ```ts + * export * from "./track/"; + * ``` + * 4. Consumers can now `import { reportMyEvent } from "@/analytics/track";`. + */ + +export * from "./track/contract"; +export * from "./track/token"; +export * from "./track/nft"; +export * from "./track/onboarding"; +export * from "./track/marketplace"; diff --git a/apps/dashboard/src/@/analytics/track/README.md b/apps/dashboard/src/@/analytics/track/README.md new file mode 100644 index 00000000000..f43419d01bb --- /dev/null +++ b/apps/dashboard/src/@/analytics/track/README.md @@ -0,0 +1,106 @@ +# Analytics Tracking Helpers + +This folder contains **type-safe wrappers** around our analytics provider (PostHog). +Instead of calling `posthog.capture` directly, feature code should import the +pre-defined helpers from here. This guarantees that: + +1. Event names are consistent. +2. Payloads adhere to a strict schema (validated at runtime via Zod and typed at + compile time). + +--- + +## Quick start + +```ts +import { reportContractDeployed } from "@/analytics/track"; + +// Contract deployment example +reportContractDeployed({ + address: "0x…", + chainId: 1, +}); +``` + +> **Note** Ensure that PostHog is initialised *before* you emit events. Our +> bootstrapping code does this automatically during app start-up. + +--- + +## Project structure + +``` +track/ # ← you are here +├─ __internal.ts # low-level wrapper around posthog.capture (do NOT use) +├─ contract.ts # "contract" event category helpers +├─ README.md # this file +└─ … # future categories live here +``` + +* `__internal.ts` exposes `__internal__reportEvent` which performs the actual + PostHog call and safeguards against the SDK not being ready. +* Every **category file** (such as `contract.ts`) groups together related events. + Each event is represented by a `report` function. +* `track.ts` sits one directory up and **re-exports all helper functions** so + consumers can import the ones they need directly: + + ```ts + import { reportContractDeployed /*, reportOtherEvent */ } from "@/analytics/track"; + ``` + +--- + +## Adding a **new event** to an existing category + +1. Open the relevant category file (e.g. `contract.ts`). +2. Define a new Zod schema describing the payload: + +```ts +const ContractUpgradeSchema = z.object({ + address: z.string(), + oldVersion: z.string(), + newVersion: z.string(), +}); +``` + +3. Export a reporting helper that forwards the validated payload: + +```ts +export function reportContractUpgraded( + payload: z.infer, +) { + __internal__reportEvent("contract upgraded", payload); +} +``` + +That's it – consumers can now call `track.contract.reportContractUpgraded(...)`. + +--- + +## Adding a **new category** + +1. Create a new file `track/.ts`. +2. Follow the same pattern as in `contract.ts` (define schemas + export helper + functions). +3. Open `track.ts` (one directory up) and add a star-export so the helpers are + surfaced at the package root: + + ```ts + export * from "./track/"; + ``` + + Your new helpers can now be imported directly: + + ```ts + import { reportMyNewEvent } from "@/analytics/track"; + ``` + +--- + +## Conventions & best practices + +* **Lowercase, space-separated event names** – keep them human-readable. +* **Small, focused payloads** – only include properties that are useful for + analytics. +* **Avoid calling PostHog directly** – always go through `__internal__reportEvent` + so we keep a single choke-point. \ No newline at end of file diff --git a/apps/dashboard/src/@/analytics/track/__internal.ts b/apps/dashboard/src/@/analytics/track/__internal.ts new file mode 100644 index 00000000000..7601a6bda8a --- /dev/null +++ b/apps/dashboard/src/@/analytics/track/__internal.ts @@ -0,0 +1,23 @@ +import posthog, { type Properties } from "posthog-js"; + +/** + * Low-level wrapper around `posthog.capture` with a safety-check that waits + * until PostHog has been initialised. + * + * **⚠️ INTERNAL USE ONLY** – Do not call this directly from feature code. + * Instead, create a domain-specific helper (see `contract.ts`) so we keep a + * single source of truth for event names & payload contracts. + */ + +export function __internal__reportEvent( + eventName: string, + properties?: Properties | null, +) { + if (!posthog.__loaded) { + console.warn( + "[DASHBOARD_ANALYTICS] is not initialized, cannot track event", + ); + return; + } + posthog.capture(eventName, properties); +} diff --git a/apps/dashboard/src/@/analytics/track/contract.ts b/apps/dashboard/src/@/analytics/track/contract.ts new file mode 100644 index 00000000000..8e1bbe560bf --- /dev/null +++ b/apps/dashboard/src/@/analytics/track/contract.ts @@ -0,0 +1,56 @@ +/** + * Contract-related analytics helpers. + * + * This file groups all PostHog events that belong to the "contract" domain + * (e.g. contract deployment, upgrade, verification …). Each event is a thin + * wrapper around {@link __internal__reportEvent} that: + * 1. Defines a Zod schema describing the payload that will be sent so we get + * runtime validation and static TypeScript inference for free. + * 2. Exposes a `report` function that forwards the validated + * payload to PostHog. + * + * Usage – emitting this event: + * ```ts + * import { reportContractDeployed } from "@/analytics/track"; + * + * reportContractDeployed({ + * address: "0x…", + * chainId: 1, + * }); + * ``` + * + * Adding a new event: + * 1. Add a new Zod schema that describes the event payload. + * 2. Add a `reportContract` function that takes + * `z.infer` as its single argument and calls + * `__internal__reportEvent("", payload);`. + * 3. (Optional) Document the event in the README if it should be consumed by + * other teams. + */ + +import { isAddress } from "thirdweb"; +import { z } from "zod/v4-mini"; +import { __internal__reportEvent } from "./__internal"; + +const EVMAddressSchema = z.string().check( + z.refine(isAddress, { + message: "Invalid EVM address", + }), +); + +const EVMChainIdSchema = z.coerce.number().check( + z.gte(1, { + message: "Invalid chain ID", + }), +); + +const EVMContractSchema = z.object({ + address: EVMAddressSchema, + chainId: EVMChainIdSchema, +}); + +export function reportContractDeployed( + payload: z.infer, +) { + __internal__reportEvent("contract deployed", payload); +} diff --git a/apps/dashboard/src/@/analytics/track/marketplace.ts b/apps/dashboard/src/@/analytics/track/marketplace.ts new file mode 100644 index 00000000000..50c3b48187d --- /dev/null +++ b/apps/dashboard/src/@/analytics/track/marketplace.ts @@ -0,0 +1,23 @@ +/** + * Marketplace analytics helpers. + */ + +import { z } from "zod/v4-mini"; +import { __internal__reportEvent } from "./__internal"; +import { EVMAddressSchema, EVMChainIdSchema } from "./schemas"; + +const ListingCreatedSchema = z.object({ + chainId: EVMChainIdSchema, + marketplaceAddress: EVMAddressSchema, + assetAddress: EVMAddressSchema, + assetTokenId: z.string(), +}); + +type ListingCreatedPayload = z.infer; + +export function reportListingCreated(payload: ListingCreatedPayload) { + __internal__reportEvent( + "marketplace listing created", + ListingCreatedSchema.parse(payload), + ); +} diff --git a/apps/dashboard/src/@/analytics/track/nft.ts b/apps/dashboard/src/@/analytics/track/nft.ts new file mode 100644 index 00000000000..cf9c1f0ea88 --- /dev/null +++ b/apps/dashboard/src/@/analytics/track/nft.ts @@ -0,0 +1,23 @@ +/** + * NFT-related analytics helpers (ERC721 & ERC1155 non-fungible). + */ + +import { z } from "zod/v4-mini"; +import { __internal__reportEvent } from "./__internal"; +import { EVMAddressSchema, EVMChainIdSchema } from "./schemas"; + +const BaseNFTSchema = z.object({ + address: EVMAddressSchema, + chainId: EVMChainIdSchema, +}); + +type NFTBasePayload = z.infer; + +export function reportNFTMinted( + payload: NFTBasePayload & { tokenId?: string }, +) { + __internal__reportEvent("nft minted", { + ...BaseNFTSchema.parse(payload), + tokenId: payload.tokenId, + }); +} diff --git a/apps/dashboard/src/@/analytics/track/onboarding.ts b/apps/dashboard/src/@/analytics/track/onboarding.ts new file mode 100644 index 00000000000..c5cb747469d --- /dev/null +++ b/apps/dashboard/src/@/analytics/track/onboarding.ts @@ -0,0 +1,56 @@ +import { __internal__reportEvent } from "./__internal"; + +// ------------------ +// Account onboarding +// ------------------ + +export function reportAccountEmailVerificationRequested(payload: { + email: string; +}) { + __internal__reportEvent("account email verification requested", { + email: payload.email, + }); +} + +export function reportAccountEmailVerified(payload: { email?: string }) { + __internal__reportEvent( + "account email verified", + payload.email ? { email: payload.email } : null, + ); +} + +export function reportAccountWalletLinkRequested(payload: { email: string }) { + __internal__reportEvent("account wallet link requested", { + email: payload.email, + }); +} + +export function reportAccountWalletLinked() { + __internal__reportEvent("account wallet linked", null); +} + +// ------------------ +// Team onboarding +// ------------------ + +export function reportTeamInviteMembersSent(payload: { + inviteCount: number; +}) { + __internal__reportEvent("team invite members sent", payload); +} + +export function reportTeamInviteMembersSkipped() { + __internal__reportEvent("team invite members skipped", null); +} + +// ------------------ +// Plan selection +// ------------------ + +export function reportPlanSelected(payload: { planSKU: string }) { + __internal__reportEvent("plan selected", { planSKU: payload.planSKU }); +} + +export function reportPlanSelectSkipped() { + __internal__reportEvent("plan select skipped", null); +} diff --git a/apps/dashboard/src/@/analytics/track/schemas.ts b/apps/dashboard/src/@/analytics/track/schemas.ts new file mode 100644 index 00000000000..26e851a9600 --- /dev/null +++ b/apps/dashboard/src/@/analytics/track/schemas.ts @@ -0,0 +1,20 @@ +import { isAddress } from "thirdweb"; +import { z } from "zod/v4-mini"; + +// --------------------------- +// Shared Zod Schemas +// --------------------------- + +/** Validates an EVM address (0x…) using thirdweb's `isAddress`. */ +export const EVMAddressSchema = z.string().check( + z.refine(isAddress, { + message: "Invalid EVM address", + }), +); + +/** Coerces the chain ID into a number and checks it is positive. */ +export const EVMChainIdSchema = z.coerce.number().check( + z.gte(1, { + message: "Invalid chain ID", + }), +); diff --git a/apps/dashboard/src/@/analytics/track/token.ts b/apps/dashboard/src/@/analytics/track/token.ts new file mode 100644 index 00000000000..627831621da --- /dev/null +++ b/apps/dashboard/src/@/analytics/track/token.ts @@ -0,0 +1,46 @@ +/** + * Token-related analytics events (ERC20, ERC1155 fungible, etc.). + */ + +import { z } from "zod/v4-mini"; +import { __internal__reportEvent } from "./__internal"; +import { EVMAddressSchema, EVMChainIdSchema } from "./schemas"; + +// Quantity represented as a decimal string so we don't lose precision. +const TokenAmountSchema = z.string().check( + z.refine((val) => /^\d+$/.test(val), { + message: "Quantity must be a positive integer represented as a string", + }), +); + +const BaseTokenSchema = z.object({ + address: EVMAddressSchema, + chainId: EVMChainIdSchema, + quantity: TokenAmountSchema, +}); + +type TokenMintedPayload = z.infer; + +export function reportTokenMinted(payload: TokenMintedPayload) { + __internal__reportEvent("token minted", BaseTokenSchema.parse(payload)); +} + +export function reportTokenBurned(payload: TokenMintedPayload) { + __internal__reportEvent("token burned", BaseTokenSchema.parse(payload)); +} + +const TokenTransferredSchema = z.object({ + address: EVMAddressSchema, + chainId: EVMChainIdSchema, + quantity: TokenAmountSchema, + to: EVMAddressSchema, +}); + +type TokenTransferredPayload = z.infer; + +export function reportTokenTransferred(payload: TokenTransferredPayload) { + __internal__reportEvent( + "token transferred", + TokenTransferredSchema.parse(payload), + ); +} diff --git a/apps/dashboard/src/@/components/blocks/UpsellBannerCard.tsx b/apps/dashboard/src/@/components/blocks/UpsellBannerCard.tsx index a27dd16bd63..ba68ecc8725 100644 --- a/apps/dashboard/src/@/components/blocks/UpsellBannerCard.tsx +++ b/apps/dashboard/src/@/components/blocks/UpsellBannerCard.tsx @@ -1,8 +1,8 @@ "use client"; import { Button } from "@/components/ui/button"; -import { TrackedLinkTW } from "@/components/ui/tracked-link"; import { cn } from "@/lib/utils"; +import Link from "next/link"; import type React from "react"; const ACCENT = { @@ -103,15 +103,14 @@ export function UpsellBannerCard(props: UpsellBannerCardProps) { color.btn, )} > - {props.cta.text} {props.cta.icon && {props.cta.icon}} - + diff --git a/apps/dashboard/src/@/components/blocks/pricing-card.tsx b/apps/dashboard/src/@/components/blocks/pricing-card.tsx index d00e988b700..bb8fa94a674 100644 --- a/apps/dashboard/src/@/components/blocks/pricing-card.tsx +++ b/apps/dashboard/src/@/components/blocks/pricing-card.tsx @@ -5,7 +5,6 @@ import { Button } from "@/components/ui/button"; import { ToolTipLabel } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; import { RenewSubscriptionButton } from "components/settings/Account/Billing/renew-subscription/renew-subscription-button"; -import { useTrack } from "hooks/analytics/useTrack"; import { CheckIcon, DollarSignIcon } from "lucide-react"; import Link from "next/link"; import type React from "react"; @@ -58,7 +57,6 @@ export const PricingCard: React.FC = ({ }) => { const plan = TEAM_PLANS[billingPlan]; - const trackEvent = useTrack(); const remainingTrialDays = (activeTrialEndsAt ? remainingDays(activeTrialEndsAt) : 0) || 0; @@ -70,11 +68,6 @@ export const PricingCard: React.FC = ({ const handleCTAClick = () => { cta?.onClick?.(); - trackEvent({ - category: "account", - label: `${billingPlan}Plan`, - action: "click", - }); }; return ( diff --git a/apps/dashboard/src/@/components/ui/NavLink.tsx b/apps/dashboard/src/@/components/ui/NavLink.tsx index ecde05aebb4..7d296abaa0a 100644 --- a/apps/dashboard/src/@/components/ui/NavLink.tsx +++ b/apps/dashboard/src/@/components/ui/NavLink.tsx @@ -1,7 +1,6 @@ "use client"; import { cn } from "@/lib/utils"; -import { useTrack } from "hooks/analytics/useTrack"; import Link from "next/link"; import { usePathname } from "next/navigation"; @@ -19,13 +18,14 @@ export type NavButtonProps = { }; export function NavLink(props: React.PropsWithChildren) { - const track = useTrack(); const pathname = usePathname(); const isActive = pathname ? props.exactMatch ? pathname === props.href : pathname.startsWith(props.href) : false; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const track = (..._args: unknown[]) => {}; return ( & { }; export function TrackedLinkTW(props: TrackedLinkProps) { - const trackEvent = useTrack(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const trackEvent = (..._args: unknown[]) => {}; const { category, label, trackingProps, ...restProps } = props; return ( @@ -27,7 +27,8 @@ export function TrackedLinkTW(props: TrackedLinkProps) { } export function TrackedUnderlineLink(props: TrackedLinkProps) { - const trackEvent = useTrack(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const trackEvent = (..._args: unknown[]) => {}; const { category, label, trackingProps, ...restProps } = props; return ( diff --git a/apps/dashboard/src/@3rdweb-sdk/react/components/connect-wallet/index.tsx b/apps/dashboard/src/@3rdweb-sdk/react/components/connect-wallet/index.tsx index 76b19ad283f..7f463619347 100644 --- a/apps/dashboard/src/@3rdweb-sdk/react/components/connect-wallet/index.tsx +++ b/apps/dashboard/src/@3rdweb-sdk/react/components/connect-wallet/index.tsx @@ -1,12 +1,12 @@ "use client"; +import * as analytics from "@/analytics/dashboard.client"; import { Button } from "@/components/ui/button"; import { useStore } from "@/lib/reactive"; import { getSDKTheme } from "app/(app)/components/sdk-component-theme"; import { LazyConfigureNetworkModal } from "components/configure-networks/LazyConfigureNetworkModal"; import { CustomChainRenderer } from "components/selects/CustomChainRenderer"; import { mapV4ChainToV5Chain } from "contexts/map-chains"; -import { useTrack } from "hooks/analytics/useTrack"; import { useAllChainsData } from "hooks/chains/allChains"; import { useTheme } from "next-themes"; import Image from "next/image"; @@ -156,6 +156,7 @@ export const CustomConnectWallet = (props: { onDisconnect={async () => { try { await doLogout(); + analytics.reset(); } catch (err) { console.error("Failed to log out", err); } @@ -259,18 +260,16 @@ function ConnectWalletWelcomeScreen(props: { - New to Wallets? - + ); } @@ -303,36 +302,3 @@ export function useCustomConnectModal() { [connect, theme], ); } - -/** - * A link component extends the `Link` component and adds tracking. - */ -function TrackedAnchorLink(props: { - category: string; - label?: string; - trackingProps?: Record; - href: string; - target?: string; - children: React.ReactNode; - className?: string; - style?: React.CSSProperties; -}) { - const trackEvent = useTrack(); - const { category, label, trackingProps } = props; - - const onClick = useCallback(() => { - trackEvent({ category, action: "click", label, ...trackingProps }); - }, [trackEvent, category, label, trackingProps]); - - return ( - - {props.children} - - ); -} diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/components/client/FaucetButton.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/components/client/FaucetButton.tsx index 5a0355cc569..0b0ce2fefbc 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/components/client/FaucetButton.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/components/client/FaucetButton.tsx @@ -29,7 +29,6 @@ import { Turnstile } from "@marsidev/react-turnstile"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import type { CanClaimResponseType } from "app/(app)/api/testnet-faucet/can-claim/CanClaimResponseType"; import { mapV4ChainToV5Chain } from "contexts/map-chains"; -import { useTrack } from "hooks/analytics/useTrack"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { useForm } from "react-hook-form"; @@ -95,17 +94,11 @@ export function FaucetButton({ chain: definedChain, client, }); - const trackEvent = useTrack(); + const queryClient = useQueryClient(); const claimMutation = useMutation({ mutationFn: async (turnstileToken: string) => { - trackEvent({ - category: "faucet", - action: "claim", - label: "attempt", - chain_id: chainId, - }); const response = await fetch("/api/testnet-faucet/claim", { method: "POST", headers: { @@ -123,23 +116,6 @@ export function FaucetButton({ throw new Error(data?.error || "Failed to claim funds"); } }, - onSuccess: () => { - trackEvent({ - category: "faucet", - action: "claim", - label: "success", - chain_id: chainId, - }); - }, - onError: (error) => { - trackEvent({ - category: "faucet", - action: "claim", - label: "error", - chain_id: chainId, - errorMsg: error instanceof Error ? error.message : "Unknown error", - }); - }, onSettled: () => { return queryClient.invalidateQueries({ queryKey: ["testnet-faucet-can-claim", chainId], diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/components/client/NextSteps.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/components/client/NextSteps.tsx index 36258484c28..3cb706f1b55 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/components/client/NextSteps.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/(chainPage)/components/client/NextSteps.tsx @@ -1,5 +1,5 @@ "use client"; -import { useTrack } from "hooks/analytics/useTrack"; + import { FileTextIcon } from "lucide-react"; import Link from "next/link"; import type { ChainMetadata } from "thirdweb/chains"; @@ -7,7 +7,6 @@ import { SectionTitle } from "../server/SectionTitle"; export default function NextSteps(props: { chain: ChainMetadata }) { const { chain } = props; - const trackEvent = useTrack(); return (
@@ -23,14 +22,6 @@ export default function NextSteps(props: { chain: ChainMetadata }) { } className="before:absolute before:inset-0" target="_blank" - onClick={() => - trackEvent({ - category: "nextSteps", - action: "click-inapp", - label: "success", - chain_id: chain.chainId, - }) - } > Create a login for {chain.name} 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 index af540eb8257..e732ae7caa5 100644 --- 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 @@ -1,7 +1,6 @@ "use client"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; -import { useTrack } from "hooks/analytics/useTrack"; import { defineDashboardChain } from "lib/defineDashboardChain"; import { useTheme } from "next-themes"; import type { ThirdwebClient } from "thirdweb"; @@ -14,21 +13,10 @@ export function PayModalButton(props: { client: ThirdwebClient; }) { const { theme } = useTheme(); - const trackEvent = useTrack(); return ( - @@ -47,27 +35,11 @@ export function PayModalButton(props: { info.type === "crypto" && info.status.status !== "NOT_FOUND" ) { - trackEvent({ - category: "pay", - action: "buy", - label: "success", - type: info.type, - chainId: info.status.quote.toToken.chainId, - tokenAddress: info.status.quote.toToken.tokenAddress, - amount: info.status.quote.toAmount, - }); + // success no tracking } if (info.type === "fiat" && info.status.status !== "NOT_FOUND") { - trackEvent({ - category: "pay", - action: "buy", - label: "success", - type: info.type, - chainId: info.status.quote.toToken.chainId, - tokenAddress: info.status.quote.toToken.tokenAddress, - amount: info.status.quote.estimatedToTokenAmount, - }); + // success no tracking } }, prefillBuy: { diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/components/cancel-tab.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/components/cancel-tab.tsx index 39d15bd4cda..6fdbeaa8f0a 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/components/cancel-tab.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/components/cancel-tab.tsx @@ -1,7 +1,5 @@ "use client"; import { TransactionButton } from "components/buttons/TransactionButton"; -import { useTrack } from "hooks/analytics/useTrack"; -import { useAllChainsData } from "hooks/chains/allChains"; import { toast } from "sonner"; import type { ThirdwebContract } from "thirdweb"; import { cancelAuction, cancelListing } from "thirdweb/extensions/marketplace"; @@ -20,9 +18,6 @@ export const CancelTab: React.FC = ({ isAuction, isLoggedIn, }) => { - const trackEvent = useTrack(); - const { idToChain } = useAllChainsData(); - const network = idToChain.get(contract.chain.id); const transaction = isAuction ? cancelAuction({ contract, auctionId: BigInt(id) }) : cancelListing({ contract, listingId: BigInt(id) }); @@ -36,31 +31,7 @@ export const CancelTab: React.FC = ({ transactionCount={1} isPending={cancelQuery.isPending} onClick={() => { - trackEvent({ - category: "marketplace", - action: "cancel-listing", - label: "attempt", - }); - const promise = cancelQuery.mutateAsync(transaction, { - onSuccess: () => { - trackEvent({ - category: "marketplace", - action: "cancel-listing", - label: "success", - network, - }); - }, - onError: (error) => { - trackEvent({ - category: "marketplace", - action: "cancel-listing", - label: "error", - network, - error, - }); - console.error(error); - }, - }); + const promise = cancelQuery.mutateAsync(transaction); toast.promise(promise, { loading: `Cancelling ${isAuction ? "auction" : "listing"}`, success: "Item cancelled successfully", diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/components/list-form.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/components/list-form.tsx index 9796315067b..21c102e1b2e 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/components/list-form.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/(marketplace)/components/list-form.tsx @@ -1,3 +1,4 @@ +import { reportListingCreated } from "@/analytics/track"; import { Alert, AlertTitle } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; @@ -15,8 +16,6 @@ import { import { TransactionButton } from "components/buttons/TransactionButton"; import { CurrencySelector } from "components/shared/CurrencySelector"; import { SolidityInput } from "contract-ui/components/solidity-inputs"; -import { useTrack } from "hooks/analytics/useTrack"; -import { useAllChainsData } from "hooks/chains/allChains"; import { useTxNotifications } from "hooks/useTxNotifications"; import { isAlchemySupported } from "lib/wallet/nfts/isAlchemySupported"; import { isMoralisSupported } from "lib/wallet/nfts/isMoralisSupported"; @@ -107,10 +106,7 @@ export const CreateListingsForm: React.FC = ({ mode, isInsightSupported, }) => { - const trackEvent = useTrack(); const chainId = contract.chain.id; - const { idToChain } = useAllChainsData(); - const network = idToChain.get(chainId); const [isFormLoading, setIsFormLoading] = useState(false); const isSupportedChain = @@ -368,7 +364,15 @@ export const CreateListingsForm: React.FC = ({ }); await sendAndConfirmTx.mutateAsync(transaction, { - onSuccess: () => setOpen(false), + onSuccess: () => { + reportListingCreated({ + marketplaceAddress: contract.address, + chainId: contract.chain.id, + assetAddress: _selectedContract.address, + assetTokenId: selectedTokenId.toString(), + }); + setOpen(false); + }, }); listingNotifications.onSuccess(); @@ -418,22 +422,16 @@ export const CreateListingsForm: React.FC = ({ await sendAndConfirmTx.mutateAsync(transaction, { onSuccess: () => { - trackEvent({ - category: "marketplace", - action: "add-listing", - label: "success", - network, + reportListingCreated({ + marketplaceAddress: contract.address, + chainId: contract.chain.id, + assetAddress: _selectedContract.address, + assetTokenId: selectedTokenId.toString(), }); setOpen(false); }, - onError: (error) => { - trackEvent({ - category: "marketplace", - action: "add-listing", - label: "error", - network, - error, - }); + onError: () => { + // No error handling needed for auction }, }); auctionNotifications.onSuccess(); diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/index.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/index.tsx index c5e016aeeff..75d74e4ea69 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/index.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/index.tsx @@ -17,7 +17,6 @@ import { MenuList, } from "@chakra-ui/react"; import { TransactionButton } from "components/buttons/TransactionButton"; -import { useTrack } from "hooks/analytics/useTrack"; import { useTxNotifications } from "hooks/useTxNotifications"; import { CircleHelpIcon, PlusIcon } from "lucide-react"; import { Fragment, createContext, useContext, useMemo, useState } from "react"; @@ -204,7 +203,6 @@ export const ClaimConditionsForm: React.FC = ({ // if neither 1155 or 20 then it's 721 const isErc721 = !isErc20 && !isErc1155; const walletAddress = useActiveAccount()?.address; - const trackEvent = useTrack(); const isAdmin = useIsAdmin(contract); const [openSnapshotIndex, setOpenSnapshotIndex] = useState(-1); @@ -359,13 +357,6 @@ export const ClaimConditionsForm: React.FC = ({ }); const handleFormSubmit = form.handleSubmit(async (d) => { - const category = isErc20 ? "token" : "nft"; - - trackEvent({ - category, - action: "set-claim-conditions", - label: "attempt", - }); if (isErc20 && !tokenDecimals.data) { return toast.error( `Could not fetch token decimals for contract ${contract.address}`, @@ -385,11 +376,7 @@ export const ClaimConditionsForm: React.FC = ({ d.phases, ); await sendTx.mutateAsync(tx); - trackEvent({ - category, - action: "set-claim-conditions", - label: "success", - }); + saveClaimPhaseNotification.onSuccess(); const newPhases = d.phases.map((phase) => ({ @@ -401,11 +388,7 @@ export const ClaimConditionsForm: React.FC = ({ form.setValue("phases", newPhases); } catch (error) { console.error(error); - trackEvent({ - category, - action: "set-claim-conditions", - label: "error", - }); + if (error instanceof ZodError) { // biome-ignore lint/complexity/noForEach: FIXME error.errors.forEach((e) => { diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/reset-claim-eligibility.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/reset-claim-eligibility.tsx index 4948c16aff8..99ec0e80978 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/reset-claim-eligibility.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/reset-claim-eligibility.tsx @@ -3,7 +3,6 @@ import { ToolTipLabel } from "@/components/ui/tooltip"; import { AdminOnly } from "@3rdweb-sdk/react/components/roles/admin-only"; import { TransactionButton } from "components/buttons/TransactionButton"; -import { useTrack } from "hooks/analytics/useTrack"; import { useTxNotifications } from "hooks/useTxNotifications"; import { CircleHelpIcon } from "lucide-react"; import type { ThirdwebContract } from "thirdweb"; @@ -27,8 +26,6 @@ export const ResetClaimEligibility: React.FC = ({ isLoggedIn, isMultiphase, }) => { - const trackEvent = useTrack(); - const sendTxMutation = useSendAndConfirmTransaction(); const txNotification = useTxNotifications( @@ -37,14 +34,6 @@ export const ResetClaimEligibility: React.FC = ({ ); const handleResetClaimEligibility = () => { - const category = isErc20 ? "token" : "nft"; - - trackEvent({ - category, - action: "reset-claim-conditions", - label: "attempt", - }); - const tx = (() => { switch (true) { // erc 20 @@ -69,20 +58,9 @@ export const ResetClaimEligibility: React.FC = ({ sendTxMutation.mutate(tx, { onSuccess: () => { txNotification.onSuccess(); - trackEvent({ - category, - action: "reset-claim-conditions", - label: "success", - }); }, onError: (error) => { txNotification.onError(error); - trackEvent({ - category, - action: "reset-claim-conditions", - label: "error", - error, - }); }, }); }; diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_layout/primary-dashboard-button.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_layout/primary-dashboard-button.tsx index 4da649b8f1d..ac405fbed90 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_layout/primary-dashboard-button.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_layout/primary-dashboard-button.tsx @@ -17,7 +17,6 @@ import { AddToProjectSelector, type MinimalTeamsAndProjects, } from "components/contract-components/contract-deploy-form/add-to-project-card"; -import { useTrack } from "hooks/analytics/useTrack"; import { CodeIcon, PlusIcon } from "lucide-react"; import { CircleAlertIcon, ExternalLinkIcon } from "lucide-react"; import Link from "next/link"; @@ -29,8 +28,6 @@ import { useAddContractToProject } from "../../../../../team/[team_slug]/[projec import type { ProjectMeta } from "../../../../../team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/types"; import { buildContractPagePath } from "../_utils/contract-page-path"; -const TRACKING_CATEGORY = "add_to_dashboard_upsell"; - type AddToDashboardCardProps = { contractAddress: string; chain: Chain; @@ -143,7 +140,6 @@ function AddToProjectModalContent(props: { contractAddress: string; client: ThirdwebClient; }) { - const trackEvent = useTrack(); const addContractToProject = useAddContractToProject(); const [importSelection, setImportSelection] = useState({ @@ -159,12 +155,6 @@ function AddToProjectModalContent(props: { teamId: string; projectId: string; }) { - trackEvent({ - category: TRACKING_CATEGORY, - action: "add-to-dashboard", - label: "attempt", - contractAddress: props.contractAddress, - }); addContractToProject.mutate( { contractAddress: props.contractAddress, @@ -177,12 +167,6 @@ function AddToProjectModalContent(props: { { onSuccess: () => { toast.success("Contract added to the project successfully"); - trackEvent({ - category: TRACKING_CATEGORY, - action: "add-to-dashboard", - label: "success", - contractAddress: props.contractAddress, - }); }, onError: (err) => { if (err.message.includes("PROJECT_CONTRACT_ALREADY_EXISTS")) { @@ -190,13 +174,6 @@ function AddToProjectModalContent(props: { } else { toast.error("Failed to import contract"); } - trackEvent({ - category: TRACKING_CATEGORY, - action: "add-to-dashboard", - label: "error", - contractAddress: props.contractAddress, - error: err, - }); }, }, ); diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/airdrop-tab.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/airdrop-tab.tsx index 0cb64ac9b3d..94efb5b4a33 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/airdrop-tab.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/airdrop-tab.tsx @@ -10,7 +10,6 @@ import { } from "@/components/ui/sheet"; import { cn } from "@/lib/utils"; import { TransactionButton } from "components/buttons/TransactionButton"; -import { useTrack } from "hooks/analytics/useTrack"; import { useTxNotifications } from "hooks/useTxNotifications"; import { UploadIcon } from "lucide-react"; import { useState } from "react"; @@ -45,7 +44,7 @@ const AirdropTab: React.FC = ({ }>({ defaultValues: { addresses: [] }, }); - const trackEvent = useTrack(); + const sendAndConfirmTx = useSendAndConfirmTransaction(); const addresses = watch("addresses"); const [open, setOpen] = useState(false); @@ -59,13 +58,6 @@ const AirdropTab: React.FC = ({
{ try { - trackEvent({ - category: "nft", - action: "airdrop", - label: "attempt", - contract_address: contract.address, - token_id: tokenId, - }); const totalOwned = await balanceOf({ contract, tokenId: BigInt(tokenId), @@ -92,24 +84,10 @@ const AirdropTab: React.FC = ({ const transaction = multicall({ contract, data }); await sendAndConfirmTx.mutateAsync(transaction, { onSuccess: () => { - trackEvent({ - category: "nft", - action: "airdrop", - label: "success", - contract_address: contract.address, - token_id: tokenId, - }); reset(); }, onError: (error) => { - trackEvent({ - category: "nft", - action: "airdrop", - label: "success", - contract_address: contract.address, - token_id: tokenId, - error, - }); + console.error(error); }, }); diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/burn-tab.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/burn-tab.tsx index 515c88710bc..54d2d005566 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/burn-tab.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/burn-tab.tsx @@ -2,7 +2,6 @@ import { FormControl, Input } from "@chakra-ui/react"; import { TransactionButton } from "components/buttons/TransactionButton"; -import { useTrack } from "hooks/analytics/useTrack"; import { useTxNotifications } from "hooks/useTxNotifications"; import { useForm } from "react-hook-form"; import type { ThirdwebContract } from "thirdweb"; @@ -29,7 +28,6 @@ interface BurnTabProps { const BurnTab: React.FC = ({ contract, tokenId, isLoggedIn }) => { const account = useActiveAccount(); - const trackEvent = useTrack(); const { register, handleSubmit, @@ -58,11 +56,6 @@ const BurnTab: React.FC = ({ contract, tokenId, isLoggedIn }) => {
{ - trackEvent({ - category: "nft", - action: "burn", - label: "attempt", - }); const transaction = isErc721 ? burn721({ contract, tokenId: BigInt(tokenId) }) : burn1155({ @@ -73,11 +66,6 @@ const BurnTab: React.FC = ({ contract, tokenId, isLoggedIn }) => { }); mutate(transaction, { onSuccess: () => { - trackEvent({ - category: "nft", - action: "burn", - label: "success", - }); onSuccess(); if (contract) { invalidateContractQuery({ @@ -88,12 +76,7 @@ const BurnTab: React.FC = ({ contract, tokenId, isLoggedIn }) => { reset(); }, onError: (error) => { - trackEvent({ - category: "nft", - action: "burn", - label: "error", - error, - }); + console.error(error); onError(error); }, }); diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/claim-tab.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/claim-tab.tsx index db79fcd8e42..7894ca52c44 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/claim-tab.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/claim-tab.tsx @@ -2,7 +2,6 @@ import { Flex, FormControl, Input } from "@chakra-ui/react"; import { TransactionButton } from "components/buttons/TransactionButton"; -import { useTrack } from "hooks/analytics/useTrack"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { type ThirdwebContract, ZERO_ADDRESS } from "thirdweb"; @@ -23,7 +22,6 @@ const ClaimTabERC1155: React.FC = ({ tokenId, isLoggedIn, }) => { - const trackEvent = useTrack(); const address = useActiveAccount()?.address; const form = useForm<{ to: string; amount: string }>({ defaultValues: { amount: "1", to: address }, @@ -36,11 +34,6 @@ const ClaimTabERC1155: React.FC = ({ direction="column" as="form" onSubmit={form.handleSubmit(async (data) => { - trackEvent({ - category: "nft", - action: "claim", - label: "attempt", - }); if (!account) { return toast.error("No account detected"); } @@ -75,20 +68,9 @@ const ClaimTabERC1155: React.FC = ({ }; }, }); - trackEvent({ - category: "nft", - action: "claim", - label: "success", - }); form.reset(); } catch (error) { console.error(error); - trackEvent({ - category: "nft", - action: "claim", - label: "error", - error, - }); } })} > diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/mint-supply-tab.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/mint-supply-tab.tsx index 6829cdf39df..c35a7254fc9 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/mint-supply-tab.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/mint-supply-tab.tsx @@ -12,7 +12,6 @@ import { import { Input } from "@/components/ui/input"; import { zodResolver } from "@hookform/resolvers/zod"; import { TransactionButton } from "components/buttons/TransactionButton"; -import { useTrack } from "hooks/analytics/useTrack"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { type ThirdwebContract, isAddress } from "thirdweb"; @@ -38,7 +37,6 @@ const MintSupplyTab: React.FC = ({ tokenId, isLoggedIn, }) => { - const trackEvent = useTrack(); const address = useActiveAccount()?.address; const form = useForm>({ @@ -52,11 +50,6 @@ const MintSupplyTab: React.FC = ({ const sendAndConfirmTx = useSendAndConfirmTransaction(); function onSubmit(values: z.input) { - trackEvent({ - category: "nft", - action: "mint-supply", - label: "attempt", - }); const transaction = mintAdditionalSupplyTo({ contract, to: values.to, @@ -65,20 +58,10 @@ const MintSupplyTab: React.FC = ({ }); const promise = sendAndConfirmTx.mutateAsync(transaction, { onSuccess: () => { - trackEvent({ - category: "nft", - action: "mint-supply", - label: "success", - }); form.reset(); }, onError: (error) => { - trackEvent({ - category: "nft", - action: "mint-supply", - label: "error", - error, - }); + console.error(error); }, }); toast.promise(promise, { diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/transfer-tab.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/transfer-tab.tsx index 51225b0f317..a3114290817 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/transfer-tab.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/transfer-tab.tsx @@ -4,7 +4,6 @@ import { GenericLoadingPage } from "@/components/blocks/skeletons/GenericLoading import { FormControl, Input } from "@chakra-ui/react"; import { TransactionButton } from "components/buttons/TransactionButton"; import { SolidityInput } from "contract-ui/components/solidity-inputs"; -import { useTrack } from "hooks/analytics/useTrack"; import { useTxNotifications } from "hooks/useTxNotifications"; import { useForm } from "react-hook-form"; import { type ThirdwebContract, ZERO_ADDRESS } from "thirdweb"; @@ -30,7 +29,6 @@ const TransferTab: React.FC = ({ }) => { const account = useActiveAccount(); - const trackEvent = useTrack(); const form = useForm<{ to: string; amount: string }>({ defaultValues: { to: "", amount: "1" }, }); @@ -56,11 +54,6 @@ const TransferTab: React.FC = ({
{ - trackEvent({ - category: "nft", - action: "transfer", - label: "attempt", - }); const transaction = isErc1155 ? safeTransferFrom({ contract, @@ -78,21 +71,11 @@ const TransferTab: React.FC = ({ }); sendTxAndConfirm.mutate(transaction, { onSuccess: () => { - trackEvent({ - category: "nft", - action: "transfer", - label: "success", - }); onSuccess(); form.reset(); }, onError: (error) => { - trackEvent({ - category: "nft", - action: "transfer", - label: "error", - error, - }); + console.error(error); onError(error); }, }); diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/update-metadata-form.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/update-metadata-form.tsx index 2c8ca2b5d3c..7f8e0c2b160 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/update-metadata-form.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/components/update-metadata-form.tsx @@ -14,7 +14,6 @@ import { OpenSeaPropertyBadge } from "components/badges/opensea"; import { TransactionButton } from "components/buttons/TransactionButton"; import { PropertiesFormControl } from "components/contract-pages/forms/properties.shared"; import { FileInput } from "components/shared/FileInput"; -import { useTrack } from "hooks/analytics/useTrack"; import { useTxNotifications } from "hooks/useTxNotifications"; import { type Dispatch, type SetStateAction, useMemo } from "react"; import { useForm } from "react-hook-form"; @@ -65,7 +64,6 @@ export const UpdateNftMetadata: React.FC = ({ setOpen, isLoggedIn, }) => { - const trackEvent = useTrack(); const address = useActiveAccount()?.address; const transformedQueryData = useMemo(() => { @@ -126,11 +124,6 @@ export const UpdateNftMetadata: React.FC = ({ toast.error("Please connect your wallet to update metadata."); return; } - trackEvent({ - category: "nft", - action: "update-metadata", - label: "attempt", - }); try { const newMetadata = parseAttributes({ @@ -166,22 +159,8 @@ export const UpdateNftMetadata: React.FC = ({ }); await sendAndConfirmTx.mutateAsync(transaction, { onSuccess: () => { - trackEvent({ - category: "nft", - action: "update-metadata", - label: "success", - }); setOpen(false); }, - // biome-ignore lint/suspicious/noExplicitAny: FIXME - onError: (error: any) => { - trackEvent({ - category: "nft", - action: "update-metadata", - label: "error", - error, - }); - }, }); updateMetadataNotifications.onSuccess(); diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/batch-lazy-mint-button.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/batch-lazy-mint-button.tsx index d672316d0d1..2875c12ebac 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/batch-lazy-mint-button.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/batch-lazy-mint-button.tsx @@ -10,7 +10,6 @@ import { } from "@/components/ui/sheet"; import { MinterOnly } from "@3rdweb-sdk/react/components/roles/minter-only"; import { BatchLazyMint } from "core-ui/batch-upload/batch-lazy-mint"; -import { useTrack } from "hooks/analytics/useTrack"; import { useTxNotifications } from "hooks/useTxNotifications"; import { FileStackIcon } from "lucide-react"; import { useState } from "react"; @@ -32,7 +31,6 @@ export const BatchLazyMintButton: React.FC = ({ isErc721, isLoggedIn, }) => { - const trackEvent = useTrack(); const [open, setOpen] = useState(false); const nextTokenIdToMintQuery = useReadContract( @@ -71,13 +69,6 @@ export const BatchLazyMintButton: React.FC = ({ client={contract.client} chainId={contract.chain.id} onSubmit={async ({ revealType, data }) => { - // nice, we can set up everything the same for both the only thing that changes is the action string - const action = `batch-upload-${revealType}` as const; - trackEvent({ - category: "nft", - action, - label: "attempt", - }); try { const tx = (() => { switch (true) { @@ -112,20 +103,10 @@ export const BatchLazyMintButton: React.FC = ({ await sendTxMutation.mutateAsync(tx); - trackEvent({ - category: "nft", - action, - label: "success", - }); txNotifications.onSuccess(); setOpen(false); } catch (error) { - trackEvent({ - category: "nft", - action, - label: "error", - error, - }); + console.error(error); txNotifications.onError(error); } }} diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/claim-button.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/claim-button.tsx index 23b1d19b2be..1dc23ce2bdd 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/claim-button.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/claim-button.tsx @@ -10,7 +10,6 @@ import { } from "@/components/ui/sheet"; import { FormControl, Input } from "@chakra-ui/react"; import { TransactionButton } from "components/buttons/TransactionButton"; -import { useTrack } from "hooks/analytics/useTrack"; import { useTxNotifications } from "hooks/useTxNotifications"; import { GemIcon } from "lucide-react"; import { useState } from "react"; @@ -37,7 +36,6 @@ export const NFTClaimButton: React.FC = ({ contract, isLoggedIn, }) => { - const trackEvent = useTrack(); const address = useActiveAccount()?.address; const { register, handleSubmit, formState, setValue } = useForm({ defaultValues: { amount: "1", to: address }, @@ -116,11 +114,6 @@ export const NFTClaimButton: React.FC = ({ type="submit" onClick={handleSubmit(async (d) => { try { - trackEvent({ - category: "nft", - action: "claim", - label: "attempt", - }); if (!account) { return toast.error("No account detected"); } @@ -159,20 +152,10 @@ export const NFTClaimButton: React.FC = ({ await sendAndConfirmTx.mutateAsync(transaction, { onSuccess: () => { - trackEvent({ - category: "nft", - action: "claim", - label: "success", - }); setOpen(false); }, onError: (error) => { - trackEvent({ - category: "nft", - action: "claim", - label: "error", - error, - }); + console.error(error); }, }); @@ -180,12 +163,6 @@ export const NFTClaimButton: React.FC = ({ } catch (error) { console.error(error); claimNFTNotifications.onError(error); - trackEvent({ - category: "nft", - action: "claim", - label: "error", - error, - }); } })} > diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/lazy-mint-form.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/lazy-mint-form.tsx index 851efebf5e3..479f8e7762a 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/lazy-mint-form.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/lazy-mint-form.tsx @@ -16,7 +16,6 @@ import { OpenSeaPropertyBadge } from "components/badges/opensea"; import { TransactionButton } from "components/buttons/TransactionButton"; import { PropertiesFormControl } from "components/contract-pages/forms/properties.shared"; import { FileInput } from "components/shared/FileInput"; -import { useTrack } from "hooks/analytics/useTrack"; import { useTxNotifications } from "hooks/useTxNotifications"; import type { Dispatch, SetStateAction } from "react"; import { useForm } from "react-hook-form"; @@ -53,7 +52,6 @@ export const LazyMintNftForm: React.FC = ({ setOpen, isLoggedIn, }) => { - const trackEvent = useTrack(); const address = useActiveAccount()?.address; const sendAndConfirmTx = useSendAndConfirmTransaction(); @@ -97,11 +95,6 @@ export const LazyMintNftForm: React.FC = ({ return; } try { - trackEvent({ - category: "nft", - action: "lazy-mint", - label: "attempt", - }); const nfts = [parseAttributes(data)]; const transaction = isErc721 ? lazyMint721({ contract, nfts }) @@ -109,20 +102,10 @@ export const LazyMintNftForm: React.FC = ({ await sendAndConfirmTx.mutateAsync(transaction, { onSuccess: () => { - trackEvent({ - category: "nft", - action: "lazy-mint", - label: "success", - }); setOpen(false); }, onError: (error) => { - trackEvent({ - category: "nft", - action: "lazy-mint", - label: "error", - error, - }); + console.error(error); }, }); diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/mint-form.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/mint-form.tsx index 67fce1df341..2f5ec4dda50 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/mint-form.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/mint-form.tsx @@ -1,5 +1,6 @@ "use client"; +import { reportNFTMinted } from "@/analytics/track"; import { Accordion, AccordionButton, @@ -15,7 +16,6 @@ import { OpenSeaPropertyBadge } from "components/badges/opensea"; import { TransactionButton } from "components/buttons/TransactionButton"; import { PropertiesFormControl } from "components/contract-pages/forms/properties.shared"; import { FileInput } from "components/shared/FileInput"; -import { useTrack } from "hooks/analytics/useTrack"; import { useTxNotifications } from "hooks/useTxNotifications"; import type { Dispatch, SetStateAction } from "react"; import { useForm } from "react-hook-form"; @@ -53,7 +53,6 @@ export const NFTMintForm: React.FC = ({ setOpen, isLoggedIn, }) => { - const trackEvent = useTrack(); const address = useActiveAccount()?.address; const form = useForm< NFTMetadataInputLimited & { @@ -107,11 +106,6 @@ export const NFTMintForm: React.FC = ({ animation_url: data.animation_url, }; - trackEvent({ - category: "nft", - action: "mint", - label: "attempt", - }); const nft = parseAttributes(dataWithCustom); const transaction = isErc721 ? erc721MintTo({ contract, to: address, nft }) @@ -123,22 +117,13 @@ export const NFTMintForm: React.FC = ({ }); await sendAndConfirmTx.mutateAsync(transaction, { onSuccess: () => { - trackEvent({ - category: "nft", - action: "mint", - label: "success", + reportNFTMinted({ + address: contract.address, + chainId: contract.chain.id, + tokenId: undefined, }); setOpen(false); }, - // biome-ignore lint/suspicious/noExplicitAny: FIXME - onError: (error: any) => { - trackEvent({ - category: "nft", - action: "mint", - label: "error", - error, - }); - }, }); nftMintNotifications.onSuccess(); diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/reveal-button.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/reveal-button.tsx index 03f7213a6e9..afd6f62fede 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/reveal-button.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/reveal-button.tsx @@ -12,7 +12,6 @@ import { ToolTipLabel } from "@/components/ui/tooltip"; import { MinterOnly } from "@3rdweb-sdk/react/components/roles/minter-only"; import { FormControl, Input, Select } from "@chakra-ui/react"; import { TransactionButton } from "components/buttons/TransactionButton"; -import { useTrack } from "hooks/analytics/useTrack"; import { EyeIcon } from "lucide-react"; import { useState } from "react"; import { useForm } from "react-hook-form"; @@ -36,7 +35,6 @@ export const NFTRevealButton: React.FC = ({ const batchesQuery = useReadContract(getBatchesToReveal, { contract, }); - const trackEvent = useTrack(); const sendTxMutation = useSendAndConfirmTransaction(); @@ -85,12 +83,6 @@ export const NFTRevealButton: React.FC = ({ className="mt-10 flex flex-col gap-6" id={REVEAL_FORM_ID} onSubmit={handleSubmit((data) => { - trackEvent({ - category: "nft", - action: "batch-upload-reveal", - label: "attempt", - }); - const tx = reveal({ contract, batchId: BigInt(data.batchId), @@ -99,20 +91,10 @@ export const NFTRevealButton: React.FC = ({ const promise = sendTxMutation.mutateAsync(tx, { onSuccess: () => { - trackEvent({ - category: "nft", - action: "batch-upload-reveal", - label: "success", - }); setOpen(false); }, onError: (error) => { console.error(error); - trackEvent({ - category: "nft", - action: "batch-upload-reveal", - label: "error", - }); }, }); diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/shared-metadata-form.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/shared-metadata-form.tsx index 88cc8f4e313..ef244734728 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/shared-metadata-form.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/shared-metadata-form.tsx @@ -13,7 +13,6 @@ import { } from "@chakra-ui/react"; import { TransactionButton } from "components/buttons/TransactionButton"; import { FileInput } from "components/shared/FileInput"; -import { useTrack } from "hooks/analytics/useTrack"; import { useTxNotifications } from "hooks/useTxNotifications"; import type { Dispatch, SetStateAction } from "react"; import { useForm } from "react-hook-form"; @@ -42,7 +41,6 @@ export const SharedMetadataForm: React.FC<{ setOpen: Dispatch>; isLoggedIn: boolean; }> = ({ contract, setOpen, isLoggedIn }) => { - const trackEvent = useTrack(); const address = useActiveAccount()?.address; const sendAndConfirmTx = useSendAndConfirmTransaction(); const form = useForm(); @@ -82,11 +80,6 @@ export const SharedMetadataForm: React.FC<{ animation_url: data.animation_url, }; - trackEvent({ - category: "nft", - action: "set-shared-metadata", - label: "attempt", - }); try { const transaction = setSharedMetadata({ contract, @@ -94,21 +87,10 @@ export const SharedMetadataForm: React.FC<{ }); await sendAndConfirmTx.mutateAsync(transaction, { onSuccess: () => { - trackEvent({ - category: "nft", - action: "set-shared-metadata", - label: "success", - }); setOpen(false); }, - // biome-ignore lint/suspicious/noExplicitAny: FIXME - onError: (error: any) => { - trackEvent({ - category: "nft", - action: "set-shared-metadata", - label: "error", - error, - }); + onError: (error) => { + console.error(error); }, }); diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/overview/components/Analytics.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/overview/components/Analytics.tsx index c20968c3c9c..5d0279ad886 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/overview/components/Analytics.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/overview/components/Analytics.tsx @@ -8,7 +8,6 @@ import { useContractUniqueWalletAnalytics, } from "data/analytics/hooks"; import { differenceInCalendarDays, format } from "date-fns"; -import { useTrack } from "hooks/analytics/useTrack"; import { ArrowRightIcon } from "lucide-react"; import Link from "next/link"; import { useMemo, useState } from "react"; @@ -32,7 +31,6 @@ export function ContractAnalyticsOverviewCard(props: { chainSlug: string; projectMeta: ProjectMeta | undefined; }) { - const trackEvent = useTrack(); const [startDate] = useState( (() => { const date = new Date(); @@ -84,13 +82,6 @@ export function ContractAnalyticsOverviewCard(props: { className="gap-2 bg-background text-muted-foreground" size="sm" variant="outline" - onClick={() => { - trackEvent({ - category: props.trackingCategory, - action: "click", - label: "view_all_analytics", - }); - }} > View All diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/permissions/components/index.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/permissions/components/index.tsx index 9bbd1d211cd..61c7920a1da 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/permissions/components/index.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/permissions/components/index.tsx @@ -7,7 +7,6 @@ import { createSetAllRoleMembersTx, getAllRoleMembers, } from "contract-ui/hooks/permissions"; -import { useTrack } from "hooks/analytics/useTrack"; import { useTxNotifications } from "hooks/useTxNotifications"; import { useMemo } from "react"; import { FormProvider, useForm } from "react-hook-form"; @@ -31,7 +30,6 @@ export function Permissions({ contract: ThirdwebContract; isLoggedIn: boolean; }) { - const trackEvent = useTrack(); const account = useActiveAccount(); const allRoleMembers = useReadContract(getAllRoleMembers, { contract, @@ -70,11 +68,6 @@ export function Permissions({ onError(new Error("Wallet not connected!")); return; } - trackEvent({ - category: "permissions", - action: "set-permissions", - label: "attempt", - }); const tx = createSetAllRoleMembersTx({ account, contract, @@ -82,21 +75,11 @@ export function Permissions({ }); sendTx.mutate(tx, { onSuccess: () => { - trackEvent({ - category: "permissions", - action: "set-permissions", - label: "success", - }); form.reset(d); onSuccess(); }, onError: (error) => { - trackEvent({ - category: "permissions", - action: "set-permissions", - label: "error", - error, - }); + console.error(error); onError(error); }, }); diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/proposals/components/delegate-button.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/proposals/components/delegate-button.tsx index 748f9edc537..e245d7b70d0 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/proposals/components/delegate-button.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/proposals/components/delegate-button.tsx @@ -6,7 +6,6 @@ import { useDelegateMutation, } from "@3rdweb-sdk/react/hooks/useVote"; import { TransactionButton } from "components/buttons/TransactionButton"; -import { useTrack } from "hooks/analytics/useTrack"; import { toast } from "sonner"; import type { ThirdwebContract } from "thirdweb"; import { useActiveAccount, useReadContract } from "thirdweb/react"; @@ -20,7 +19,6 @@ export const DelegateButton: React.FC = ({ contract, isLoggedIn, }) => { - const trackEvent = useTrack(); const account = useActiveAccount(); const tokensDelegatedQuery = useReadContract(tokensDelegated, { contract, @@ -45,20 +43,8 @@ export const DelegateButton: React.FC = ({ onClick={() => { toast.promise( delegateMutation.mutateAsync(contract, { - onSuccess: () => { - trackEvent({ - category: "vote", - action: "delegate", - label: "success", - }); - }, onError: (error) => { - trackEvent({ - category: "vote", - action: "delegate", - label: "error", - error, - }); + console.error(error); }, }), { diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/proposals/components/proposal-button.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/proposals/components/proposal-button.tsx index 79948ad4e19..ac44ae64dd4 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/proposals/components/proposal-button.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/proposals/components/proposal-button.tsx @@ -9,7 +9,6 @@ import { } from "@/components/ui/sheet"; import { FormControl, Textarea } from "@chakra-ui/react"; import { TransactionButton } from "components/buttons/TransactionButton"; -import { useTrack } from "hooks/analytics/useTrack"; import { PlusIcon } from "lucide-react"; import { useState } from "react"; import { useForm } from "react-hook-form"; @@ -37,7 +36,6 @@ export const ProposalButton: React.FC = ({ handleSubmit, formState: { errors }, } = useForm<{ description: string }>(); - const trackEvent = useTrack(); return ( @@ -66,21 +64,10 @@ export const ProposalButton: React.FC = ({ toast.promise( sendTx.mutateAsync(tx, { onSuccess: () => { - trackEvent({ - category: "vote", - action: "create-proposal", - label: "success", - }); setOpen(false); }, onError: (error) => { console.error(error); - trackEvent({ - category: "vote", - action: "create-proposal", - label: "error", - error, - }); }, }), { diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/_components/claim-tokens/claim-tokens-ui.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/_components/claim-tokens/claim-tokens-ui.tsx index 52e18003b4c..606cc6f0ab4 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/_components/claim-tokens/claim-tokens-ui.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/_components/claim-tokens/claim-tokens-ui.tsx @@ -8,7 +8,6 @@ import { SkeletonContainer } from "@/components/ui/skeleton"; import { cn } from "@/lib/utils"; import { useMutation, useQuery } from "@tanstack/react-query"; import { TransactionButton } from "components/buttons/TransactionButton"; -import { useTrack } from "hooks/analytics/useTrack"; import { CheckIcon, CircleAlertIcon, @@ -34,11 +33,7 @@ import { type getActiveClaimCondition, getApprovalForTransaction, } from "thirdweb/extensions/erc20"; -import { - useActiveAccount, - useActiveWallet, - useSendTransaction, -} from "thirdweb/react"; +import { useActiveAccount, useSendTransaction } from "thirdweb/react"; import { getClaimParams, maxUint256 } from "thirdweb/utils"; import { tryCatch } from "utils/try-catch"; import { ToolTipLabel } from "../../../../../../../../../../@/components/ui/tooltip"; @@ -69,9 +64,8 @@ export function ClaimTokenCardUI(props: { }) { const [quantity, setQuantity] = useState("1"); const account = useActiveAccount(); - const activeWallet = useActiveWallet(); + const { theme } = useTheme(); - const trackEvent = useTrack(); const sendClaimTx = useSendTransaction({ payModal: { theme: getSDKTheme(theme === "light" ? "light" : "dark"), @@ -84,32 +78,6 @@ export function ClaimTokenCardUI(props: { } >(undefined); - function trackAssetBuy( - params: - | { - type: "attempt" | "success"; - } - | { - type: "error"; - errorMessage: string; - }, - ) { - trackEvent({ - category: "asset", - action: "buy", - label: params.type, - contractType: "DropERC20", - accountAddress: account?.address, - walletId: activeWallet?.id, - chainId: props.contract.chain.id, - ...(params.type === "error" - ? { - errorMessage: params.errorMessage, - } - : {}), - }); - } - const [stepsUI, setStepsUI] = useState< | undefined | { @@ -125,10 +93,6 @@ export function ClaimTokenCardUI(props: { return; } - trackAssetBuy({ - type: "attempt", - }); - setStepsUI(undefined); const transaction = claimTo({ @@ -162,11 +126,6 @@ export function ClaimTokenCardUI(props: { claim: "idle", }); - trackAssetBuy({ - type: "error", - errorMessage: approveTxResult.error.message, - }); - console.error(approveTxResult.error); toast.error("Failed to approve spending", { description: approveTxResult.error.message, @@ -197,11 +156,6 @@ export function ClaimTokenCardUI(props: { claim: "error", }); - trackAssetBuy({ - type: "error", - errorMessage: claimTxResult.error.message, - }); - console.error(claimTxResult.error); toast.error("Failed to buy tokens", { description: claimTxResult.error.message, @@ -214,10 +168,6 @@ export function ClaimTokenCardUI(props: { claim: "success", }); - trackAssetBuy({ - type: "success", - }); - setSuccessScreen({ txHash: claimTxResult.data.transactionHash, }); diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/metadata.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/metadata.tsx index eb452f6a9d8..f860c010b3c 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/metadata.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/metadata.tsx @@ -13,7 +13,6 @@ import type { ExtensionDetectedState } from "components/buttons/ExtensionDetecte import { TransactionButton } from "components/buttons/TransactionButton"; import { FileInput } from "components/shared/FileInput"; import { CommonContractSchema } from "constants/schemas"; -import { useTrack } from "hooks/analytics/useTrack"; import { useTxNotifications } from "hooks/useTxNotifications"; import { PlusIcon, Trash2Icon } from "lucide-react"; import { useMemo } from "react"; @@ -70,7 +69,6 @@ export const SettingsMetadata = ({ detectedState: ExtensionDetectedState; isLoggedIn: boolean; }) => { - const trackEvent = useTrack(); const metadata = useReadContract(getContractMetadata, { contract }); const sendTransaction = useSendAndConfirmTransaction(); @@ -167,12 +165,6 @@ export const SettingsMetadata = ({ {}, ); - trackEvent({ - category: "settings", - action: "set-metadata", - label: "attempt", - }); - const tx = setContractMetadata({ contract, ...data, @@ -181,20 +173,10 @@ export const SettingsMetadata = ({ sendTransaction.mutate(tx, { onSuccess: () => { - trackEvent({ - category: "settings", - action: "set-metadata", - label: "success", - }); onSuccess(); }, onError: (error) => { - trackEvent({ - category: "settings", - action: "set-metadata", - label: "error", - error, - }); + console.error(error); onError(error); }, }); diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/platform-fees.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/platform-fees.tsx index 60cf9102e7d..b820b96154a 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/platform-fees.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/platform-fees.tsx @@ -7,7 +7,6 @@ import { TransactionButton } from "components/buttons/TransactionButton"; import { BasisPointsInput } from "components/inputs/BasisPointsInput"; import { AddressOrEnsSchema, BasisPointsSchema } from "constants/schemas"; import { SolidityInput } from "contract-ui/components/solidity-inputs"; -import { useTrack } from "hooks/analytics/useTrack"; import { useTxNotifications } from "hooks/useTxNotifications"; import { useForm } from "react-hook-form"; import type { ThirdwebContract } from "thirdweb"; @@ -51,7 +50,6 @@ export const SettingsPlatformFees = ({ detectedState: ExtensionDetectedState; isLoggedIn: boolean; }) => { - const trackEvent = useTrack(); const address = useActiveAccount()?.address; const sendAndConfirmTx = useSendAndConfirmTransaction(); const platformFeesQuery = useReadContract(getPlatformFeeInfo, { contract }); @@ -80,11 +78,6 @@ export const SettingsPlatformFees = ({ { - trackEvent({ - category: "settings", - action: "set-platform-fees", - label: "attempt", - }); const transaction = setPlatformFeeInfo({ contract, platformFeeRecipient: data.platform_fee_recipient, @@ -92,21 +85,11 @@ export const SettingsPlatformFees = ({ }); sendAndConfirmTx.mutate(transaction, { onSuccess: () => { - trackEvent({ - category: "settings", - action: "set-platform-fees", - label: "success", - }); form.reset(data); onSuccess(); }, onError: (error) => { - trackEvent({ - category: "settings", - action: "set-platform-fees", - label: "error", - error, - }); + console.error(error); onError(error); }, }); diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/primary-sale.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/primary-sale.tsx index 0706daba67a..81c682e7bd0 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/primary-sale.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/primary-sale.tsx @@ -6,7 +6,6 @@ import type { ExtensionDetectedState } from "components/buttons/ExtensionDetecte import { TransactionButton } from "components/buttons/TransactionButton"; import { AddressOrEnsSchema } from "constants/schemas"; import { SolidityInput } from "contract-ui/components/solidity-inputs"; -import { useTrack } from "hooks/analytics/useTrack"; import { useTxNotifications } from "hooks/useTxNotifications"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; @@ -44,7 +43,7 @@ export const SettingsPrimarySale = ({ isLoggedIn: boolean; }) => { const address = useActiveAccount()?.address; - const trackEvent = useTrack(); + const query = useReadContract(primarySaleRecipient, { contract, }); @@ -72,11 +71,6 @@ export const SettingsPrimarySale = ({ { - trackEvent({ - category: "settings", - action: "set-primary-sale", - label: "attempt", - }); const saleRecipient = d.primary_sale_recipient; if (!saleRecipient) { return toast.error( @@ -90,21 +84,11 @@ export const SettingsPrimarySale = ({ // if we switch back to mutateAsync then *need* to catch errors mutation.mutate(transaction, { onSuccess: () => { - trackEvent({ - category: "settings", - action: "set-primary-sale", - label: "success", - }); form.reset({ primary_sale_recipient: saleRecipient }); onSuccess(); }, onError: (error) => { - trackEvent({ - category: "settings", - action: "set-primary-sale", - label: "error", - error, - }); + console.error(error); onError(error); }, }); diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/royalties.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/royalties.tsx index df69e74c7cb..b9db6020636 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/royalties.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/settings/components/royalties.tsx @@ -7,7 +7,6 @@ import { TransactionButton } from "components/buttons/TransactionButton"; import { BasisPointsInput } from "components/inputs/BasisPointsInput"; import { AddressOrEnsSchema, BasisPointsSchema } from "constants/schemas"; import { SolidityInput } from "contract-ui/components/solidity-inputs"; -import { useTrack } from "hooks/analytics/useTrack"; import { useTxNotifications } from "hooks/useTxNotifications"; import { useForm } from "react-hook-form"; import type { ThirdwebContract } from "thirdweb"; @@ -65,7 +64,6 @@ export const SettingsRoyalties = ({ detectedState: ExtensionDetectedState; isLoggedIn: boolean; }) => { - const trackEvent = useTrack(); const query = useReadContract(getDefaultRoyaltyInfo, { contract, }); @@ -93,11 +91,6 @@ export const SettingsRoyalties = ({ { - trackEvent({ - category: "settings", - action: "set-royalty", - label: "attempt", - }); const transaction = setDefaultRoyaltyInfo({ contract, royaltyRecipient: d.fee_recipient, @@ -105,21 +98,11 @@ export const SettingsRoyalties = ({ }); mutation.mutate(transaction, { onSuccess: () => { - trackEvent({ - category: "settings", - action: "set-royalty", - label: "success", - }); form.reset(d); onSuccess(); }, onError: (error) => { - trackEvent({ - category: "settings", - action: "set-royalty", - label: "error", - error, - }); + console.error(error); onError(error); }, }); diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/split/components/distribute-button.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/split/components/distribute-button.tsx index 88a5ab2b4f7..b4cb44b6167 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/split/components/distribute-button.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/split/components/distribute-button.tsx @@ -3,7 +3,6 @@ import { Button } from "@/components/ui/button"; import { useSplitDistributeFunds } from "@3rdweb-sdk/react/hooks/useSplit"; import { TransactionButton } from "components/buttons/TransactionButton"; -import { useTrack } from "hooks/analytics/useTrack"; import { useTxNotifications } from "hooks/useTxNotifications"; import { useMemo } from "react"; import type { ThirdwebContract } from "thirdweb"; @@ -25,7 +24,6 @@ export const DistributeButton: React.FC = ({ isLoggedIn, ...restButtonProps }) => { - const trackEvent = useTrack(); const validBalances = balances.filter( (item) => item.balance !== "0" && item.balance !== "0.0", ); @@ -55,19 +53,9 @@ export const DistributeButton: React.FC = ({ mutation.mutate(undefined, { onSuccess: () => { onSuccess(); - trackEvent({ - category: "split", - action: "distribute", - label: "success", - }); }, onError: (error) => { - trackEvent({ - category: "split", - action: "distribute", - label: "error", - error, - }); + console.error(error); onError(error); }, }); diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/airdrop-form.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/airdrop-form.tsx index 145b38ad4c3..d93eb47fca9 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/airdrop-form.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/airdrop-form.tsx @@ -1,7 +1,6 @@ "use client"; import { TransactionButton } from "components/buttons/TransactionButton"; -import { useTrack } from "hooks/analytics/useTrack"; import { useTxNotifications } from "hooks/useTxNotifications"; import { CircleCheckIcon, UploadIcon } from "lucide-react"; import { type Dispatch, type SetStateAction, useState } from "react"; @@ -29,7 +28,6 @@ export const TokenAirdropForm: React.FC = ({ }>({ defaultValues: { addresses: [] }, }); - const trackEvent = useTrack(); const sendTransaction = useSendAndConfirmTransaction(); const addresses = watch("addresses"); const [airdropFormOpen, setAirdropFormOpen] = useState(false); @@ -48,12 +46,6 @@ export const TokenAirdropForm: React.FC = ({ { try { - trackEvent({ - category: "token", - action: "airdrop", - label: "attempt", - contractAddress: contract.address, - }); const tx = transferBatch({ contract, batch: data.addresses @@ -65,25 +57,12 @@ export const TokenAirdropForm: React.FC = ({ }); await sendTransaction.mutateAsync(tx, { onSuccess: () => { - trackEvent({ - category: "token", - action: "airdrop", - label: "success", - contract_address: contract.address, - }); // Close the sheet/modal on success if (toggle) { toggle(false); } }, onError: (error) => { - trackEvent({ - category: "token", - action: "airdrop", - label: "success", - contract_address: contract.address, - error, - }); console.error(error); }, }); diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/burn-button.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/burn-button.tsx index 88571fb0c4e..f314ebc5aeb 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/burn-button.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/burn-button.tsx @@ -1,5 +1,6 @@ "use client"; +import { reportTokenBurned } from "@/analytics/track"; import { Button } from "@/components/ui/button"; import { Sheet, @@ -11,7 +12,6 @@ import { } from "@/components/ui/sheet"; import { FormControl, Input } from "@chakra-ui/react"; import { TransactionButton } from "components/buttons/TransactionButton"; -import { useTrack } from "hooks/analytics/useTrack"; import { FlameIcon } from "lucide-react"; import { useState } from "react"; import { useForm } from "react-hook-form"; @@ -53,7 +53,6 @@ export const TokenBurnButton: React.FC = ({ const hasBalance = tokenBalanceQuery.data && tokenBalanceQuery.data > 0n; const [open, setOpen] = useState(false); const sendConfirmation = useSendAndConfirmTransaction(); - const trackEvent = useTrack(); const form = useForm({ defaultValues: { amount: "0" } }); const decimalsQuery = useReadContract(ERC20Ext.decimals, { contract }); @@ -107,13 +106,6 @@ export const TokenBurnButton: React.FC = ({ disabled={!form.formState.isDirty} onClick={form.handleSubmit((data) => { if (address) { - trackEvent({ - category: "token", - action: "burn", - label: "attempt", - }); - - // TODO: burn should be updated to take amount / amountWei (v6?) const tx = ERC20Ext.burn({ contract, asyncParams: async () => { @@ -128,23 +120,14 @@ export const TokenBurnButton: React.FC = ({ const promise = sendConfirmation.mutateAsync(tx, { onSuccess: () => { - trackEvent({ - category: "token", - action: "burn", - label: "success", + reportTokenBurned({ + address: contract.address, + chainId: contract.chain.id, + quantity: data.amount, }); form.reset({ amount: "0" }); setOpen(false); }, - onError: (error) => { - trackEvent({ - category: "token", - action: "burn", - label: "error", - error, - }); - console.error(error); - }, }); toast.promise(promise, { loading: `Burning ${data.amount} token(s)`, diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/claim-button.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/claim-button.tsx index c3801de44d5..83831e3c293 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/claim-button.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/claim-button.tsx @@ -11,7 +11,6 @@ import { } from "@/components/ui/sheet"; import { FormControl, Input } from "@chakra-ui/react"; import { TransactionButton } from "components/buttons/TransactionButton"; -import { useTrack } from "hooks/analytics/useTrack"; import { useTxNotifications } from "hooks/useTxNotifications"; import { GemIcon } from "lucide-react"; import { useState } from "react"; @@ -40,7 +39,6 @@ export const TokenClaimButton: React.FC = ({ }) => { const [open, setOpen] = useState(false); const sendAndConfirmTransaction = useSendAndConfirmTransaction(); - const trackEvent = useTrack(); const account = useActiveAccount(); const form = useForm({ defaultValues: { amount: "0", to: account?.address }, @@ -107,11 +105,7 @@ export const TokenClaimButton: React.FC = ({ "Need to specify an address to receive tokens", ); } - trackEvent({ - category: "token", - action: "claim", - label: "attempt", - }); + if (!account) { return toast.error("No account detected"); } @@ -147,21 +141,10 @@ export const TokenClaimButton: React.FC = ({ await sendAndConfirmTransaction.mutateAsync(transaction, { onSuccess: () => { - trackEvent({ - category: "token", - action: "claim", - label: "success", - }); form.reset({ amount: "0", to: account?.address }); setOpen(false); }, onError: (error) => { - trackEvent({ - category: "token", - action: "claim", - label: "error", - error, - }); console.error(error); }, }); @@ -170,12 +153,6 @@ export const TokenClaimButton: React.FC = ({ } catch (error) { console.error(error); claimTokensNotifications.onError(error); - trackEvent({ - category: "token", - action: "claim", - label: "error", - error, - }); } })} > diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/mint-button.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/mint-button.tsx index 748ca7f68a5..6776bdf3df4 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/mint-button.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/mint-button.tsx @@ -1,5 +1,6 @@ "use client"; +import { reportTokenMinted } from "@/analytics/track"; import { Button } from "@/components/ui/button"; import { Sheet, @@ -12,7 +13,6 @@ import { import { MinterOnly } from "@3rdweb-sdk/react/components/roles/minter-only"; import { FormControl, Input } from "@chakra-ui/react"; import { TransactionButton } from "components/buttons/TransactionButton"; -import { useTrack } from "hooks/analytics/useTrack"; import { PlusIcon } from "lucide-react"; import { useState } from "react"; import { useForm } from "react-hook-form"; @@ -47,7 +47,6 @@ export const TokenMintButton: React.FC = ({ contract, }); const sendAndConfirmTransaction = useSendAndConfirmTransaction(); - const trackEvent = useTrack(); const form = useForm({ defaultValues: { amount: "0" } }); return ( @@ -70,11 +69,6 @@ export const TokenMintButton: React.FC = ({ if (!address) { return toast.error("No wallet connected"); } - trackEvent({ - category: "token", - action: "mint", - label: "attempt", - }); const transaction = ERC20Ext.mintTo({ contract, amount: d.amount, @@ -84,23 +78,14 @@ export const TokenMintButton: React.FC = ({ transaction, { onSuccess: () => { - trackEvent({ - category: "token", - action: "mint", - label: "success", + reportTokenMinted({ + address: contract.address, + chainId: contract.chain.id, + quantity: d.amount, }); form.reset({ amount: "0" }); setOpen(false); }, - onError: (error) => { - trackEvent({ - category: "token", - action: "mint", - label: "error", - error, - }); - console.error(error); - }, }, ); toast.promise(promise, { diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/transfer-button.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/transfer-button.tsx index d2b2d2ea336..46f30ec0ff2 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/transfer-button.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/tokens/components/transfer-button.tsx @@ -1,5 +1,6 @@ "use client"; +import { reportTokenTransferred } from "@/analytics/track"; import { Button } from "@/components/ui/button"; import { Sheet, @@ -12,7 +13,6 @@ import { import { FormControl, Input } from "@chakra-ui/react"; import { TransactionButton } from "components/buttons/TransactionButton"; import { SolidityInput } from "contract-ui/components/solidity-inputs"; -import { useTrack } from "hooks/analytics/useTrack"; import { SendIcon } from "lucide-react"; import { useState } from "react"; import { useForm } from "react-hook-form"; @@ -44,7 +44,6 @@ export const TokenTransferButton: React.FC = ({ address: address || "", queryOptions: { enabled: !!address }, }); - const trackEvent = useTrack(); const form = useForm({ defaultValues: { amount: "0", to: "" } }); const hasBalance = tokenBalanceQuery.data && tokenBalanceQuery.data > 0n; const decimalsQuery = useReadContract(ERC20Ext.decimals, { contract }); @@ -110,11 +109,6 @@ export const TokenTransferButton: React.FC = ({ type="submit" disabled={!form.formState.isDirty} onClick={form.handleSubmit((d) => { - trackEvent({ - category: "token", - action: "transfer", - label: "attempt", - }); const transaction = ERC20Ext.transfer({ contract, amount: d.amount, @@ -122,23 +116,15 @@ export const TokenTransferButton: React.FC = ({ }); const promise = sendConfirmation.mutateAsync(transaction, { onSuccess: () => { - trackEvent({ - category: "token", - action: "transfer", - label: "success", + reportTokenTransferred({ + address: contract.address, + chainId: contract.chain.id, + quantity: d.amount, + to: d.to, }); form.reset({ amount: "0", to: "" }); setOpen(false); }, - onError: (error) => { - trackEvent({ - category: "token", - action: "transfer", - label: "error", - error, - }); - console.error(error); - }, }); toast.promise(promise, { loading: "Transferring tokens", diff --git a/apps/dashboard/src/app/(app)/(dashboard)/profile/[addressOrEns]/components/PublishedContractTable.tsx b/apps/dashboard/src/app/(app)/(dashboard)/profile/[addressOrEns]/components/PublishedContractTable.tsx index 9d7e9011103..b580f39132c 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/profile/[addressOrEns]/components/PublishedContractTable.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/profile/[addressOrEns]/components/PublishedContractTable.tsx @@ -12,7 +12,6 @@ import { import { ToolTipLabel } from "@/components/ui/tooltip"; import { TrackedLinkTW } from "@/components/ui/tracked-link"; import type { PublishedContractDetails } from "components/contract-components/hooks"; -import { useTrack } from "hooks/analytics/useTrack"; import { replaceDeployerAddress } from "lib/publisher-utils"; import { replaceIpfsUrl } from "lib/sdk"; import { ShieldCheckIcon } from "lucide-react"; @@ -44,7 +43,6 @@ function convertContractDataToRowData( export function PublishedContractTable(props: PublishedContractTableProps) { const { contractDetails, footer, publisherEnsName } = props; - const trackEvent = useTrack(); const rows = useMemo( () => contractDetails.map(convertContractDataToRowData), [contractDetails], @@ -123,11 +121,6 @@ export function PublishedContractTable(props: PublishedContractTableProps) { target="_blank" onClick={(e) => { e.stopPropagation(); - trackEvent({ - category: "visit-audit", - action: "click", - label: cell.value.audit, - }); }} > @@ -141,7 +134,7 @@ export function PublishedContractTable(props: PublishedContractTableProps) { ]; return cols; - }, [trackEvent, publisherEnsName, props.client]); + }, [publisherEnsName, props.client]); const tableInstance = useTable({ columns: tableColumns, diff --git a/apps/dashboard/src/app/(app)/account/components/AccountHeader.tsx b/apps/dashboard/src/app/(app)/account/components/AccountHeader.tsx index aef54edfbee..b3c5a9fd885 100644 --- a/apps/dashboard/src/app/(app)/account/components/AccountHeader.tsx +++ b/apps/dashboard/src/app/(app)/account/components/AccountHeader.tsx @@ -1,6 +1,8 @@ "use client"; import { createTeam } from "@/actions/createTeam"; +import * as analytics from "@/analytics/dashboard.client"; +import { AccountIdentifier } from "@/analytics/dashboard.client"; import type { Project } from "@/api/projects"; import type { Team } from "@/api/team"; import { useDashboardRouter } from "@/lib/DashboardRouter"; @@ -35,6 +37,7 @@ export function AccountHeader(props: { const logout = useCallback(async () => { try { await doLogout(); + analytics.reset(); if (wallet) { disconnect(wallet); } @@ -77,6 +80,7 @@ export function AccountHeader(props: { return (
+ diff --git a/apps/dashboard/src/app/(app)/account/contracts/DeployedContractsPageHeader.tsx b/apps/dashboard/src/app/(app)/account/contracts/DeployedContractsPageHeader.tsx index 46015f7d9e7..fff9396e31d 100644 --- a/apps/dashboard/src/app/(app)/account/contracts/DeployedContractsPageHeader.tsx +++ b/apps/dashboard/src/app/(app)/account/contracts/DeployedContractsPageHeader.tsx @@ -3,7 +3,6 @@ import { Button } from "@/components/ui/button"; import { TrackedLinkTW } from "@/components/ui/tracked-link"; import { ImportModal } from "components/contract-components/import-contract/modal"; -import { useTrack } from "hooks/analytics/useTrack"; import { DownloadIcon, PlusIcon } from "lucide-react"; import { useState } from "react"; import type { ThirdwebClient } from "thirdweb"; @@ -14,7 +13,6 @@ export function DeployedContractsPageHeader(props: { client: ThirdwebClient; }) { const [importModalOpen, setImportModalOpen] = useState(false); - const trackEvent = useTrack(); return (
@@ -40,11 +38,6 @@ export function DeployedContractsPageHeader(props: { className="gap-2 bg-card" variant="outline" onClick={() => { - trackEvent({ - action: "click", - category: "contracts", - label: "import-contract", - }); setImportModalOpen(true); }} > diff --git a/apps/dashboard/src/app/(app)/account/contracts/_components/DeployViaCLIOrImportCard.tsx b/apps/dashboard/src/app/(app)/account/contracts/_components/DeployViaCLIOrImportCard.tsx index 7633e341bf0..a91f9eab0d4 100644 --- a/apps/dashboard/src/app/(app)/account/contracts/_components/DeployViaCLIOrImportCard.tsx +++ b/apps/dashboard/src/app/(app)/account/contracts/_components/DeployViaCLIOrImportCard.tsx @@ -3,7 +3,6 @@ import { Button } from "@/components/ui/button"; import { TrackedLinkTW } from "@/components/ui/tracked-link"; import { ImportModal } from "components/contract-components/import-contract/modal"; -import { useTrack } from "hooks/analytics/useTrack"; import { ArrowUpRightIcon, DownloadIcon } from "lucide-react"; import { useState } from "react"; import type { ThirdwebClient } from "thirdweb"; @@ -13,7 +12,6 @@ export function DeployViaCLIOrImportCard(props: { projectId: string; client: ThirdwebClient; }) { - const trackEvent = useTrack(); const [importModalOpen, setImportModalOpen] = useState(false); return ( @@ -59,11 +57,6 @@ export function DeployViaCLIOrImportCard(props: { className="gap-2 bg-background" onClick={() => { setImportModalOpen(true); - trackEvent({ - category: "contracts-banner", - action: "click", - label: "import-contract", - }); }} > diff --git a/apps/dashboard/src/app/(app)/account/settings/AccountSettingsPage.tsx b/apps/dashboard/src/app/(app)/account/settings/AccountSettingsPage.tsx index 6cd50b51f00..782b2aa3361 100644 --- a/apps/dashboard/src/app/(app)/account/settings/AccountSettingsPage.tsx +++ b/apps/dashboard/src/app/(app)/account/settings/AccountSettingsPage.tsx @@ -2,6 +2,7 @@ import { confirmEmailWithOTP } from "@/actions/confirmEmail"; import { apiServerProxy } from "@/actions/proxies"; import { updateAccount } from "@/actions/updateAccount"; +import * as analytics from "@/analytics/dashboard.client"; import { useDashboardRouter } from "@/lib/DashboardRouter"; import type { Account } from "@3rdweb-sdk/react/hooks/useApi"; import type { ThirdwebClient } from "thirdweb"; @@ -46,6 +47,7 @@ export function AccountSettingsPage(props: { }} onAccountDeleted={async () => { await doLogout(); + analytics.reset(); if (activeWallet) { disconnect(activeWallet); } diff --git a/apps/dashboard/src/app/(app)/components/TeamPlanBadge.tsx b/apps/dashboard/src/app/(app)/components/TeamPlanBadge.tsx index ae1f9b14030..2f7b5674528 100644 --- a/apps/dashboard/src/app/(app)/components/TeamPlanBadge.tsx +++ b/apps/dashboard/src/app/(app)/components/TeamPlanBadge.tsx @@ -3,7 +3,6 @@ import type { Team } from "@/api/team"; import { Badge, type BadgeProps } from "@/components/ui/badge"; import { cn } from "@/lib/utils"; -import { useTrack } from "hooks/analytics/useTrack"; import { useDashboardRouter } from "../../../@/lib/DashboardRouter"; const teamPlanToBadgeVariant: Record< @@ -39,7 +38,6 @@ export function TeamPlanBadge(props: { postfix?: string; }) { const router = useDashboardRouter(); - const track = useTrack(); function handleNavigateToBilling(e: React.MouseEvent | React.KeyboardEvent) { if (props.plan !== "free") { @@ -47,11 +45,6 @@ export function TeamPlanBadge(props: { } e.stopPropagation(); e.preventDefault(); - track({ - category: "billing", - action: "show_plans", - label: "team_badge", - }); router.push(`/team/${props.teamSlug}/~/settings/billing?showPlans=true`); } diff --git a/apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/_components/plan-selector.tsx b/apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/_components/plan-selector.tsx index a677a5ad447..93ab1bee7f4 100644 --- a/apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/_components/plan-selector.tsx +++ b/apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/_components/plan-selector.tsx @@ -1,11 +1,11 @@ "use client"; +import { reportPlanSelectSkipped, reportPlanSelected } from "@/analytics/track"; import type { Team } from "@/api/team"; import { PricingCard } from "@/components/blocks/pricing-card"; import { Button } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; import { useDashboardRouter } from "@/lib/DashboardRouter"; -import { useTrack } from "hooks/analytics/useTrack"; import Link from "next/link"; import { pollWithTimeout } from "utils/pollWithTimeout"; import { useStripeRedirectEvent } from "../../../../../(stripe)/stripe-redirect/stripeRedirectChannel"; @@ -14,7 +14,6 @@ export function PlanSelector(props: { team: Team; getTeam: () => Promise; }) { - const trackEvent = useTrack(); const router = useDashboardRouter(); useStripeRedirectEvent(async () => { @@ -25,12 +24,6 @@ export function PlanSelector(props: { const isNonFreePlan = team.billingPlan !== "free"; if (isNonFreePlan) { - trackEvent({ - category: "teamOnboarding", - action: "upgradePlan", - label: "success", - plan: team.billingPlan, - }); router.replace(`/get-started/team/${props.team.slug}/add-members`); } @@ -49,12 +42,7 @@ export function PlanSelector(props: { label: "Get Started", type: "checkout", onClick() { - trackEvent({ - category: "teamOnboarding", - action: "selectPlan", - label: "attempt", - plan: "starter", - }); + reportPlanSelected({ planSKU: "starter" }); }, }} getTeam={props.getTeam} @@ -71,12 +59,7 @@ export function PlanSelector(props: { label: "Get Started", type: "checkout", onClick() { - trackEvent({ - category: "teamOnboarding", - action: "selectPlan", - label: "attempt", - plan: "growth", - }); + reportPlanSelected({ planSKU: "growth" }); }, }} highlighted @@ -94,12 +77,7 @@ export function PlanSelector(props: { label: "Get started", type: "checkout", onClick() { - trackEvent({ - category: "teamOnboarding", - action: "selectPlan", - label: "attempt", - plan: "scale", - }); + reportPlanSelected({ planSKU: "scale" }); }, }} getTeam={props.getTeam} @@ -116,12 +94,7 @@ export function PlanSelector(props: { label: "Get started", type: "checkout", onClick() { - trackEvent({ - category: "teamOnboarding", - action: "selectPlan", - label: "attempt", - plan: "pro", - }); + reportPlanSelected({ planSKU: "pro" }); }, }} getTeam={props.getTeam} @@ -147,11 +120,7 @@ export function PlanSelector(props: { className="self-center text-muted-foreground" asChild onClick={() => { - trackEvent({ - category: "teamOnboarding", - action: "selectPlan", - label: "skip", - }); + reportPlanSelectSkipped(); }} > - - - - - {children} - - - - + + {children} + + + ); } diff --git a/apps/dashboard/src/app/(app)/login/onboarding/LinkWalletPrompt/LinkWalletPrompt.stories.tsx b/apps/dashboard/src/app/(app)/login/onboarding/LinkWalletPrompt/LinkWalletPrompt.stories.tsx index ae272cdbd95..d17c254ecb2 100644 --- a/apps/dashboard/src/app/(app)/login/onboarding/LinkWalletPrompt/LinkWalletPrompt.stories.tsx +++ b/apps/dashboard/src/app/(app)/login/onboarding/LinkWalletPrompt/LinkWalletPrompt.stories.tsx @@ -54,9 +54,6 @@ function Story(props: { onBack={() => { storybookLog("onBack"); }} - trackEvent={(params) => { - storybookLog("trackEvent", params); - }} accountAddress="0x1234567890123456789012345678901234567890" /> diff --git a/apps/dashboard/src/app/(app)/login/onboarding/LinkWalletPrompt/LinkWalletPrompt.tsx b/apps/dashboard/src/app/(app)/login/onboarding/LinkWalletPrompt/LinkWalletPrompt.tsx index 868043866e8..427455a9a0b 100644 --- a/apps/dashboard/src/app/(app)/login/onboarding/LinkWalletPrompt/LinkWalletPrompt.tsx +++ b/apps/dashboard/src/app/(app)/login/onboarding/LinkWalletPrompt/LinkWalletPrompt.tsx @@ -1,11 +1,14 @@ "use client"; +import { + reportAccountWalletLinkRequested, + reportAccountWalletLinked, +} from "@/analytics/track"; import { Spinner } from "@/components/ui/Spinner/Spinner"; import { Button } from "@/components/ui/button"; -import { TrackedLinkTW } from "@/components/ui/tracked-link"; import { useMutation } from "@tanstack/react-query"; -import type { TrackingParams } from "hooks/analytics/useTrack"; import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react"; +import Link from "next/link"; import { toast } from "sonner"; import { shortenString } from "utils/usedapp-external"; @@ -14,7 +17,6 @@ export function LinkWalletPrompt(props: { accountAddress: string; onBack: () => void; requestLinkWallet: (email: string) => Promise; - trackEvent: (params: TrackingParams) => void; onLinkWalletRequestSent: () => void; }) { const requestLinkWallet = useMutation({ @@ -22,35 +24,19 @@ export function LinkWalletPrompt(props: { }); function handleLinkWalletRequest() { - props.trackEvent({ - category: "account", - action: "linkWallet", - label: "attempt", - data: { - email: props.email, - }, + reportAccountWalletLinkRequested({ + email: props.email, }); requestLinkWallet.mutate(props.email, { - onSuccess: (data) => { + onSuccess: () => { props.onLinkWalletRequestSent(); - props.trackEvent({ - category: "account", - action: "linkWallet", - label: "success", - data, - }); + reportAccountWalletLinked(); }, onError: (err) => { const error = err as Error; console.error(error); toast.error("Failed to send link wallet request"); - props.trackEvent({ - category: "account", - action: "linkWallet", - label: "error", - error, - }); }, }); } @@ -72,15 +58,13 @@ export function LinkWalletPrompt(props: {

You can link your wallet with this account to access it.
{" "} Multiple wallets can be linked to the same account.{" "} - Learn more about wallet linking - +

diff --git a/apps/dashboard/src/app/(app)/login/onboarding/LoginOrSignup/LoginOrSignup.stories.tsx b/apps/dashboard/src/app/(app)/login/onboarding/LoginOrSignup/LoginOrSignup.stories.tsx index f4e43504da6..938dc9b06aa 100644 --- a/apps/dashboard/src/app/(app)/login/onboarding/LoginOrSignup/LoginOrSignup.stories.tsx +++ b/apps/dashboard/src/app/(app)/login/onboarding/LoginOrSignup/LoginOrSignup.stories.tsx @@ -62,9 +62,6 @@ function Story(props: { throw new Error("email address already exists"); } }} - trackEvent={(params) => { - storybookLog("trackEvent", params); - }} /> ); diff --git a/apps/dashboard/src/app/(app)/login/onboarding/LoginOrSignup/LoginOrSignup.tsx b/apps/dashboard/src/app/(app)/login/onboarding/LoginOrSignup/LoginOrSignup.tsx index 6b7f49381a5..87dd123887b 100644 --- a/apps/dashboard/src/app/(app)/login/onboarding/LoginOrSignup/LoginOrSignup.tsx +++ b/apps/dashboard/src/app/(app)/login/onboarding/LoginOrSignup/LoginOrSignup.tsx @@ -1,5 +1,6 @@ "use client"; +import { reportAccountEmailVerificationRequested } from "@/analytics/track"; import { FormFieldSetup } from "@/components/blocks/FormFieldSetup"; import { Spinner } from "@/components/ui/Spinner/Spinner"; import { Button } from "@/components/ui/button"; @@ -8,7 +9,6 @@ import { Input } from "@/components/ui/input"; import { TabButtons } from "@/components/ui/tabs"; import { zodResolver } from "@hookform/resolvers/zod"; import { useMutation } from "@tanstack/react-query"; -import type { TrackingParams } from "hooks/analytics/useTrack"; import { ArrowRightIcon } from "lucide-react"; import { useState } from "react"; import { useForm } from "react-hook-form"; @@ -30,7 +30,6 @@ export function LoginOrSignup(props: { subscribeToUpdates?: true; name?: string; }) => Promise; - trackEvent: (params: TrackingParams) => void; }) { const [tab, setTab] = useState<"signup" | "login">("signup"); const loginOrSignup = useMutation({ @@ -43,17 +42,12 @@ export function LoginOrSignup(props: { name?: string; }) { loginOrSignup.mutate(values, { - onSuccess: (data) => { + onSuccess: () => { props.onRequestSent({ email: values.email, isExistingEmail: false, }); - props.trackEvent({ - category: "onboarding", - action: "update", - label: "success", - data, - }); + reportAccountEmailVerificationRequested({ email: values.email }); }, onError: (error) => { if (error?.message.match(/email address already exists/)) { @@ -69,13 +63,6 @@ export function LoginOrSignup(props: { } console.error(error); - props.trackEvent({ - category: "account", - action: "update", - label: "error", - error: error.message, - fromOnboarding: true, - }); }, }); } diff --git a/apps/dashboard/src/app/(app)/login/onboarding/VerifyEmail/VerifyEmail.stories.tsx b/apps/dashboard/src/app/(app)/login/onboarding/VerifyEmail/VerifyEmail.stories.tsx index 54de6b6fb76..c6f1f11ba34 100644 --- a/apps/dashboard/src/app/(app)/login/onboarding/VerifyEmail/VerifyEmail.stories.tsx +++ b/apps/dashboard/src/app/(app)/login/onboarding/VerifyEmail/VerifyEmail.stories.tsx @@ -76,9 +76,6 @@ function Story(props: { onBack={() => { storybookLog("onBack"); }} - trackEvent={(params) => { - storybookLog("trackEvent", params); - }} /> ); diff --git a/apps/dashboard/src/app/(app)/login/onboarding/VerifyEmail/VerifyEmail.tsx b/apps/dashboard/src/app/(app)/login/onboarding/VerifyEmail/VerifyEmail.tsx index 43d902dce9f..539a73f6180 100644 --- a/apps/dashboard/src/app/(app)/login/onboarding/VerifyEmail/VerifyEmail.tsx +++ b/apps/dashboard/src/app/(app)/login/onboarding/VerifyEmail/VerifyEmail.tsx @@ -1,4 +1,8 @@ "use client"; +import { + reportAccountEmailVerified, + reportAccountWalletLinked, +} from "@/analytics/track"; import { Spinner } from "@/components/ui/Spinner/Spinner"; import { Button } from "@/components/ui/button"; import { @@ -10,7 +14,6 @@ import { cn } from "@/lib/utils"; import type { Account } from "@3rdweb-sdk/react/hooks/useApi"; import { zodResolver } from "@hookform/resolvers/zod"; import { useMutation } from "@tanstack/react-query"; -import type { TrackingParams } from "hooks/analytics/useTrack"; import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp"; import { ArrowLeftIcon, RotateCcwIcon } from "lucide-react"; import { useForm } from "react-hook-form"; @@ -28,7 +31,6 @@ type VerifyEmailProps = { confirmationToken: string; }) => Promise<{ account: Account }>; resendConfirmationEmail: () => Promise; - trackEvent: (params: TrackingParams) => void; accountAddress: string; title: string; trackingAction: string; @@ -51,30 +53,17 @@ export function VerifyEmail(props: VerifyEmailProps) { }); const handleSubmit = form.handleSubmit((values) => { - props.trackEvent({ - category: "account", - action: props.trackingAction, - label: "attempt", - }); - verifyEmail.mutate(values, { onSuccess: (response) => { props.onEmailConfirmed(response); - props.trackEvent({ - category: "account", - action: props.trackingAction, - label: "success", - }); + if (props.trackingAction === "confirmEmail") { + reportAccountEmailVerified({ email: props.email }); + } else if (props.trackingAction === "confirmLinkWallet") { + reportAccountWalletLinked(); + } }, - onError: (error) => { - console.error(error); - toast.error("Invalid confirmation code"); - props.trackEvent({ - category: "account", - action: props.trackingAction, - label: "error", - error: error.message, - }); + onError: () => { + toast.error("Failed to send verification code"); }, }); }); @@ -83,29 +72,12 @@ export function VerifyEmail(props: VerifyEmailProps) { form.setValue("confirmationToken", ""); verifyEmail.reset(); - props.trackEvent({ - category: "account", - action: "resendEmailConfirmation", - label: "attempt", - }); - resendConfirmationEmail.mutate(undefined, { onSuccess: () => { toast.success("Verification code sent"); - props.trackEvent({ - category: "account", - action: "resendEmailConfirmation", - label: "success", - }); }, - onError: (error) => { + onError: () => { toast.error("Failed to send verification code"); - props.trackEvent({ - category: "account", - action: "resendEmailConfirmation", - label: "error", - error, - }); }, }); } diff --git a/apps/dashboard/src/app/(app)/login/onboarding/account-onboarding-ui.tsx b/apps/dashboard/src/app/(app)/login/onboarding/account-onboarding-ui.tsx index 69b622924f4..a30a895bc63 100644 --- a/apps/dashboard/src/app/(app)/login/onboarding/account-onboarding-ui.tsx +++ b/apps/dashboard/src/app/(app)/login/onboarding/account-onboarding-ui.tsx @@ -1,6 +1,5 @@ "use client"; import type { Account } from "@3rdweb-sdk/react/hooks/useApi"; -import type { TrackingParams } from "hooks/analytics/useTrack"; import { useState } from "react"; import { LinkWalletPrompt } from "./LinkWalletPrompt/LinkWalletPrompt"; import { LoginOrSignup } from "./LoginOrSignup/LoginOrSignup"; @@ -27,7 +26,6 @@ type AccountOnboardingScreen = type AccountOnboardingProps = { onComplete: (param: { account: Account }) => void; accountAddress: string; - trackEvent: (params: TrackingParams) => void; verifyEmail: (params: { confirmationToken: string; }) => Promise<{ account: Account }>; @@ -56,7 +54,6 @@ export function AccountOnboardingUI(props: AccountOnboardingProps) { {screen.id === "login-or-signup" && ( { if (params.isExistingEmail) { setScreen({ @@ -79,7 +76,6 @@ export function AccountOnboardingUI(props: AccountOnboardingProps) { { setScreen({ id: "link-wallet-verify-email", @@ -97,7 +93,6 @@ export function AccountOnboardingUI(props: AccountOnboardingProps) { accountAddress={props.accountAddress} verifyEmail={props.verifyEmail} resendConfirmationEmail={props.resendEmailConfirmation} - trackEvent={props.trackEvent} onEmailConfirmed={props.onComplete} onBack={() => setScreen(screen.backScreen)} email={screen.email} @@ -109,7 +104,6 @@ export function AccountOnboardingUI(props: AccountOnboardingProps) { accountAddress={props.accountAddress} verifyEmail={props.verifyEmail} resendConfirmationEmail={props.resendEmailConfirmation} - trackEvent={props.trackEvent} onEmailConfirmed={props.onComplete} onBack={() => setScreen(screen.backScreen)} email={screen.email} diff --git a/apps/dashboard/src/app/(app)/login/onboarding/account-onboarding.stories.tsx b/apps/dashboard/src/app/(app)/login/onboarding/account-onboarding.stories.tsx index 0af63467eb1..7dd68707f99 100644 --- a/apps/dashboard/src/app/(app)/login/onboarding/account-onboarding.stories.tsx +++ b/apps/dashboard/src/app/(app)/login/onboarding/account-onboarding.stories.tsx @@ -47,9 +47,6 @@ function Story(props: { storybookLog("onComplete"); }} accountAddress="" - trackEvent={(params) => { - storybookLog("trackEvent", params); - }} loginOrSignup={async () => { await new Promise((resolve) => setTimeout(resolve, 1000)); if (props.loginOrSignupType === "error-email-exists") { diff --git a/apps/dashboard/src/app/(app)/login/onboarding/account-onboarding.tsx b/apps/dashboard/src/app/(app)/login/onboarding/account-onboarding.tsx index 6eaa4dbcf00..82ed5ec563d 100644 --- a/apps/dashboard/src/app/(app)/login/onboarding/account-onboarding.tsx +++ b/apps/dashboard/src/app/(app)/login/onboarding/account-onboarding.tsx @@ -1,11 +1,11 @@ "use client"; +import * as analytics from "@/analytics/dashboard.client"; import { resendEmailClient, updateAccountClient, verifyEmailClient, } from "@3rdweb-sdk/react/hooks/useApi"; -import { useTrack } from "hooks/analytics/useTrack"; import { useActiveWallet } from "thirdweb/react"; import { useDisconnect } from "thirdweb/react"; import { doLogout } from "../auth-actions"; @@ -16,7 +16,6 @@ function AccountOnboarding(props: { onLogout: () => void; accountAddress: string; }) { - const trackEvent = useTrack(); const activeWallet = useActiveWallet(); const { disconnect } = useDisconnect(); return ( @@ -27,6 +26,7 @@ function AccountOnboarding(props: { disconnect(activeWallet); } await doLogout(); + analytics.reset(); props.onLogout(); }} accountAddress={props.accountAddress} @@ -37,7 +37,6 @@ function AccountOnboarding(props: { resendEmailConfirmation={async () => { await resendEmailClient(); }} - trackEvent={trackEvent} requestLinkWallet={async (email) => { await updateAccountClient({ email, diff --git a/apps/dashboard/src/app/(app)/login/onboarding/team-onboarding/InviteTeamMembers.stories.tsx b/apps/dashboard/src/app/(app)/login/onboarding/team-onboarding/InviteTeamMembers.stories.tsx index cd5a73be46d..9e195e93716 100644 --- a/apps/dashboard/src/app/(app)/login/onboarding/team-onboarding/InviteTeamMembers.stories.tsx +++ b/apps/dashboard/src/app/(app)/login/onboarding/team-onboarding/InviteTeamMembers.stories.tsx @@ -62,9 +62,6 @@ function Story(props: { { - storybookLog("trackEvent", params); - }} getTeam={async () => { return teamStub("foo", props.plan); }} diff --git a/apps/dashboard/src/app/(app)/login/onboarding/team-onboarding/InviteTeamMembers.tsx b/apps/dashboard/src/app/(app)/login/onboarding/team-onboarding/InviteTeamMembers.tsx index 0db70385b23..bffb76ce380 100644 --- a/apps/dashboard/src/app/(app)/login/onboarding/team-onboarding/InviteTeamMembers.tsx +++ b/apps/dashboard/src/app/(app)/login/onboarding/team-onboarding/InviteTeamMembers.tsx @@ -1,5 +1,10 @@ "use client"; +import { + reportPlanSelected, + reportTeamInviteMembersSent, + reportTeamInviteMembersSkipped, +} from "@/analytics/track"; import type { Team } from "@/api/team"; import { PricingCard } from "@/components/blocks/pricing-card"; import { Spinner } from "@/components/ui/Spinner/Spinner"; @@ -14,7 +19,6 @@ import { } from "@/components/ui/sheet"; import { TabButtons } from "@/components/ui/tabs"; import { useDashboardRouter } from "@/lib/DashboardRouter"; -import type { TrackingParams } from "hooks/analytics/useTrack"; import { ArrowRightIcon, CircleArrowUpIcon } from "lucide-react"; import { useState, useTransition } from "react"; import type { ThirdwebClient } from "thirdweb"; @@ -30,7 +34,6 @@ export function InviteTeamMembersUI(props: { inviteTeamMembers: InviteTeamMembersFn; onComplete: () => void; getTeam: () => Promise; - trackEvent: (params: TrackingParams) => void; client: ThirdwebClient; }) { const [showPlanModal, setShowPlanModal] = useState(false); @@ -52,15 +55,6 @@ export function InviteTeamMembersUI(props: { const isNonFreePlan = team.billingPlan !== "free" && team.billingPlan !== "starter"; - if (isNonFreePlan) { - props.trackEvent({ - category: "teamOnboarding", - action: "upgradePlan", - label: "success", - plan: team.billingPlan, - }); - } - return isNonFreePlan; }, timeoutMs: 5000, @@ -80,7 +74,6 @@ export function InviteTeamMembersUI(props: { @@ -88,7 +81,11 @@ export function InviteTeamMembersUI(props: { { + const res = await props.inviteTeamMembers(params); + reportTeamInviteMembersSent({ inviteCount: params.length }); + return res; + }} team={props.team} userHasEditPermission={true} onInviteSuccess={() => setHasSentInvites(true)} @@ -106,11 +103,6 @@ export function InviteTeamMembersUI(props: { className="gap-2" onClick={() => { setShowPlanModal(true); - props.trackEvent({ - category: "teamOnboarding", - action: "upgradePlan", - label: "openModal", - }); }} > @@ -122,11 +114,7 @@ export function InviteTeamMembersUI(props: { onClick={() => { props.onComplete(); if (!hasSentInvites) { - props.trackEvent({ - category: "teamOnboarding", - action: "inviteTeamMembers", - label: "skip", - }); + reportTeamInviteMembersSkipped(); } }} className="gap-2" @@ -152,7 +140,6 @@ export function InviteTeamMembersUI(props: { function InviteModalContent(props: { teamSlug: string; billingStatus: Team["billingStatus"]; - trackEvent: (params: TrackingParams) => void; getTeam: () => Promise; teamId: string; }) { @@ -169,12 +156,7 @@ function InviteModalContent(props: { label: "Get Started", type: "checkout", onClick() { - props.trackEvent({ - category: "teamOnboarding", - action: "upgradePlan", - label: "attempt", - plan: "growth", - }); + reportPlanSelected({ planSKU: "growth" }); }, }} highlighted @@ -192,12 +174,7 @@ function InviteModalContent(props: { label: "Get started", type: "checkout", onClick() { - props.trackEvent({ - category: "teamOnboarding", - action: "upgradePlan", - label: "attempt", - plan: "scale", - }); + reportPlanSelected({ planSKU: "scale" }); }, }} getTeam={props.getTeam} @@ -214,12 +191,7 @@ function InviteModalContent(props: { label: "Get started", type: "checkout", onClick() { - props.trackEvent({ - category: "teamOnboarding", - action: "upgradePlan", - label: "attempt", - plan: "pro", - }); + reportPlanSelected({ planSKU: "pro" }); }, }} getTeam={props.getTeam} diff --git a/apps/dashboard/src/app/(app)/login/onboarding/team-onboarding/team-onboarding.tsx b/apps/dashboard/src/app/(app)/login/onboarding/team-onboarding/team-onboarding.tsx index 2d2997180c0..fae2daa62de 100644 --- a/apps/dashboard/src/app/(app)/login/onboarding/team-onboarding/team-onboarding.tsx +++ b/apps/dashboard/src/app/(app)/login/onboarding/team-onboarding/team-onboarding.tsx @@ -3,7 +3,6 @@ import { apiServerProxy } from "@/actions/proxies"; import { sendTeamInvites } from "@/actions/sendTeamInvite"; import type { Team } from "@/api/team"; import { useDashboardRouter } from "@/lib/DashboardRouter"; -import { useTrack } from "hooks/analytics/useTrack"; import { toast } from "sonner"; import type { ThirdwebClient } from "thirdweb"; import { upload } from "thirdweb/storage"; @@ -17,7 +16,7 @@ export function TeamInfoForm(props: { teamSlug: string; }) { const router = useDashboardRouter(); - const trackEvent = useTrack(); + return ( { @@ -48,12 +47,6 @@ export function TeamInfoForm(props: { slug: data.slug, }; - trackEvent({ - category: "teamOnboarding", - action: "updateTeam", - label: "attempt", - }); - if (data.image) { try { teamValue.image = await upload({ @@ -72,21 +65,9 @@ export function TeamInfoForm(props: { }); if (!res.ok) { - trackEvent({ - category: "teamOnboarding", - action: "updateTeam", - label: "error", - }); - throw new Error(res.error); } - trackEvent({ - category: "teamOnboarding", - action: "updateTeam", - label: "success", - }); - return res.data; }} /> @@ -98,11 +79,9 @@ export function InviteTeamMembers(props: { client: ThirdwebClient; }) { const router = useDashboardRouter(); - const trackEvent = useTrack(); return ( { router.replace(`/team/${props.team.slug}`); @@ -128,27 +107,10 @@ export function InviteTeamMembers(props: { invites: params, }); - trackEvent({ - category: "teamOnboarding", - action: "inviteTeamMembers", - label: "attempt", - }); - if (!res.ok) { - trackEvent({ - category: "teamOnboarding", - action: "inviteTeamMembers", - label: "error", - }); throw new Error(res.errorMessage); } - trackEvent({ - category: "teamOnboarding", - action: "inviteTeamMembers", - label: "success", - }); - return { results: res.results, }; diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/_components/invite-team-members-button.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/_components/invite-team-members-button.tsx index f9da4f417fa..5e450b57652 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/_components/invite-team-members-button.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/_components/invite-team-members-button.tsx @@ -1,25 +1,12 @@ "use client"; import { Button } from "@/components/ui/button"; -import { useTrack } from "hooks/analytics/useTrack"; import { UserPlusIcon } from "lucide-react"; import Link from "next/link"; export function InviteTeamMembersButton(props: { teamSlug: string }) { - const trackEvent = useTrack(); return ( -
); } diff --git a/apps/dashboard/src/app/(app)/team/components/TeamHeader/team-header-logged-in.client.tsx b/apps/dashboard/src/app/(app)/team/components/TeamHeader/team-header-logged-in.client.tsx index ff3e2261675..269cb9f40c4 100644 --- a/apps/dashboard/src/app/(app)/team/components/TeamHeader/team-header-logged-in.client.tsx +++ b/apps/dashboard/src/app/(app)/team/components/TeamHeader/team-header-logged-in.client.tsx @@ -1,18 +1,19 @@ "use client"; import { createTeam } from "@/actions/createTeam"; +import * as analytics from "@/analytics/dashboard.client"; +import { TeamIdentifier } from "@/analytics/dashboard.client"; import type { Project } from "@/api/projects"; import type { Team } from "@/api/team"; import { useDashboardRouter } from "@/lib/DashboardRouter"; import { CustomConnectWallet } from "@3rdweb-sdk/react/components/connect-wallet"; import type { Account } from "@3rdweb-sdk/react/hooks/useApi"; import { LazyCreateProjectDialog } from "components/settings/ApiKeys/Create/LazyCreateAPIKeyDialog"; -import { useCallback, useState } from "react"; +import { useCallback, useMemo, useState } from "react"; import { toast } from "sonner"; import type { ThirdwebClient } from "thirdweb"; import { useActiveWallet, useDisconnect } from "thirdweb/react"; import { doLogout } from "../../../login/auth-actions"; - import { type TeamHeaderCompProps, TeamHeaderDesktopUI, @@ -38,6 +39,7 @@ export function TeamHeaderLoggedIn(props: { // log out the user try { await doLogout(); + analytics.reset(); if (activeWallet) { disconnect(activeWallet); } @@ -81,8 +83,18 @@ export function TeamHeaderLoggedIn(props: { accountAddress: props.accountAddress, }; + const isStaffMode = useMemo( + () => + !props.teamsAndProjects.some( + (team) => team.team.id === props.currentTeam.id, + ), + [props.teamsAndProjects, props.currentTeam.id], + ); + return (
+ {/* do NOT identify as the team in staff mode */} + {!isStaffMode && } diff --git a/apps/dashboard/src/app/bridge/components/client/Providers.client.tsx b/apps/dashboard/src/app/bridge/components/client/Providers.client.tsx index 51a71506333..8afb600ba23 100644 --- a/apps/dashboard/src/app/bridge/components/client/Providers.client.tsx +++ b/apps/dashboard/src/app/bridge/components/client/Providers.client.tsx @@ -2,8 +2,6 @@ import { ThemeProvider } from "next-themes"; import { Toaster } from "sonner"; import { ThirdwebProvider } from "thirdweb/react"; -import { PHProvider } from "../../../../lib/posthog/Posthog"; -import { PostHogPageView } from "../../../../lib/posthog/PosthogPageView"; export function BridgeProviders({ children }: { children: React.ReactNode }) { return ( @@ -14,11 +12,8 @@ export function BridgeProviders({ children }: { children: React.ReactNode }) { enableSystem={false} defaultTheme="dark" > - - - {children} - - + {children} + ); diff --git a/apps/dashboard/src/app/nebula-app/(app)/components/CustomChat/CustomChatButton.tsx b/apps/dashboard/src/app/nebula-app/(app)/components/CustomChat/CustomChatButton.tsx index f15a4e8930b..8048cd00281 100644 --- a/apps/dashboard/src/app/nebula-app/(app)/components/CustomChat/CustomChatButton.tsx +++ b/apps/dashboard/src/app/nebula-app/(app)/components/CustomChat/CustomChatButton.tsx @@ -3,7 +3,6 @@ import { Button } from "@/components/ui/button"; import { NEXT_PUBLIC_DASHBOARD_CLIENT_ID } from "@/constants/public-envs"; import { cn } from "@/lib/utils"; -import { useTrack } from "hooks/analytics/useTrack"; import { MessageCircleIcon, XIcon } from "lucide-react"; import { useCallback, useRef, useState } from "react"; import { createThirdwebClient } from "thirdweb"; @@ -30,17 +29,12 @@ export function CustomChatButton(props: { const [hasBeenOpened, setHasBeenOpened] = useState(false); const closeModal = useCallback(() => setIsOpen(false), []); const ref = useRef(null); - const trackEvent = useTrack(); return ( <> {/* Inline Button (not floating) */} diff --git a/apps/dashboard/src/components/embedded-wallets/Configure/InAppWalletSettingsUI.stories.tsx b/apps/dashboard/src/components/embedded-wallets/Configure/InAppWalletSettingsUI.stories.tsx index 2f9fb64da72..ee01f00bcde 100644 --- a/apps/dashboard/src/components/embedded-wallets/Configure/InAppWalletSettingsUI.stories.tsx +++ b/apps/dashboard/src/components/embedded-wallets/Configure/InAppWalletSettingsUI.stories.tsx @@ -64,7 +64,6 @@ function Variants(props: { }} teamSlug="bar" isUpdating={false} - trackingCategory="foo" updateApiKey={() => {}} smsCountryTiers={{ // scaffold some countries to play around with the UI diff --git a/apps/dashboard/src/components/embedded-wallets/Configure/index.tsx b/apps/dashboard/src/components/embedded-wallets/Configure/index.tsx index 181e174e7a1..b4011499af9 100644 --- a/apps/dashboard/src/components/embedded-wallets/Configure/index.tsx +++ b/apps/dashboard/src/components/embedded-wallets/Configure/index.tsx @@ -20,7 +20,6 @@ import { import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; -import { TrackedLinkTW } from "@/components/ui/tracked-link"; import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler"; import { cn } from "@/lib/utils"; import { updateProjectClient } from "@3rdweb-sdk/react/hooks/useApi"; @@ -32,8 +31,8 @@ import { type ApiKeyEmbeddedWalletsValidationSchema, apiKeyEmbeddedWalletsValidationSchema, } from "components/settings/ApiKeys/validations"; -import { useTrack } from "hooks/analytics/useTrack"; import { CircleAlertIcon, PlusIcon, Trash2Icon } from "lucide-react"; +import Link from "next/link"; import type React from "react"; import { useState } from "react"; import { type UseFormReturn, useFieldArray, useForm } from "react-hook-form"; @@ -46,7 +45,6 @@ import { FileInput } from "../../shared/FileInput"; import CountrySelector from "./sms-country-select/country-selector"; type InAppWalletSettingsPageProps = { - trackingCategory: string; project: Project; teamId: string; teamSlug: string; @@ -55,8 +53,6 @@ type InAppWalletSettingsPageProps = { client: ThirdwebClient; }; -const TRACKING_CATEGORY = "embedded-wallet"; - type UpdateAPIKeyTrackingData = { hasCustomBranding: boolean; hasCustomJwt: boolean; @@ -76,38 +72,14 @@ export function InAppWalletSettingsPage(props: InAppWalletSettingsPageProps) { }, }); - const { trackingCategory } = props; - const trackEvent = useTrack(); - - function handleUpdateProject( - projectValues: Partial, - trackingData: UpdateAPIKeyTrackingData, - ) { - trackEvent({ - category: trackingCategory, - action: "configuration-update", - label: "attempt", - }); - + function handleUpdateProject(projectValues: Partial) { updateProject.mutate(projectValues, { onSuccess: () => { toast.success("In-App Wallet API Key configuration updated"); - trackEvent({ - category: trackingCategory, - action: "configuration-update", - label: "success", - data: trackingData, - }); }, onError: (err) => { toast.error("Failed to update an API Key"); console.error(err); - trackEvent({ - category: trackingCategory, - action: "configuration-update", - label: "error", - error: err, - }); }, }); } @@ -545,15 +517,13 @@ function JSONWebTokenFields(props: { description={ <> Optionally allow users to authenticate with a custom JWT.{" "} - Learn more - + } > @@ -638,15 +608,13 @@ function AuthEndpointFields(props: { <> Optionally allow users to authenticate with any arbitrary payload that you provide.{" "} - Learn more - + } > diff --git a/apps/dashboard/src/components/notices/AnnouncementBanner.tsx b/apps/dashboard/src/components/notices/AnnouncementBanner.tsx index 7a646419f57..504d523576b 100644 --- a/apps/dashboard/src/components/notices/AnnouncementBanner.tsx +++ b/apps/dashboard/src/components/notices/AnnouncementBanner.tsx @@ -1,9 +1,9 @@ "use client"; import { Button } from "@/components/ui/button"; -import { TrackedLinkTW } from "@/components/ui/tracked-link"; import { useLocalStorage } from "hooks/useLocalStorage"; import { XIcon } from "lucide-react"; +import Link from "next/link"; function AnnouncementBannerUI(props: { href: string; @@ -19,17 +19,15 @@ function AnnouncementBannerUI(props: { return (
- {props.label} - +