mirror of
https://github.com/nowcommunity/nowdatabase.git
synced 2026-10-12 19:13:52 +03:00
feat: optional GA4 page view tracking
This commit is contained in:
@@ -39,3 +39,7 @@ VITE_RUNNING_ENV: dev
|
|||||||
|
|
||||||
VITE_BACKEND_URL: http://localhost:4000
|
VITE_BACKEND_URL: http://localhost:4000
|
||||||
VITE_HMR_PORT: 5173
|
VITE_HMR_PORT: 5173
|
||||||
|
|
||||||
|
# Optional: enables Google Analytics (GA4) page-view tracking in the frontend when set.
|
||||||
|
# Disabled by default, and never runs in `VITE_RUNNING_ENV: dev`.
|
||||||
|
VITE_GA4_MEASUREMENT_ID: ""
|
||||||
|
|||||||
@@ -67,6 +67,13 @@ Some DB fields use MariaDB `DECIMAL` and are represented as `Decimal` in Prisma-
|
|||||||
- Prefer explicit parsing/conversion at usage points if numeric operations are required.
|
- Prefer explicit parsing/conversion at usage points if numeric operations are required.
|
||||||
- `estimate_temp` additionally has validator bounds to avoid DB errors from out-of-range values (`-999.9` to `999.9`).
|
- `estimate_temp` additionally has validator bounds to avoid DB errors from out-of-range values (`-999.9` to `999.9`).
|
||||||
|
|
||||||
|
**Analytics (optional)**
|
||||||
|
|
||||||
|
The frontend can optionally send GA4 page-view events if configured.
|
||||||
|
|
||||||
|
- Set `VITE_GA4_MEASUREMENT_ID` to your GA4 measurement id (for example `G-XXXXXXXXXX`).
|
||||||
|
- Tracking is disabled by default and never runs when `VITE_RUNNING_ENV` is `dev`.
|
||||||
|
|
||||||
**Detailed documentation for editingComponents**
|
**Detailed documentation for editingComponents**
|
||||||
|
|
||||||
- [RadioSelector](./components/editingComponents/RadioSelector.md)
|
- [RadioSelector](./components/editingComponents/RadioSelector.md)
|
||||||
|
|||||||
@@ -1,11 +1,19 @@
|
|||||||
import { Container } from '@mui/material'
|
import { Container } from '@mui/material'
|
||||||
import Grid from '@mui/material/Grid2'
|
import Grid from '@mui/material/Grid2'
|
||||||
import { Outlet } from 'react-router-dom'
|
import { Outlet, useLocation } from 'react-router-dom'
|
||||||
import { NavBar } from './components/NavBar'
|
import { NavBar } from './components/NavBar'
|
||||||
import { Notification, NotificationContextProvider } from './components/Notification'
|
import { Notification, NotificationContextProvider } from './components/Notification'
|
||||||
import { Footer } from './Footer'
|
import { Footer } from './Footer'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import { trackPageView } from './util/analytics'
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
|
const location = useLocation()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
trackPageView(`${location.pathname}${location.search}`)
|
||||||
|
}, [location.pathname, location.search])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NotificationContextProvider>
|
<NotificationContextProvider>
|
||||||
<Notification />
|
<Notification />
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export const ENV = 'dev'
|
export const ENV = 'dev'
|
||||||
export const BACKEND_URL = 'http://localhost'
|
export const BACKEND_URL = 'http://localhost'
|
||||||
export const ENABLE_WRITE = true
|
export const ENABLE_WRITE = true
|
||||||
|
export const GA4_MEASUREMENT_ID = undefined
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { ENV, GA4_MEASUREMENT_ID } from './config'
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
dataLayer?: unknown[]
|
||||||
|
gtag?: (...args: unknown[]) => void
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const canTrack = () => {
|
||||||
|
if (ENV === 'dev') return false
|
||||||
|
if (!GA4_MEASUREMENT_ID) return false
|
||||||
|
|
||||||
|
// Honor "Do Not Track" signals when present.
|
||||||
|
const win = window as Window & { doNotTrack?: string }
|
||||||
|
const dnt = navigator.doNotTrack === '1' || win.doNotTrack === '1'
|
||||||
|
if (dnt) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
let initialized = false
|
||||||
|
|
||||||
|
const ensureGtagLoaded = () => {
|
||||||
|
if (initialized) return
|
||||||
|
if (!canTrack()) return
|
||||||
|
|
||||||
|
if (typeof document === 'undefined') return
|
||||||
|
|
||||||
|
const srcId = 'ga4-gtag-src'
|
||||||
|
if (!document.getElementById(srcId)) {
|
||||||
|
const script = document.createElement('script')
|
||||||
|
script.id = srcId
|
||||||
|
script.async = true
|
||||||
|
script.src = `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(GA4_MEASUREMENT_ID!)}`
|
||||||
|
document.head.appendChild(script)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.dataLayer = window.dataLayer ?? []
|
||||||
|
window.gtag =
|
||||||
|
window.gtag ??
|
||||||
|
((...args: unknown[]) => {
|
||||||
|
window.dataLayer?.push(args)
|
||||||
|
})
|
||||||
|
|
||||||
|
window.gtag('js', new Date())
|
||||||
|
window.gtag('config', GA4_MEASUREMENT_ID, { send_page_view: false, anonymize_ip: true })
|
||||||
|
|
||||||
|
initialized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
export const trackPageView = (path: string) => {
|
||||||
|
if (!canTrack()) return
|
||||||
|
ensureGtagLoaded()
|
||||||
|
window.gtag?.('event', 'page_view', { page_path: path })
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
export const ENV = import.meta.env.VITE_RUNNING_ENV as 'dev' | 'staging' | 'prod'
|
export const ENV = import.meta.env.VITE_RUNNING_ENV as 'dev' | 'staging' | 'prod'
|
||||||
export const BACKEND_URL = import.meta.env.VITE_BACKEND_URL as string
|
export const BACKEND_URL = import.meta.env.VITE_BACKEND_URL as string
|
||||||
export const ENABLE_WRITE = import.meta.env.VITE_ENABLE_WRITE === 'true'
|
export const ENABLE_WRITE = import.meta.env.VITE_ENABLE_WRITE === 'true'
|
||||||
|
export const GA4_MEASUREMENT_ID = import.meta.env.VITE_GA4_MEASUREMENT_ID as string | undefined
|
||||||
|
|||||||
Reference in New Issue
Block a user