4.2.2 - tracking across email, fixes to dark mode.
This commit is contained in:
@@ -9,6 +9,9 @@
|
||||
export let booking: BookingContent;
|
||||
export let allowGeneralEnquiry = false;
|
||||
type EnquiryType = 'booking' | 'general';
|
||||
const visitStartedStorageKey = 'goodwalk_visit_started_at';
|
||||
const journeyStorageKey = 'goodwalk_journey';
|
||||
const maxJourneyEntries = 8;
|
||||
|
||||
let step = 1;
|
||||
$: headingParts = splitBookingTitle(booking.title);
|
||||
@@ -23,6 +26,12 @@
|
||||
let selectedServices: string[] = [];
|
||||
let website = '';
|
||||
let formStartedAt = 0;
|
||||
let visitStartedAt = 0;
|
||||
let pageEnteredAt = 0;
|
||||
let firstInteractionAt = 0;
|
||||
let sendClickedAt = 0;
|
||||
let stepChanges = 0;
|
||||
let journey: string[] = [];
|
||||
|
||||
let fullNameInput: HTMLInputElement;
|
||||
let emailInput: HTMLInputElement;
|
||||
@@ -84,7 +93,11 @@
|
||||
$: successPetName = petName.trim() || 'your dog';
|
||||
|
||||
onMount(() => {
|
||||
formStartedAt = Date.now();
|
||||
const now = Date.now();
|
||||
formStartedAt = now;
|
||||
pageEnteredAt = now;
|
||||
visitStartedAt = readOrCreateVisitStartedAt(now);
|
||||
journey = updateJourneySnapshot(window.location.pathname, window.location.search);
|
||||
});
|
||||
|
||||
function splitBookingTitle(title: string) {
|
||||
@@ -107,7 +120,58 @@
|
||||
}
|
||||
}
|
||||
|
||||
function readOrCreateVisitStartedAt(fallback: number) {
|
||||
try {
|
||||
const raw = window.sessionStorage.getItem(visitStartedStorageKey);
|
||||
const parsed = raw ? Number(raw) : NaN;
|
||||
|
||||
if (Number.isFinite(parsed) && parsed > 0) {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
window.sessionStorage.setItem(visitStartedStorageKey, String(fallback));
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function updateJourneySnapshot(pathname: string, search: string) {
|
||||
const nextEntry = `${pathname}${search}`;
|
||||
|
||||
try {
|
||||
const raw = window.sessionStorage.getItem(journeyStorageKey);
|
||||
const previous = raw ? (JSON.parse(raw) as string[]) : [];
|
||||
const cleaned = previous.filter((value) => typeof value === 'string' && value.trim());
|
||||
const deduped = cleaned[cleaned.length - 1] === nextEntry ? cleaned : [...cleaned, nextEntry];
|
||||
const nextJourney = deduped.slice(-maxJourneyEntries);
|
||||
|
||||
window.sessionStorage.setItem(journeyStorageKey, JSON.stringify(nextJourney));
|
||||
return nextJourney;
|
||||
} catch {
|
||||
return [nextEntry];
|
||||
}
|
||||
}
|
||||
|
||||
function noteInteraction() {
|
||||
if (!firstInteractionAt) {
|
||||
firstInteractionAt = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
function setStep(nextStep: number, trackTransition = false) {
|
||||
if (step !== nextStep && trackTransition) {
|
||||
stepChanges += 1;
|
||||
}
|
||||
|
||||
step = nextStep;
|
||||
errors = {};
|
||||
}
|
||||
|
||||
function toggleService(service: string, checked: boolean) {
|
||||
noteInteraction();
|
||||
|
||||
if (checked) {
|
||||
selectedServices = [...selectedServices, service];
|
||||
return;
|
||||
@@ -117,6 +181,7 @@
|
||||
}
|
||||
|
||||
function setEnquiryType(nextType: EnquiryType) {
|
||||
noteInteraction();
|
||||
enquiryType = nextType;
|
||||
if (nextType === 'general') {
|
||||
petName = '';
|
||||
@@ -186,9 +251,9 @@
|
||||
}
|
||||
|
||||
function goToOwnerStep() {
|
||||
noteInteraction();
|
||||
if (!validateDetailsStep()) return;
|
||||
errors = {};
|
||||
step = 2;
|
||||
setStep(2, true);
|
||||
}
|
||||
|
||||
async function handleSubmit(event: SubmitEvent) {
|
||||
@@ -204,6 +269,8 @@
|
||||
}
|
||||
|
||||
errors = {};
|
||||
noteInteraction();
|
||||
sendClickedAt = Date.now();
|
||||
submitting = true;
|
||||
submitErrorDetail = '';
|
||||
showErrorModal = false;
|
||||
@@ -223,6 +290,12 @@
|
||||
services: isGeneralEnquiry ? [] : selectedServices,
|
||||
website,
|
||||
formStartedAt,
|
||||
visitStartedAt,
|
||||
pageEnteredAt,
|
||||
firstInteractionAt,
|
||||
sendClickedAt,
|
||||
stepChanges,
|
||||
journey,
|
||||
referrer: document.referrer,
|
||||
page: window.location.href
|
||||
})
|
||||
@@ -279,10 +352,7 @@
|
||||
type="button"
|
||||
class:active={step === 1}
|
||||
class="booking-step"
|
||||
on:click={() => {
|
||||
step = 1;
|
||||
errors = {};
|
||||
}}
|
||||
on:click={() => setStep(1, step !== 1)}
|
||||
>
|
||||
<span class="booking-step-number">1</span>
|
||||
<span class="booking-step-label">{detailsStepLabel}</span>
|
||||
@@ -300,7 +370,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="booking-form" id="bookingForm" novalidate on:submit={handleSubmit}>
|
||||
<form
|
||||
class="booking-form"
|
||||
id="bookingForm"
|
||||
novalidate
|
||||
on:submit={handleSubmit}
|
||||
on:focusin={noteInteraction}
|
||||
on:input={noteInteraction}
|
||||
on:change={noteInteraction}
|
||||
>
|
||||
<div class="booking-honeypot" aria-hidden="true">
|
||||
<label for="website">Website</label>
|
||||
<input
|
||||
@@ -569,10 +647,7 @@
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline btn-outline-green"
|
||||
on:click={() => {
|
||||
step = 1;
|
||||
errors = {};
|
||||
}}
|
||||
on:click={() => setStep(1, true)}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
|
||||
@@ -36,6 +36,7 @@ async function moveToOwnerStep(container: HTMLElement) {
|
||||
|
||||
describe('BookingSection', () => {
|
||||
beforeEach(() => {
|
||||
window.sessionStorage.clear();
|
||||
Object.defineProperty(document, 'referrer', {
|
||||
configurable: true,
|
||||
value: 'https://www.google.com/'
|
||||
@@ -104,9 +105,15 @@ describe('BookingSection', () => {
|
||||
message: 'Loves small group walks.',
|
||||
services: ['Pack Walks', 'Other Services'],
|
||||
website: '',
|
||||
referrer: 'https://www.google.com/'
|
||||
referrer: 'https://www.google.com/',
|
||||
stepChanges: 1,
|
||||
journey: [window.location.pathname]
|
||||
});
|
||||
expect(payload.formStartedAt).toEqual(expect.any(Number));
|
||||
expect(payload.visitStartedAt).toEqual(expect.any(Number));
|
||||
expect(payload.pageEnteredAt).toEqual(expect.any(Number));
|
||||
expect(payload.firstInteractionAt).toEqual(expect.any(Number));
|
||||
expect(payload.sendClickedAt).toEqual(expect.any(Number));
|
||||
|
||||
expect(screen.getByRole('dialog', { name: /Booking confirmed/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('heading', { name: /on our radar/i })).toBeInTheDocument();
|
||||
@@ -156,7 +163,9 @@ describe('BookingSection', () => {
|
||||
petName: '',
|
||||
location: '',
|
||||
message: 'I would like to discuss a business partnership.',
|
||||
services: []
|
||||
services: [],
|
||||
stepChanges: 1,
|
||||
journey: [window.location.pathname]
|
||||
});
|
||||
|
||||
expect(screen.getByRole('dialog', { name: /Enquiry confirmed/i })).toBeInTheDocument();
|
||||
|
||||
Reference in New Issue
Block a user