mirror of
https://github.com/lobehub/lobehub
synced 2026-04-21 09:37:28 +00:00
✨ feat: add x ads tracking entry points (#13986)
* ✨ feat: add x ads tracking entry points * 🔨 chore: bump analytics to v1.6.2 * 🐛 fix: add auth analytics provider entry
This commit is contained in:
parent
ed64e2b8af
commit
5dd7cd7408
24 changed files with 298 additions and 91 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
>
|
||||
<AuthProvider>{children}</AuthProvider>
|
||||
<AnalyticsRSCProvider>
|
||||
<AuthProvider>{children}</AuthProvider>
|
||||
</AnalyticsRSCProvider>
|
||||
</AuthServerConfigProvider>
|
||||
</AuthThemeLite>
|
||||
</NextThemeProvider>
|
||||
|
|
|
|||
|
|
@ -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<SignInFormValues, 'email'>) => {
|
||||
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<SignInFormValues, 'password'>) => {
|
||||
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 () => {
|
||||
|
|
|
|||
|
|
@ -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 = (
|
||||
<Text>
|
||||
{t('betterAuth.signup.hasAccount')}{' '}
|
||||
<Link href={`/signin?${searchParams.toString()}`}>{t('betterAuth.signup.signinLink')}</Link>
|
||||
<Link
|
||||
href={`/signin?${searchParams.toString()}`}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
void trackLoginOrSignupClicked({ spm: 'signup.go_to_signin.click' }).finally(() => {
|
||||
window.location.href = `/signin?${searchParams.toString()}`;
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t('betterAuth.signup.signinLink')}
|
||||
</Link>
|
||||
</Text>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
|||
31
src/components/Analytics/HomePageTracker.tsx
Normal file
31
src/components/Analytics/HomePageTracker.tsx
Normal file
|
|
@ -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;
|
||||
|
|
@ -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<typeof createSingletonAnalytics> | 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<ReturnType<typeof createSingletonAnalytics> | 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;
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,13 @@ export const LobeAnalyticsProviderWrapper = memo<Props>(({ 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}
|
||||
</LobeAnalyticsProvider>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
48
src/components/Analytics/X.tsx
Normal file
48
src/components/Analytics/X.tsx
Normal file
|
|
@ -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<string, string | undefined>;
|
||||
pixelId?: string;
|
||||
purchaseEventId?: string;
|
||||
}
|
||||
|
||||
const XAds = memo<XAdsProps>(({ eventIds, pixelId, purchaseEventId }) => {
|
||||
const analyticsRef = useRef<ReturnType<typeof createAnalytics> | 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;
|
||||
|
|
@ -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 && (
|
||||
<Google gaId={analyticsEnv.GOOGLE_ANALYTICS_MEASUREMENT_ID} />
|
||||
)}
|
||||
{analyticsEnv.ENABLED_X_ADS && (
|
||||
<X
|
||||
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}
|
||||
/>
|
||||
)}
|
||||
{analyticsEnv.ENABLED_PLAUSIBLE_ANALYTICS && (
|
||||
<Plausible
|
||||
domain={analyticsEnv.PLAUSIBLE_DOMAIN}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@ describe('getAnalyticsConfig', () => {
|
|||
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',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
};
|
||||
45
src/layout/AnalyticsRSCProvider.tsx
Normal file
45
src/layout/AnalyticsRSCProvider.tsx
Normal file
|
|
@ -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 (
|
||||
<LobeAnalyticsProvider
|
||||
ga4Config={{
|
||||
debug: isDev,
|
||||
enabled: !!analyticsEnv.GOOGLE_ANALYTICS_MEASUREMENT_ID,
|
||||
gtagConfig: {
|
||||
debug_mode: isDev,
|
||||
},
|
||||
measurementId: analyticsEnv.GOOGLE_ANALYTICS_MEASUREMENT_ID ?? '',
|
||||
}}
|
||||
postHogConfig={{
|
||||
debug: analyticsEnv.DEBUG_POSTHOG_ANALYTICS,
|
||||
enabled: analyticsEnv.ENABLED_POSTHOG_ANALYTICS,
|
||||
host: analyticsEnv.POSTHOG_HOST,
|
||||
key: analyticsEnv.POSTHOG_KEY ?? '',
|
||||
person_profiles: 'always',
|
||||
}}
|
||||
xAdsConfig={{
|
||||
debug: isDev,
|
||||
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,
|
||||
},
|
||||
enabled: analyticsEnv.ENABLED_X_ADS,
|
||||
pixelId: analyticsEnv.X_ADS_PIXEL_ID ?? '',
|
||||
purchaseEventId: analyticsEnv.X_ADS_PURCHASE_EVENT_ID,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</LobeAnalyticsProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default AnalyticsRSCProvider;
|
||||
|
|
@ -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(() => {
|
|||
<Portal />
|
||||
<AgentWorkingSidebar />
|
||||
</Flexbox>
|
||||
<MainInterfaceTracker />
|
||||
<TelemetryNotification mobile={false} />
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
|||
<Conversation />
|
||||
<Portal />
|
||||
</Flexbox>
|
||||
<MainInterfaceTracker />
|
||||
<TelemetryNotification mobile={false} />
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 && <PageTitle title="" />}
|
||||
<HomePageTracker />
|
||||
<NavHeader right={<Flexbox horizontal align="center" />} />
|
||||
<Flexbox height={'100%'} style={{ overflowY: 'auto', paddingBottom: '16vh' }} width={'100%'}>
|
||||
<WideScreenContainer>
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
|||
<ConversationArea />
|
||||
<Topic />
|
||||
<PortalPanel mobile />
|
||||
<MainInterfaceTracker />
|
||||
<TelemetryNotification mobile />
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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<PropsWithChildren>(({ children }) => {
|
|||
<ProductLogo size={32} />
|
||||
</Link>
|
||||
) : (
|
||||
<NextLink href="/signin" style={{ color: 'inherit' }}>
|
||||
<NextLink
|
||||
href="/signin"
|
||||
style={{ color: 'inherit' }}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
void trackLoginOrSignupClicked({ spm: 'share.logo_to_signin.click' }).finally(
|
||||
() => {
|
||||
window.location.href = '/signin';
|
||||
},
|
||||
);
|
||||
}}
|
||||
>
|
||||
<ProductLogo size={32} />
|
||||
</NextLink>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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={
|
||||
<Button href="/signin" type="primary">
|
||||
<Button
|
||||
href="/signin"
|
||||
type="primary"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
void trackLoginOrSignupClicked({
|
||||
spm: 'share.unauthorized.signin.click',
|
||||
}).finally(() => {
|
||||
window.location.href = '/signin';
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t('sharePage.error.unauthorized.action')}
|
||||
</Button>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,11 @@ export interface AnalyticsConfig {
|
|||
reactScan?: { apiKey: string };
|
||||
umami?: { scriptUrl: string; websiteId: string };
|
||||
vercel?: { debug: boolean; enabled: boolean };
|
||||
xAds?: {
|
||||
eventIds?: Record<string, string | undefined>;
|
||||
pixelId: string;
|
||||
purchaseEventId?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SPAClientEnv {
|
||||
|
|
|
|||
Loading…
Reference in a new issue