Files
portifolio-evandrodevbr/lib/analytics.ts
T
Evandro Fonseca Junior f8545cd442 feat(home): ofertas que vendem o título, WhatsApp no hero e menu do celular acessível
Três ofertas (site que traz pedidos pelo WhatsApp, sistema para tirar a operação da planilha, revisão de sistema existente) com miniatura e formulário com o tipo escolhido; botão principal do hero abre o WhatsApp; moldura da SEV no hero do celular; menu fecha ao navegar, Esc, foco preso incluindo a barra de cookies e espaço para ela; CinematicHome como componente de servidor com ilha cliente; /ia com H1 de oferta e cartão do que você recebe; botão flutuante em aside com transform; AVIF antes de WebP; copy sem repetir contato direto.
2026-10-14 17:01:36 -03:00

131 lines
4.1 KiB
TypeScript

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<string, string>;
/**
* 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));
}