export const GA_MEASUREMENT_ID = "G-QTT9QT0T3S"; export const GOOGLE_ADS_ID = "AW-18432694647"; export const CONSENT_STORAGE_KEY = "evandro.analytics-consent.v1"; export const GA_DISABLE_KEY = "ga-disable-G-QTT9QT0T3S"; export type AnalyticsConsent = "granted" | "denied"; export type Gtag = (...args: unknown[]) => void; declare global { interface Window { dataLayer?: IArguments[]; "ga-disable-G-QTT9QT0T3S"?: boolean; gtag?: Gtag; } } export function readConsent(): AnalyticsConsent | null { try { const value = localStorage.getItem(CONSENT_STORAGE_KEY); return value === "granted" || value === "denied" ? value : null; } catch { return null; } } function analyticsPagePath(): string { const path = window.location.pathname.replace(/\/$/, "") || "/"; switch (path) { case "/": case "/servicos": case "/cases": case "/curriculo": case "/privacidade": case "/ia": return path; default: // Route templates keep arbitrary slugs and personal data out of metrics. if (path.startsWith("/servicos/")) return "/servicos/[slug]"; if (path.startsWith("/cases/")) return "/cases/[slug]"; return "/other"; } } type EventParams = Record; /** * Sends one GA4 event only while the visitor has granted consent; every other state is silent. * Callers must supply static labels, never form values or destination URLs. * Returns true when the event was handed to gtag, so the Ads conversion is only sent for a counted click. */ function sendEvent(name: string, params: EventParams): boolean { if ( process.env.NODE_ENV !== "production" || typeof window === "undefined" || readConsent() !== "granted" || window[GA_DISABLE_KEY] === true || typeof window.gtag !== "function" ) { return false; } try { const pagePath = analyticsPagePath(); window.gtag("event", name, { send_to: GA_MEASUREMENT_ID, ...params, page_path: pagePath, // Override gtag's URL defaults so query/hash/referrer data cannot leak. page_location: `https://evandro.dev.br${pagePath}`, page_referrer: "", }); return true; } catch { // Analytics must never interrupt navigation. Dropped events are not replayed. return false; } } /** * Google Ads conversion for the same click, only when a conversion label is configured. * Without NEXT_PUBLIC_GOOGLE_ADS_CONVERSION_LABEL nothing is sent to Ads. */ function sendAdsConversion(): void { const label = process.env.NEXT_PUBLIC_GOOGLE_ADS_CONVERSION_LABEL; if (!label) return; try { window.gtag?.("event", "conversion", { send_to: `${GOOGLE_ADS_ID}/${label}`, }); } catch { // Same rule as above: a blocked tag never interrupts navigation. } } /** Measures an attempted handoff, not an opened app, delivered message or lead. */ export function trackWhatsAppOpen({ service, placement, }: { service: string; placement: string; }): void { if (sendEvent("whatsapp_open", { service, placement })) sendAdsConversion(); } /** Measures a click on a mailto: link, not a sent e-mail. */ export function trackEmailClick({ placement }: { placement: string }): void { if (sendEvent("email_click", { placement })) sendAdsConversion(); } /** Measures a click on a social profile; `network` is a static name such as "linkedin". */ export function trackSocialClick({ network }: { network: string }): void { sendEvent("social_click", { network }); } /** Measures a click on the résumé PDF download. */ export function trackCvDownload({ placement }: { placement: string }): void { sendEvent("cv_download", { placement }); } /** O rodapé pede a reabertura da barra de cookies; quem a monta (GoogleAnalytics) escuta este evento. */ export const OPEN_COOKIE_PREFERENCES_EVENT = "evandro:open-cookie-preferences"; /** Id da barra de cookies: o menu do celular a inclui na armadilha de foco enquanto ela está aberta (Header). */ export const COOKIE_BAR_ID = "cookie-bar"; export function openCookiePreferences(): void { if (typeof window === "undefined") return; window.dispatchEvent(new Event(OPEN_COOKIE_PREFERENCES_EVENT)); }