feat(GRO-1794): add booking funnel analytics events
- New analytics utility (src/lib/analytics.ts) with ANALYTICS_EVENTS constants and fireAnalyticsEvent() – thin wrapper over window.dispatchEvent, no-op safe Built for Plausible/GTM integration later. - Public booking wizard (Book.tsx): fires step-transition events at each step (service → time → contact → submit) plus booking_confirmed on the dedicated confirmation page. - Portal BookingFlow (Appointments.tsx): fires equivalent events for the portal booking flow. booking_confirmed fires via useEffect when the inline success state is shown. - BookingErrorPage: fires booking_error on mount (no PII in payload). Events include step name and flow type (public/portal) but contain no PII: no names, emails, phone numbers, or pet names in any payload. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Calendar, Clock, Plus, ChevronRight, ChevronDown, Loader2 } from 'lucide-react';
|
||||
import { ANALYTICS_EVENTS, fireAnalyticsEvent } from '../../lib/analytics';
|
||||
|
||||
export interface Appointment {
|
||||
id: string;
|
||||
@@ -720,6 +721,11 @@ function BookingFlow({ onClose, sessionId }: BookingFlowProps) {
|
||||
const [notes, setNotes] = useState('');
|
||||
const [recurring, setRecurring] = useState('');
|
||||
const [confirmed, setConfirmed] = useState(false);
|
||||
useEffect(() => {
|
||||
if (confirmed) {
|
||||
fireAnalyticsEvent(ANALYTICS_EVENTS.BOOKING_CONFIRMED, { step: "confirmed", flow: "portal" });
|
||||
}
|
||||
}, [confirmed]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
@@ -801,6 +807,7 @@ function BookingFlow({ onClose, sessionId }: BookingFlowProps) {
|
||||
|
||||
if (response.ok) {
|
||||
setConfirmed(true);
|
||||
fireAnalyticsEvent(ANALYTICS_EVENTS.BOOKING_STEP_SUBMIT, { step: "submit", flow: "portal" });
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1500);
|
||||
@@ -876,6 +883,7 @@ function BookingFlow({ onClose, sessionId }: BookingFlowProps) {
|
||||
onClick={() => {
|
||||
setSelectedPet(pet);
|
||||
setStep(2);
|
||||
fireAnalyticsEvent(ANALYTICS_EVENTS.BOOKING_STEP_SERVICE, { step: "service", flow: "portal" });
|
||||
}}
|
||||
className={`w-full flex items-center gap-3 p-3 rounded-xl border text-left transition-colors ${
|
||||
selectedPet?.id === pet.id
|
||||
@@ -1034,7 +1042,10 @@ function BookingFlow({ onClose, sessionId }: BookingFlowProps) {
|
||||
Back
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setStep(4)}
|
||||
onClick={() => {
|
||||
setStep(4);
|
||||
fireAnalyticsEvent(ANALYTICS_EVENTS.BOOKING_STEP_CONTACT, { step: "groomer", flow: "portal" });
|
||||
}}
|
||||
className="flex-1 px-4 py-2 bg-blue-600 text-white rounded-lg text-sm font-medium"
|
||||
>
|
||||
Next
|
||||
@@ -1093,7 +1104,10 @@ function BookingFlow({ onClose, sessionId }: BookingFlowProps) {
|
||||
Back
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setStep(5)}
|
||||
onClick={() => {
|
||||
setStep(5);
|
||||
fireAnalyticsEvent(ANALYTICS_EVENTS.BOOKING_STEP_TIME, { step: "time", flow: "portal" });
|
||||
}}
|
||||
disabled={!selectedDate || !selectedTime}
|
||||
className="flex-1 px-4 py-2 bg-blue-600 text-white rounded-lg text-sm font-medium disabled:opacity-50"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user