All files / src/utils trackAppointmentEvent.ts

100% Statements 17/17
100% Branches 8/8
100% Functions 5/5
100% Lines 17/17

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116            2x   2x                                                                                 2x                                         2x       6x 6x 3x   3x       1x       8x 8x             8x             8x 8x 8x 6x     2x                
/**
 * Privacy-safe booking analytics. Emits English, PII-free events that cross
 * Shadow DOM via document + composed CustomEvent. If etracker is present on
 * the host page, also forwards object/category/action. Never throws.
 */
 
export const APPOINTMENT_TRACK_EVENT = "zms-appointment-track";
 
export const APPOINTMENT_TRACK_CATEGORY = "appointment";
 
export type AppointmentTrackObject =
  | "appointment"
  | "appointment_detail"
  | "appointment_slider"
  | "appointment_overview"
  | "service_finder"
  | "service_combination"
  | "appointment_selection"
  | "contact"
  | "summary"
  | "reserved"
  | "updated"
  | "preconfirmed"
  | "confirmed"
  | "cancelled"
  | "rebooking"
  | "login";
 
export type AppointmentTrackAction =
  "view" | "click" | "success" | "started" | "abandoned";
 
export type AppointmentTrackFlow = "new" | "rebooking";
 
export type AppointmentTrackSlotUi = "list" | "calendar";
 
export type AppointmentTrackWidget =
  | "appointment"
  | "appointment_detail"
  | "appointment_slider"
  | "appointment_overview";
 
export type AppointmentTrackPayload = {
  object: AppointmentTrackObject;
  action: AppointmentTrackAction;
  flow?: AppointmentTrackFlow;
  slot_ui?: AppointmentTrackSlotUi;
  widget?: AppointmentTrackWidget;
};
 
const BOOKING_SCREENS: Record<number, AppointmentTrackObject> = {
  1: "appointment_selection",
  2: "contact",
  3: "summary",
};
 
declare global {
  interface Window {
    _etracker?: {
      sendEvent?: (event: unknown) => void;
    };
    et_UserDefinedEvent?: new (
      objectName: string,
      category: string,
      action?: string,
      type?: string
    ) => unknown;
  }
}
 
export function bookingFlow(isRebooking: boolean): AppointmentTrackFlow {
  return isRebooking ? "rebooking" : "new";
}
 
export function trackAppointmentScreenFromView(view: number): void {
  const object = BOOKING_SCREENS[view];
  if (!object) {
    return;
  }
  trackAppointmentEvent({ object, action: "view" });
}
 
export function trackWidgetView(widget: AppointmentTrackWidget): void {
  trackAppointmentEvent({ object: widget, action: "view" });
}
 
export function trackAppointmentEvent(payload: AppointmentTrackPayload): void {
  try {
    document.dispatchEvent(
      new CustomEvent(APPOINTMENT_TRACK_EVENT, {
        detail: payload,
        bubbles: true,
        composed: true,
      })
    );
    sendToEtracker(payload);
  } catch {
    // Tracking must never break the booking flow.
  }
}
 
function sendToEtracker(payload: AppointmentTrackPayload): void {
  const sendEvent = window._etracker?.sendEvent;
  const UserDefinedEvent = window.et_UserDefinedEvent;
  if (!sendEvent || !UserDefinedEvent) {
    return;
  }
 
  sendEvent(
    new UserDefinedEvent(
      payload.object,
      APPOINTMENT_TRACK_CATEGORY,
      payload.action
    )
  );
}