feat: add GA4 custom event tracking for configurator and voice agent
All checks were successful
Build & Push / build-and-push (push) Successful in 1m23s

Events tracked:
- configurator_step_completed (with step number)
- configurator_brief_generated (with services and AI flag)
- voice_agent_started
- voice_agent_brief_generated

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-07 20:57:31 -04:00
parent 2e23e26fc1
commit 5710d27663
4 changed files with 28 additions and 1 deletions

View File

@@ -3,6 +3,7 @@
import { useState } from 'react'; import { useState } from 'react';
import { useLocale, useTranslations } from 'next-intl'; import { useLocale, useTranslations } from 'next-intl';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import { trackEvent } from '@/lib/analytics';
import StepServices from './StepServices'; import StepServices from './StepServices';
import StepDetails from './StepDetails'; import StepDetails from './StepDetails';
import StepContact from './StepContact'; import StepContact from './StepContact';
@@ -90,7 +91,11 @@ export default function WizardContainer() {
const goNext = () => { const goNext = () => {
setDirection(1); setDirection(1);
setCurrentStep((prev) => Math.min(prev + 1, 4) as 1 | 2 | 3 | 4); setCurrentStep((prev) => {
const next = Math.min(prev + 1, 4) as 1 | 2 | 3 | 4;
trackEvent('configurator_step_completed', { step: prev });
return next;
});
}; };
const goBack = () => { const goBack = () => {
@@ -131,6 +136,10 @@ export default function WizardContainer() {
setDirection(1); setDirection(1);
setIsGenerating(false); setIsGenerating(false);
setCurrentStep(4); setCurrentStep(4);
trackEvent('configurator_brief_generated', {
services: formData.services.join(','),
ai_enabled: formData.aiEnabled,
});
} catch { } catch {
setSubmitError(t('errors.network')); setSubmitError(t('errors.network'));
setIsGenerating(false); setIsGenerating(false);

View File

@@ -5,6 +5,7 @@ import { useLocale, useTranslations } from 'next-intl';
import { motion, AnimatePresence } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import { MessageCircle } from 'lucide-react'; import { MessageCircle } from 'lucide-react';
import { revealVariants, staggerContainer, viewportOnce } from '@/lib/animations'; import { revealVariants, staggerContainer, viewportOnce } from '@/lib/animations';
import { trackEvent } from '@/lib/analytics';
import VoiceAgentProvider from '@/components/configurator/VoiceAgentProvider'; import VoiceAgentProvider from '@/components/configurator/VoiceAgentProvider';
import VoiceAgent from '@/components/configurator/VoiceAgent'; import VoiceAgent from '@/components/configurator/VoiceAgent';
import StepComplete from '@/components/configurator/StepComplete'; import StepComplete from '@/components/configurator/StepComplete';
@@ -36,6 +37,7 @@ export default function Discovery() {
const handleOpen = () => { const handleOpen = () => {
setIsOpen(true); setIsOpen(true);
trackEvent('voice_agent_started');
// Scroll to panel after it renders // Scroll to panel after it renders
requestAnimationFrame(() => { requestAnimationFrame(() => {
panelRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }); panelRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
@@ -44,6 +46,7 @@ export default function Discovery() {
const handleComplete = (brief: string, formData: WizardFormData) => { const handleComplete = (brief: string, formData: WizardFormData) => {
setCompleted({ brief, formData }); setCompleted({ brief, formData });
trackEvent('voice_agent_brief_generated');
}; };
const handleReset = () => { const handleReset = () => {

12
src/lib/analytics.ts Normal file
View File

@@ -0,0 +1,12 @@
/**
* Send a custom event to Google Analytics 4.
* Safe to call anywhere — silently no-ops if gtag isn't loaded.
*/
export function trackEvent(
eventName: string,
params?: Record<string, string | number | boolean>,
) {
if (typeof window !== 'undefined' && typeof window.gtag === 'function') {
window.gtag('event', eventName, params)
}
}

3
src/types/global.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
interface Window {
gtag?: (...args: unknown[]) => void
}