feat: add GA4 custom event tracking for configurator and voice agent
All checks were successful
Build & Push / build-and-push (push) Successful in 1m23s
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:
@@ -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);
|
||||||
|
|||||||
@@ -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
12
src/lib/analytics.ts
Normal 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
3
src/types/global.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
interface Window {
|
||||||
|
gtag?: (...args: unknown[]) => void
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user