diff --git a/frontend/app/api/analytics/events/route.ts b/frontend/app/api/analytics/events/route.ts index 3eb2c857..1abbcbf8 100644 --- a/frontend/app/api/analytics/events/route.ts +++ b/frontend/app/api/analytics/events/route.ts @@ -10,7 +10,7 @@ import { const API_BASE = process.env.POLYWEATHER_API_BASE_URL; const ANALYTICS_ENABLED = - process.env.NEXT_PUBLIC_POLYWEATHER_APP_ANALYTICS === "true"; + process.env.NEXT_PUBLIC_POLYWEATHER_APP_ANALYTICS !== "false"; export async function POST(req: NextRequest) { if (!ANALYTICS_ENABLED) { diff --git a/frontend/components/account/AccountCenter.tsx b/frontend/components/account/AccountCenter.tsx index bbdebd04..a2e68eeb 100644 --- a/frontend/components/account/AccountCenter.tsx +++ b/frontend/components/account/AccountCenter.tsx @@ -45,7 +45,7 @@ import { getCurrentPaymentHost, isPaymentHostAllowed, } from "@/lib/payment-host"; -import { trackAppEvent } from "@/lib/app-analytics"; +import { markAnalyticsOnce, trackAppEvent } from "@/lib/app-analytics"; import { useI18n } from "@/hooks/useI18n"; import { UnlockProOverlay } from "@/components/subscription/UnlockProOverlay"; @@ -927,6 +927,23 @@ export function AccountCenter() { [currentPaymentHost], ); + useEffect(() => { + if (!authIsAuthenticated || !authUserId) return; + const actorKey = authUserId.toLowerCase(); + if (markAnalyticsOnce(`signup_completed:${actorKey}`, "local")) { + trackAppEvent("signup_completed", { + entry: "account_center", + user_id: authUserId, + }); + } + if (markAnalyticsOnce(`dashboard_active:${actorKey}`, "session")) { + trackAppEvent("dashboard_active", { + entry: "account_center", + user_id: authUserId, + }); + } + }, [authIsAuthenticated, authUserId]); + useEffect(() => { let canceled = false; let timeoutId: number | null = null; diff --git a/frontend/components/account/__tests__/paymentShell.test.ts b/frontend/components/account/__tests__/paymentShell.test.ts index d4796277..a46d4d31 100644 --- a/frontend/components/account/__tests__/paymentShell.test.ts +++ b/frontend/components/account/__tests__/paymentShell.test.ts @@ -17,6 +17,14 @@ export function runTests() { const accountCenterSource = fs.readFileSync(accountCenterPath, "utf8"); const serviceWorkerSource = fs.readFileSync(serviceWorkerPath, "utf8"); + const appAnalyticsSource = fs.readFileSync( + path.join(projectRoot, "lib", "app-analytics.ts"), + "utf8", + ); + const analyticsRouteSource = fs.readFileSync( + path.join(projectRoot, "app", "api", "analytics", "events", "route.ts"), + "utf8", + ); assert( accountCenterSource.includes( @@ -48,4 +56,14 @@ export function runTests() { accountCenterSource.includes("只有 USDC 可能无法完成授权或支付"), "payment wallet tab must warn users that Polygon POL/MATIC gas is required in addition to USDC", ); + assert( + !appAnalyticsSource.includes('NEXT_PUBLIC_POLYWEATHER_APP_ANALYTICS === "true"') && + !analyticsRouteSource.includes('NEXT_PUBLIC_POLYWEATHER_APP_ANALYTICS === "true"'), + "app analytics must be enabled by default so ops funnel can collect data without a fragile production env flag", + ); + assert( + accountCenterSource.includes('trackAppEvent("signup_completed"') && + accountCenterSource.includes('trackAppEvent("dashboard_active"'), + "account center must emit signup_completed and dashboard_active so the ops funnel has top-of-funnel data", + ); } diff --git a/frontend/lib/app-analytics.ts b/frontend/lib/app-analytics.ts index 1d2c3272..8879659a 100644 --- a/frontend/lib/app-analytics.ts +++ b/frontend/lib/app-analytics.ts @@ -1,7 +1,7 @@ "use client"; const ANALYTICS_ENABLED = - process.env.NEXT_PUBLIC_POLYWEATHER_APP_ANALYTICS === "true"; + process.env.NEXT_PUBLIC_POLYWEATHER_APP_ANALYTICS !== "false"; type TrackableAnalyticsEvent = | "signup_completed"