diff --git a/package.json b/package.json index 173cf77565..2645e62ce3 100644 --- a/package.json +++ b/package.json @@ -266,7 +266,7 @@ "@lobechat/ssrf-safe-fetch": "workspace:*", "@lobechat/utils": "workspace:*", "@lobechat/web-crawler": "workspace:*", - "@lobehub/analytics": "^1.6.0", + "@lobehub/analytics": "^1.6.2", "@lobehub/charts": "^5.0.0", "@lobehub/desktop-ipc-typings": "workspace:*", "@lobehub/editor": "^4.8.1", diff --git a/src/app/[variants]/(auth)/_layout/AuthGlobalProvider.tsx b/src/app/[variants]/(auth)/_layout/AuthGlobalProvider.tsx index 531e0dfb09..78ee31b447 100644 --- a/src/app/[variants]/(auth)/_layout/AuthGlobalProvider.tsx +++ b/src/app/[variants]/(auth)/_layout/AuthGlobalProvider.tsx @@ -1,6 +1,7 @@ import { type ReactNode } from 'react'; import { appEnv } from '@/envs/app'; +import AnalyticsRSCProvider from '@/layout/AnalyticsRSCProvider'; import AuthProvider from '@/layout/AuthProvider'; import NextThemeProvider from '@/layout/GlobalProvider/NextThemeProvider'; import StyleRegistry from '@/layout/GlobalProvider/StyleRegistry'; @@ -30,7 +31,9 @@ const AuthGlobalProvider = async ({ children, variants }: AuthGlobalProviderProp segmentVariants={variants} serverConfig={serverConfig} > - {children} + + {children} + diff --git a/src/app/[variants]/(auth)/signin/useSignIn.ts b/src/app/[variants]/(auth)/signin/useSignIn.ts index 8aea1e23d1..482cd669f0 100644 --- a/src/app/[variants]/(auth)/signin/useSignIn.ts +++ b/src/app/[variants]/(auth)/signin/useSignIn.ts @@ -8,6 +8,7 @@ import { type CheckUserResponseData } from '@/app/(backend)/api/auth/check-user/ import { type ResolveUsernameResponseData } from '@/app/(backend)/api/auth/resolve-username/route'; import { useBusinessSignin } from '@/business/client/hooks/useBusinessSignin'; import { message } from '@/components/AntdStaticMethods'; +import { trackLoginOrSignupClicked } from '@/features/User/UserLoginOrSignup/trackLoginOrSignupClicked'; import { requestPasswordReset, signIn } from '@/libs/better-auth/auth-client'; import { isBuiltinProvider, normalizeProviderId } from '@/libs/better-auth/utils/client'; @@ -125,6 +126,8 @@ export const useSignIn = () => { const handleCheckUser = async (values: Pick) => { setLoading(true); + await trackLoginOrSignupClicked({ spm: 'signin.email_step.submit' }); + try { const resolvedEmail = await resolveEmailFromIdentifier(values.email); if (!resolvedEmail) return; @@ -172,6 +175,8 @@ export const useSignIn = () => { const handleSignIn = async (values: Pick) => { setLoading(true); + await trackLoginOrSignupClicked({ spm: 'signin.password_step.submit' }); + try { const callbackUrl = searchParams.get('callbackUrl') || '/'; const result = await signIn.email( @@ -203,6 +208,11 @@ export const useSignIn = () => { const handleSocialSignIn = async (provider: string) => { setSocialLoading(provider); const normalizedProvider = normalizeProviderId(provider); + await trackLoginOrSignupClicked({ + provider: normalizedProvider, + spm: 'signin.social.click', + }); + try { if (ENABLE_BUSINESS_FEATURES && !(await preSocialSigninCheck())) { setSocialLoading(null); @@ -252,7 +262,9 @@ export const useSignIn = () => { const params = new URLSearchParams(); if (currentEmail) params.set('email', currentEmail); params.set('callbackUrl', callbackUrl); - router.push(`/signup?${params.toString()}`); + void trackLoginOrSignupClicked({ spm: 'signin.go_to_signup.click' }).finally(() => { + router.push(`/signup?${params.toString()}`); + }); }; const handleForgotPassword = async () => { diff --git a/src/app/[variants]/(auth)/signup/[[...signup]]/BetterAuthSignUpForm.tsx b/src/app/[variants]/(auth)/signup/[[...signup]]/BetterAuthSignUpForm.tsx index fbf35c3038..e065a1881b 100644 --- a/src/app/[variants]/(auth)/signup/[[...signup]]/BetterAuthSignUpForm.tsx +++ b/src/app/[variants]/(auth)/signup/[[...signup]]/BetterAuthSignUpForm.tsx @@ -9,6 +9,7 @@ import { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { AuthCard } from '../../../../../features/AuthCard'; +import { trackLoginOrSignupClicked } from '../../../../../features/User/UserLoginOrSignup/trackLoginOrSignupClicked'; import { type SignUpFormValues } from './useSignUp'; import { useSignUp } from './useSignUp'; @@ -27,7 +28,17 @@ const BetterAuthSignUpForm = () => { const footer = ( {t('betterAuth.signup.hasAccount')}{' '} - {t('betterAuth.signup.signinLink')} + { + event.preventDefault(); + void trackLoginOrSignupClicked({ spm: 'signup.go_to_signin.click' }).finally(() => { + window.location.href = `/signin?${searchParams.toString()}`; + }); + }} + > + {t('betterAuth.signup.signinLink')} + ); diff --git a/src/app/[variants]/(auth)/signup/[[...signup]]/useSignUp.ts b/src/app/[variants]/(auth)/signup/[[...signup]]/useSignUp.ts index 30eb9a8263..eaf6f773ac 100644 --- a/src/app/[variants]/(auth)/signup/[[...signup]]/useSignUp.ts +++ b/src/app/[variants]/(auth)/signup/[[...signup]]/useSignUp.ts @@ -7,6 +7,7 @@ import { useTranslation } from 'react-i18next'; import { type BusinessSignupFomData } from '@/business/client/hooks/useBusinessSignup'; import { useBusinessSignup } from '@/business/client/hooks/useBusinessSignup'; import { message } from '@/components/AntdStaticMethods'; +import { trackLoginOrSignupClicked } from '@/features/User/UserLoginOrSignup/trackLoginOrSignupClicked'; import { signUp } from '@/libs/better-auth/auth-client'; import { useAuthServerConfigStore } from '../../_layout/AuthServerConfigProvider'; @@ -26,6 +27,8 @@ export const useSignUp = () => { const handleSignUp = async (values: SignUpFormValues) => { setLoading(true); + await trackLoginOrSignupClicked({ spm: 'signup.submit.click' }); + try { if (ENABLE_BUSINESS_FEATURES && !(await preSocialSignupCheck(values))) { setLoading(false); diff --git a/src/app/spa/[variants]/[[...path]]/route.ts b/src/app/spa/[variants]/[[...path]]/route.ts index e1ee4d3fe0..fae36823a0 100644 --- a/src/app/spa/[variants]/[[...path]]/route.ts +++ b/src/app/spa/[variants]/[[...path]]/route.ts @@ -131,6 +131,17 @@ function buildAnalyticsConfig(): AnalyticsConfig { }; } + if (analyticsEnv.ENABLED_X_ADS && analyticsEnv.X_ADS_PIXEL_ID) { + config.xAds = { + eventIds: { + login_or_signup_clicked: analyticsEnv.X_ADS_LOGIN_OR_SIGNUP_CLICKED_EVENT_ID, + main_page_view: analyticsEnv.X_ADS_MAIN_PAGE_VIEW_EVENT_ID, + }, + pixelId: analyticsEnv.X_ADS_PIXEL_ID, + purchaseEventId: analyticsEnv.X_ADS_PURCHASE_EVENT_ID, + }; + } + if (analyticsEnv.REACT_SCAN_MONITOR_API_KEY) { config.reactScan = { apiKey: analyticsEnv.REACT_SCAN_MONITOR_API_KEY }; } diff --git a/src/components/Analytics/HomePageTracker.tsx b/src/components/Analytics/HomePageTracker.tsx new file mode 100644 index 0000000000..451bbebc5a --- /dev/null +++ b/src/components/Analytics/HomePageTracker.tsx @@ -0,0 +1,31 @@ +'use client'; + +import { useAnalytics } from '@lobehub/analytics/react'; +import { memo, useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; + +const HomePageTracker = memo(() => { + const { analytics } = useAnalytics(); + const { pathname } = useLocation(); + + useEffect(() => { + if (!analytics || pathname !== '/') return; + + const timer = setTimeout(() => { + analytics.track({ + name: 'main_page_view', + properties: { + spm: 'homepage.main_page.view', + }, + }); + }, 1000); + + return () => clearTimeout(timer); + }, [analytics, pathname]); + + return null; +}); + +HomePageTracker.displayName = 'HomePageTracker'; + +export default HomePageTracker; diff --git a/src/components/Analytics/LobeAnalyticsProvider.tsx b/src/components/Analytics/LobeAnalyticsProvider.tsx index 32f9d94978..f4ae57ee8c 100644 --- a/src/components/Analytics/LobeAnalyticsProvider.tsx +++ b/src/components/Analytics/LobeAnalyticsProvider.tsx @@ -3,11 +3,12 @@ import { type GoogleAnalyticsProviderConfig, type PostHogProviderAnalyticsConfig, + type XAdsProviderAnalyticsConfig, } from '@lobehub/analytics'; import { createSingletonAnalytics } from '@lobehub/analytics'; import { AnalyticsProvider } from '@lobehub/analytics/react'; import { type ReactNode } from 'react'; -import { memo, useMemo } from 'react'; +import { memo, useRef } from 'react'; import { BUSINESS_LINE } from '@/const/analytics'; import { isDesktop } from '@/const/version'; @@ -17,28 +18,32 @@ type Props = { children: ReactNode; ga4Config: GoogleAnalyticsProviderConfig; postHogConfig: PostHogProviderAnalyticsConfig; + xAdsConfig: XAdsProviderAnalyticsConfig; }; let analyticsInstance: ReturnType | null = null; export const LobeAnalyticsProvider = memo( - ({ children, ga4Config, postHogConfig }: Props) => { - const analytics = useMemo(() => { - if (analyticsInstance) { - return analyticsInstance; - } + ({ children, ga4Config, postHogConfig, xAdsConfig }: Props) => { + const analyticsRef = useRef | null>(null); - analyticsInstance = createSingletonAnalytics({ - business: BUSINESS_LINE, - debug: isDev, - providers: { - ga4: ga4Config, - posthog: postHogConfig, - }, - }); + if (!analyticsRef.current) { + analyticsRef.current = + analyticsInstance || + createSingletonAnalytics({ + business: BUSINESS_LINE, + debug: isDev, + providers: { + ga4: ga4Config, + posthog: postHogConfig, + xAds: xAdsConfig, + }, + }); - return analyticsInstance; - }, []); + analyticsInstance = analyticsRef.current; + } + + const analytics = analyticsRef.current; if (!analytics) return children; diff --git a/src/components/Analytics/LobeAnalyticsProviderWrapper.tsx b/src/components/Analytics/LobeAnalyticsProviderWrapper.tsx index 4b449a7c4b..202176f3a8 100644 --- a/src/components/Analytics/LobeAnalyticsProviderWrapper.tsx +++ b/src/components/Analytics/LobeAnalyticsProviderWrapper.tsx @@ -30,6 +30,13 @@ export const LobeAnalyticsProviderWrapper = memo(({ children }) => { key: analytics?.posthog?.key ?? '', person_profiles: 'always', }} + xAdsConfig={{ + debug: isDev, + eventIds: analytics?.xAds?.eventIds, + enabled: !!analytics?.xAds?.pixelId, + pixelId: analytics?.xAds?.pixelId ?? '', + purchaseEventId: analytics?.xAds?.purchaseEventId, + }} > {children} diff --git a/src/components/Analytics/MainInterfaceTracker.tsx b/src/components/Analytics/MainInterfaceTracker.tsx deleted file mode 100644 index 800f172c32..0000000000 --- a/src/components/Analytics/MainInterfaceTracker.tsx +++ /dev/null @@ -1,52 +0,0 @@ -'use client'; - -import { useAnalytics } from '@lobehub/analytics/react'; -import { memo, useCallback, useEffect } from 'react'; - -import { getChatStoreState } from '@/store/chat'; -import { displayMessageSelectors } from '@/store/chat/selectors'; -import { useGlobalStore } from '@/store/global'; -import { systemStatusSelectors } from '@/store/global/selectors'; -import { getSessionStoreState } from '@/store/session'; -import { sessionSelectors } from '@/store/session/selectors'; - -const MainInterfaceTracker = memo(() => { - const { analytics } = useAnalytics(); - - const getMainInterfaceAnalyticsData = useCallback(() => { - const currentSession = sessionSelectors.currentSession(getSessionStoreState()); - const activeSessionId = currentSession?.id; - const defaultSessions = sessionSelectors.defaultSessions(getSessionStoreState()); - const showRightPanel = systemStatusSelectors.showRightPanel(useGlobalStore.getState()); - const messages = displayMessageSelectors.activeDisplayMessages(getChatStoreState()); - return { - active_assistant: activeSessionId === 'inbox' ? null : currentSession?.meta?.title || null, - has_chat_history: messages.length > 0, - session_id: activeSessionId ? activeSessionId : 'inbox', - sidebar_state: showRightPanel ? 'expanded' : 'collapsed', - visible_assistants_count: defaultSessions.length, - }; - }, []); - - useEffect(() => { - if (!analytics) return; - - const timer = setTimeout(() => { - analytics.track({ - name: 'main_page_view', - properties: { - ...getMainInterfaceAnalyticsData(), - spm: 'main_page.interface.view', - }, - }); - }, 1000); - - return () => clearTimeout(timer); - }, [analytics, getMainInterfaceAnalyticsData]); - - return null; -}); - -MainInterfaceTracker.displayName = 'MainInterfaceTracker'; - -export default MainInterfaceTracker; diff --git a/src/components/Analytics/X.tsx b/src/components/Analytics/X.tsx new file mode 100644 index 0000000000..21e325287a --- /dev/null +++ b/src/components/Analytics/X.tsx @@ -0,0 +1,48 @@ +'use client'; + +import { createAnalytics, getSingletonAnalyticsOptional } from '@lobehub/analytics'; +import { memo, useEffect, useRef } from 'react'; + +import { BUSINESS_LINE } from '@/const/analytics'; +import { isDev } from '@/utils/env'; + +interface XAdsProps { + eventIds?: Record; + pixelId?: string; + purchaseEventId?: string; +} + +const XAds = memo(({ eventIds, pixelId, purchaseEventId }) => { + const analyticsRef = useRef | null>(null); + + useEffect(() => { + const singletonAnalytics = getSingletonAnalyticsOptional(); + if (singletonAnalytics?.getProvider('xAds')) { + return; + } + + if (!analyticsRef.current) { + analyticsRef.current = createAnalytics({ + business: BUSINESS_LINE, + debug: isDev, + providers: { + xAds: { + debug: isDev, + eventIds, + enabled: !!pixelId, + pixelId: pixelId ?? '', + purchaseEventId, + }, + }, + }); + } + + analyticsRef.current.initialize().catch((error) => { + console.error('[X Ads Bootstrap] Initialization failed:', error); + }); + }, [eventIds, pixelId, purchaseEventId]); + + return null; +}); + +export default XAds; diff --git a/src/components/Analytics/index.tsx b/src/components/Analytics/index.tsx index f24b8999ed..befa2be1ff 100644 --- a/src/components/Analytics/index.tsx +++ b/src/components/Analytics/index.tsx @@ -5,6 +5,7 @@ import dynamic from '@/libs/next/dynamic'; import Desktop from './Desktop'; import Google from './Google'; import Vercel from './Vercel'; +import X from './X'; const Plausible = dynamic(() => import('./Plausible')); const Umami = dynamic(() => import('./Umami')); @@ -20,6 +21,16 @@ const Analytics = () => { {analyticsEnv.ENABLE_GOOGLE_ANALYTICS && ( )} + {analyticsEnv.ENABLED_X_ADS && ( + + )} {analyticsEnv.ENABLED_PLAUSIBLE_ANALYTICS && ( { process.env.CLARITY_PROJECT_ID = 'clarity_id'; process.env.ENABLE_VERCEL_ANALYTICS = '1'; process.env.GOOGLE_ANALYTICS_MEASUREMENT_ID = 'ga_id'; + process.env.X_ADS_PIXEL_ID = 'tw-pixel_id'; + process.env.X_ADS_LOGIN_OR_SIGNUP_CLICKED_EVENT_ID = 'tw-pixel_id-login_or_signup_clicked'; + process.env.X_ADS_MAIN_PAGE_VIEW_EVENT_ID = 'tw-pixel_id-main_page_view'; + process.env.X_ADS_PURCHASE_EVENT_ID = 'tw-pixel_id-purchase_event_id'; const config = getAnalyticsConfig(); @@ -42,6 +46,11 @@ describe('getAnalyticsConfig', () => { DEBUG_VERCEL_ANALYTICS: false, ENABLE_GOOGLE_ANALYTICS: true, GOOGLE_ANALYTICS_MEASUREMENT_ID: 'ga_id', + ENABLED_X_ADS: true, + X_ADS_PIXEL_ID: 'tw-pixel_id', + X_ADS_LOGIN_OR_SIGNUP_CLICKED_EVENT_ID: 'tw-pixel_id-login_or_signup_clicked', + X_ADS_MAIN_PAGE_VIEW_EVENT_ID: 'tw-pixel_id-main_page_view', + X_ADS_PURCHASE_EVENT_ID: 'tw-pixel_id-purchase_event_id', }); }); }); diff --git a/src/envs/analytics.ts b/src/envs/analytics.ts index 4cbec408c6..219f022448 100644 --- a/src/envs/analytics.ts +++ b/src/envs/analytics.ts @@ -26,6 +26,12 @@ export const getAnalyticsConfig = () => { ENABLE_GOOGLE_ANALYTICS: z.boolean(), GOOGLE_ANALYTICS_MEASUREMENT_ID: z.string().optional(), + ENABLED_X_ADS: z.boolean(), + X_ADS_PIXEL_ID: z.string().optional(), + X_ADS_LOGIN_OR_SIGNUP_CLICKED_EVENT_ID: z.string().optional(), + X_ADS_MAIN_PAGE_VIEW_EVENT_ID: z.string().optional(), + X_ADS_PURCHASE_EVENT_ID: z.string().optional(), + REACT_SCAN_MONITOR_API_KEY: z.string().optional(), }, runtimeEnv: { @@ -57,6 +63,13 @@ export const getAnalyticsConfig = () => { ENABLE_GOOGLE_ANALYTICS: !!process.env.GOOGLE_ANALYTICS_MEASUREMENT_ID, GOOGLE_ANALYTICS_MEASUREMENT_ID: process.env.GOOGLE_ANALYTICS_MEASUREMENT_ID, + // X Ads + ENABLED_X_ADS: !!process.env.X_ADS_PIXEL_ID, + X_ADS_PIXEL_ID: process.env.X_ADS_PIXEL_ID, + X_ADS_LOGIN_OR_SIGNUP_CLICKED_EVENT_ID: process.env.X_ADS_LOGIN_OR_SIGNUP_CLICKED_EVENT_ID, + X_ADS_MAIN_PAGE_VIEW_EVENT_ID: process.env.X_ADS_MAIN_PAGE_VIEW_EVENT_ID, + X_ADS_PURCHASE_EVENT_ID: process.env.X_ADS_PURCHASE_EVENT_ID, + // React Scan Monitor // https://dashboard.react-scan.com REACT_SCAN_MONITOR_API_KEY: process.env.REACT_SCAN_MONITOR_API_KEY, diff --git a/src/features/User/UserLoginOrSignup/Community.tsx b/src/features/User/UserLoginOrSignup/Community.tsx index 8f2b64b8f8..d19cb42c20 100644 --- a/src/features/User/UserLoginOrSignup/Community.tsx +++ b/src/features/User/UserLoginOrSignup/Community.tsx @@ -1,22 +1,15 @@ -import { useAnalytics } from '@lobehub/analytics/react'; import { Button, Flexbox } from '@lobehub/ui'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import UserInfo from '../UserInfo'; +import { trackLoginOrSignupClicked } from './trackLoginOrSignupClicked'; const UserLoginOrSignup = memo<{ onClick: () => void }>(({ onClick }) => { const { t } = useTranslation('auth'); - const { analytics } = useAnalytics(); const handleClick = () => { - analytics?.track({ - name: 'login_or_signup_clicked', - properties: { - spm: 'homepage.login_or_signup.click', - }, - }); - + void trackLoginOrSignupClicked({ spm: 'homepage.login_or_signup.click' }); onClick(); }; diff --git a/src/features/User/UserLoginOrSignup/trackLoginOrSignupClicked.ts b/src/features/User/UserLoginOrSignup/trackLoginOrSignupClicked.ts new file mode 100644 index 0000000000..4e2f67de9c --- /dev/null +++ b/src/features/User/UserLoginOrSignup/trackLoginOrSignupClicked.ts @@ -0,0 +1,34 @@ +import { getSingletonAnalyticsOptional } from '@lobehub/analytics'; + +interface TrackLoginOrSignupClickedParams { + provider?: string; + spm: string; +} + +export const trackLoginOrSignupClicked = ({ + provider, + spm, +}: TrackLoginOrSignupClickedParams) => { + const analytics = getSingletonAnalyticsOptional(); + if (!analytics) return Promise.resolve(); + + const sendEvent = async () => { + const status = analytics.getStatus(); + + if (!status.initialized) { + await analytics.initialize(); + } + + await analytics.track({ + name: 'login_or_signup_clicked', + properties: { + ...(provider && { provider }), + spm, + }, + }); + }; + + return sendEvent().catch((error) => { + console.error('Failed to track login_or_signup_clicked:', error); + }); +}; diff --git a/src/layout/AnalyticsRSCProvider.tsx b/src/layout/AnalyticsRSCProvider.tsx new file mode 100644 index 0000000000..9c39e5832b --- /dev/null +++ b/src/layout/AnalyticsRSCProvider.tsx @@ -0,0 +1,45 @@ +import type { ReactNode } from 'react'; + +import { LobeAnalyticsProvider } from '@/components/Analytics/LobeAnalyticsProvider'; +import { analyticsEnv } from '@/envs/analytics'; +import { isDev } from '@/utils/env'; + +interface AnalyticsRSCProviderProps { + children: ReactNode; +} + +const AnalyticsRSCProvider = ({ children }: AnalyticsRSCProviderProps) => { + return ( + + {children} + + ); +}; + +export default AnalyticsRSCProvider; diff --git a/src/routes/(main)/agent/index.tsx b/src/routes/(main)/agent/index.tsx index f3f0da7a69..b949bdf165 100644 --- a/src/routes/(main)/agent/index.tsx +++ b/src/routes/(main)/agent/index.tsx @@ -3,8 +3,6 @@ import { Flexbox } from '@lobehub/ui'; import { memo } from 'react'; -import MainInterfaceTracker from '@/components/Analytics/MainInterfaceTracker'; - import Conversation from './features/Conversation'; import AgentWorkingSidebar from './features/Conversation/WorkingSidebar'; import PageTitle from './features/PageTitle'; @@ -25,7 +23,6 @@ const ChatPage = memo(() => { - ); diff --git a/src/routes/(main)/group/index.tsx b/src/routes/(main)/group/index.tsx index 2514557e77..77acff3106 100644 --- a/src/routes/(main)/group/index.tsx +++ b/src/routes/(main)/group/index.tsx @@ -3,8 +3,6 @@ import { Flexbox } from '@lobehub/ui'; import { memo } from 'react'; -import MainInterfaceTracker from '@/components/Analytics/MainInterfaceTracker'; - import Conversation from './features/Conversation'; import PageTitle from './features/PageTitle'; import Portal from './features/Portal'; @@ -23,7 +21,6 @@ const ChatPage = memo(() => { - ); diff --git a/src/routes/(main)/home/index.tsx b/src/routes/(main)/home/index.tsx index 9a7c822c59..40608d8b5d 100644 --- a/src/routes/(main)/home/index.tsx +++ b/src/routes/(main)/home/index.tsx @@ -2,6 +2,7 @@ import { Flexbox } from '@lobehub/ui'; import { type FC } from 'react'; import { useLocation } from 'react-router-dom'; +import HomePageTracker from '@/components/Analytics/HomePageTracker'; import PageTitle from '@/components/PageTitle'; import NavHeader from '@/features/NavHeader'; import WideScreenContainer from '@/features/WideScreenContainer'; @@ -15,6 +16,7 @@ const Home: FC = () => { return ( <> {isHomeRoute && } + } /> diff --git a/src/routes/(mobile)/chat/index.tsx b/src/routes/(mobile)/chat/index.tsx index a55d43a5a5..93ecfd8b2a 100644 --- a/src/routes/(mobile)/chat/index.tsx +++ b/src/routes/(mobile)/chat/index.tsx @@ -2,7 +2,6 @@ import { memo } from 'react'; -import MainInterfaceTracker from '@/components/Analytics/MainInterfaceTracker'; import ConversationArea from '@/routes/(main)/agent/features/Conversation/ConversationArea'; import PageTitle from '@/routes/(main)/agent/features/PageTitle'; import PortalPanel from '@/routes/(main)/agent/features/Portal/features/PortalPanel'; @@ -17,7 +16,6 @@ const MobileChatPage = memo(() => { - ); diff --git a/src/routes/share/t/[id]/_layout/index.tsx b/src/routes/share/t/[id]/_layout/index.tsx index d23a48abf5..80bb1e6f49 100644 --- a/src/routes/share/t/[id]/_layout/index.tsx +++ b/src/routes/share/t/[id]/_layout/index.tsx @@ -10,6 +10,7 @@ import { Link, Outlet } from 'react-router-dom'; import { ProductLogo } from '@/components/Branding'; import Loading from '@/components/Loading/BrandTextLoading'; +import { trackLoginOrSignupClicked } from '@/features/User/UserLoginOrSignup/trackLoginOrSignupClicked'; import { useIsDark } from '@/hooks/useIsDark'; import { useUserStore } from '@/store/user'; import { authSelectors } from '@/store/user/slices/auth/selectors'; @@ -44,7 +45,18 @@ const ShareTopicLayout = memo(({ children }) => { ) : ( - + { + event.preventDefault(); + void trackLoginOrSignupClicked({ spm: 'share.logo_to_signin.click' }).finally( + () => { + window.location.href = '/signin'; + }, + ); + }} + > )} diff --git a/src/routes/share/t/[id]/index.tsx b/src/routes/share/t/[id]/index.tsx index 7cf9f5971b..b353ed0bfb 100644 --- a/src/routes/share/t/[id]/index.tsx +++ b/src/routes/share/t/[id]/index.tsx @@ -10,6 +10,7 @@ import useSWR from 'swr'; import NotFound from '@/components/404'; import Loading from '@/components/Loading/BrandTextLoading'; +import { trackLoginOrSignupClicked } from '@/features/User/UserLoginOrSignup/trackLoginOrSignupClicked'; import { lambdaClient } from '@/libs/trpc/client'; import ActionBar from './features/ActionBar'; @@ -59,7 +60,18 @@ const ShareTopicPage = memo(() => { status={''} title={t('sharePage.error.unauthorized.title')} extra={ - } diff --git a/src/types/spaServerConfig.ts b/src/types/spaServerConfig.ts index 47bf6957eb..106fad9ab6 100644 --- a/src/types/spaServerConfig.ts +++ b/src/types/spaServerConfig.ts @@ -10,6 +10,11 @@ export interface AnalyticsConfig { reactScan?: { apiKey: string }; umami?: { scriptUrl: string; websiteId: string }; vercel?: { debug: boolean; enabled: boolean }; + xAds?: { + eventIds?: Record; + pixelId: string; + purchaseEventId?: string; + }; } export interface SPAClientEnv {