diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 74b58bb..f48b0a1 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -40,6 +40,7 @@ jobs: - name: Deploy env: + GOOGLE_ANALYTICS_ID: ${{ secrets.GOOGLE_ANALYTICS_ID }} CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} AUTH_EMAIL_RESEND_API_KEY: ${{ secrets.AUTH_EMAIL_RESEND_API_KEY }} AUTH_SOCIAL_GOOGLE_CLIENT_ID: ${{ secrets.AUTH_SOCIAL_GOOGLE_CLIENT_ID }} diff --git a/.github/workflows/scripts/deploy.js b/.github/workflows/scripts/deploy.js index be09e9b..4509af2 100644 --- a/.github/workflows/scripts/deploy.js +++ b/.github/workflows/scripts/deploy.js @@ -24,6 +24,7 @@ const config = { override: { vars: { MODE: "mixed", + GOOGLE_ANALYTICS_ID: process.env.GOOGLE_ANALYTICS_ID, AUTH_EMAIL_VERIFICATION_ENABLED: "true", AUTH_EMAIL_RESEND_API_KEY: process.env.AUTH_EMAIL_RESEND_API_KEY, AUTH_EMAIL_RESEND_FROM: "Typix ", diff --git a/src/app/routes/__root.tsx b/src/app/routes/__root.tsx index 0407fd5..a423d95 100644 --- a/src/app/routes/__root.tsx +++ b/src/app/routes/__root.tsx @@ -29,6 +29,32 @@ function RootComponent() { // Apply theme and theme color with automatic system theme detection useThemeManager(theme, themeColor, setTheme); + // Conditionally load Google Analytics when an ID is provided + useEffect(() => { + const gaId = import.meta.env.GOOGLE_ANALYTICS_ID as string | undefined; + if (!gaId || gaId.trim() === "") return; + + if (!document.getElementById("ga-gtag")) { + const script = document.createElement("script"); + script.id = "ga-gtag"; + script.async = true; + script.src = `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(gaId)}`; + document.head.appendChild(script); + } + + if (!document.getElementById("ga-inline")) { + const inline = document.createElement("script"); + inline.id = "ga-inline"; + inline.innerHTML = ` + window.dataLayer = window.dataLayer || []; + function gtag(){dataLayer.push(arguments);} + gtag('js', new Date()); + gtag('config', '${gaId}'); + `; + document.head.appendChild(inline); + } + }, []); + // Update loading title when initialization starts useEffect(() => { if (hasAuthResolved && !isInitialized && !initError && i18n.isInitialized) { diff --git a/vite.config.ts b/vite.config.ts index 0eab20e..3065e79 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -75,12 +75,14 @@ export default defineConfig(({ mode }) => { "@": path.resolve(__dirname, "./src"), }, }, + // Only define environment variables that are needed on the client side define: { "import.meta.env.RUNTIME": getEnv("RUNTIME"), "import.meta.env.MODE": getEnv("MODE"), "import.meta.env.AUTH_EMAIL_VERIFICATION_ENABLED": getEnv("AUTH_EMAIL_VERIFICATION_ENABLED"), "import.meta.env.AUTH_SOCIAL_GOOGLE_ENABLED": getEnv("AUTH_SOCIAL_GOOGLE_ENABLED"), "import.meta.env.AUTH_SOCIAL_GITHUB_ENABLED": getEnv("AUTH_SOCIAL_GITHUB_ENABLED"), + "import.meta.env.GOOGLE_ANALYTICS_ID": getEnv("GOOGLE_ANALYTICS_ID"), "import.meta.env.PROVIDER_CLOUDFLARE_BUILTIN": getEnv("PROVIDER_CLOUDFLARE_BUILTIN"), }, }; diff --git a/wrangler.toml b/wrangler.toml index c37e30f..a5ec769 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -30,6 +30,7 @@ binding = "AI" # Authenticated users benefit from cross-device data sync and server-side data persistence. # Provides the best of both worlds: instant local access with optional cloud features. MODE = "client" +GOOGLE_ANALYTICS_ID = "G-0T65G1J5DT" # Authentication configuration, only for mixed mode #