keystone-design-bootstrap 1.0.53 → 1.0.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/next/layouts/root-layout.tsx +2 -1
- package/src/tracking/BookingCtaTracker.tsx +32 -0
- package/src/tracking/MetaPixelTracker.tsx +92 -0
- package/src/tracking/ViewContentTracker.tsx +21 -0
- package/src/tracking/index.ts +6 -0
- package/src/tracking/trackInitiateCheckout.ts +16 -0
- package/src/tracking/trackMetaLead.ts +6 -1
- package/src/tracking/trackViewContent.ts +19 -0
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@ import type { Metadata } from 'next';
|
|
|
3
3
|
|
|
4
4
|
import { HeaderNavigation, FooterHome } from '../../design_system/sections';
|
|
5
5
|
import { ThemeProvider } from '../../contexts';
|
|
6
|
-
import { MetaPixel } from '../../tracking';
|
|
6
|
+
import { MetaPixel, MetaPixelTracker } from '../../tracking';
|
|
7
7
|
import { ChatWidget } from '../../design_system/components/ChatWidget';
|
|
8
8
|
import { FormDefinitionsProvider } from '../contexts/form-definitions';
|
|
9
9
|
import { KeystoneSSRProvider } from '../providers/ssr-provider';
|
|
@@ -159,6 +159,7 @@ export async function KeystoneRootLayout(props: {
|
|
|
159
159
|
<html lang="en" data-theme={theme}>
|
|
160
160
|
<body>
|
|
161
161
|
{metaPixelId ? <MetaPixel pixelId={metaPixelId} /> : null}
|
|
162
|
+
{metaPixelId ? <MetaPixelTracker bookingUrl={externalManagementUrl} /> : null}
|
|
162
163
|
<ThemeProvider theme={theme}>
|
|
163
164
|
<KeystoneSSRProvider>
|
|
164
165
|
<FormDefinitionsProvider
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
|
+
import { trackInitiateCheckout } from './trackInitiateCheckout';
|
|
5
|
+
|
|
6
|
+
type Props = {
|
|
7
|
+
bookingUrl: string | null | undefined;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Drop into the root layout to fire Meta Pixel InitiateCheckout whenever a visitor
|
|
12
|
+
* clicks any anchor whose href points to the external booking URL.
|
|
13
|
+
* Uses document-level click delegation — no changes needed to individual buttons.
|
|
14
|
+
*/
|
|
15
|
+
export function BookingCtaTracker({ bookingUrl }: Props) {
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (!bookingUrl) return;
|
|
18
|
+
|
|
19
|
+
const handleClick = (e: MouseEvent) => {
|
|
20
|
+
const anchor = (e.target as Element).closest('a');
|
|
21
|
+
if (!anchor) return;
|
|
22
|
+
if (anchor.href && anchor.href.startsWith(bookingUrl)) {
|
|
23
|
+
trackInitiateCheckout();
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
document.addEventListener('click', handleClick);
|
|
28
|
+
return () => document.removeEventListener('click', handleClick);
|
|
29
|
+
}, [bookingUrl]);
|
|
30
|
+
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
|
+
import { usePathname } from 'next/navigation';
|
|
5
|
+
import { trackViewContent } from './trackViewContent';
|
|
6
|
+
import { trackInitiateCheckout } from './trackInitiateCheckout';
|
|
7
|
+
|
|
8
|
+
type RouteRule = {
|
|
9
|
+
pattern: RegExp;
|
|
10
|
+
getParams: (match: RegExpMatchArray) => { contentName: string; contentCategory: string };
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Checked in order — first match wins. More specific patterns come first.
|
|
14
|
+
const ROUTE_RULES: RouteRule[] = [
|
|
15
|
+
{
|
|
16
|
+
pattern: /^\/services\/(.+)$/,
|
|
17
|
+
getParams: ([, slug]) => ({ contentName: slugToTitle(slug), contentCategory: 'Service' }),
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
pattern: /^\/locations\/(.+)$/,
|
|
21
|
+
getParams: ([, slug]) => ({ contentName: slugToTitle(slug), contentCategory: 'Location' }),
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
pattern: /^\/services$/,
|
|
25
|
+
getParams: () => ({ contentName: 'Services', contentCategory: 'Services' }),
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
pattern: /^\/locations$/,
|
|
29
|
+
getParams: () => ({ contentName: 'Locations', contentCategory: 'Locations' }),
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
pattern: /^\/service-menu$/,
|
|
33
|
+
getParams: () => ({ contentName: 'Service Menu', contentCategory: 'Pricing' }),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
pattern: /^\/faq$/,
|
|
37
|
+
getParams: () => ({ contentName: 'FAQ', contentCategory: 'FAQ' }),
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
pattern: /^\/contact$/,
|
|
41
|
+
getParams: () => ({ contentName: 'Contact', contentCategory: 'Contact' }),
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
function slugToTitle(slug: string): string {
|
|
46
|
+
return slug
|
|
47
|
+
.split('-')
|
|
48
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
49
|
+
.join(' ');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
type Props = {
|
|
53
|
+
/** External booking URL. When set, fires InitiateCheckout on any click targeting that URL. */
|
|
54
|
+
bookingUrl?: string | null;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Single client-side tracker placed once in KeystoneRootLayout.
|
|
59
|
+
* - Fires ViewContent on every route change for known page patterns.
|
|
60
|
+
* - Fires InitiateCheckout whenever a visitor clicks a link to the external booking URL.
|
|
61
|
+
*/
|
|
62
|
+
export function MetaPixelTracker({ bookingUrl }: Props) {
|
|
63
|
+
const pathname = usePathname();
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
for (const rule of ROUTE_RULES) {
|
|
67
|
+
const match = pathname.match(rule.pattern);
|
|
68
|
+
if (match) {
|
|
69
|
+
const { contentName, contentCategory } = rule.getParams(match);
|
|
70
|
+
trackViewContent(contentName, contentCategory);
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}, [pathname]);
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (!bookingUrl) return;
|
|
78
|
+
|
|
79
|
+
const handleClick = (e: MouseEvent) => {
|
|
80
|
+
const anchor = (e.target as Element).closest('a');
|
|
81
|
+
if (anchor?.href?.startsWith(bookingUrl)) {
|
|
82
|
+
console.debug('[MetaPixel] Booking link clicked', { href: anchor.href });
|
|
83
|
+
trackInitiateCheckout();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
document.addEventListener('click', handleClick);
|
|
88
|
+
return () => document.removeEventListener('click', handleClick);
|
|
89
|
+
}, [bookingUrl]);
|
|
90
|
+
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
|
+
import { trackViewContent } from './trackViewContent';
|
|
5
|
+
|
|
6
|
+
type Props = {
|
|
7
|
+
contentName?: string;
|
|
8
|
+
contentCategory?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Drop into any server-rendered page to fire the Meta Pixel ViewContent event on mount.
|
|
13
|
+
* Renders nothing — purely a tracking side-effect component.
|
|
14
|
+
*/
|
|
15
|
+
export function ViewContentTracker({ contentName, contentCategory }: Props) {
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
trackViewContent(contentName, contentCategory);
|
|
18
|
+
}, [contentName, contentCategory]);
|
|
19
|
+
|
|
20
|
+
return null;
|
|
21
|
+
}
|
package/src/tracking/index.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
export { MetaPixel } from './MetaPixel';
|
|
2
2
|
export type { MetaPixelProps } from './MetaPixel';
|
|
3
3
|
export { trackMetaLead } from './trackMetaLead';
|
|
4
|
+
export { trackViewContent } from './trackViewContent';
|
|
5
|
+
export { trackInitiateCheckout } from './trackInitiateCheckout';
|
|
6
|
+
export { MetaPixelTracker } from './MetaPixelTracker';
|
|
7
|
+
// Kept for custom use — MetaPixelTracker covers the standard cases automatically.
|
|
8
|
+
export { ViewContentTracker } from './ViewContentTracker';
|
|
9
|
+
export { BookingCtaTracker } from './BookingCtaTracker';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type FbqFn = (method: string, eventName: string, params?: object) => void;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fires the client-side Meta Pixel InitiateCheckout event.
|
|
5
|
+
* Call this when a visitor clicks a booking / scheduling button.
|
|
6
|
+
*/
|
|
7
|
+
export function trackInitiateCheckout(): void {
|
|
8
|
+
if (typeof window === 'undefined') return;
|
|
9
|
+
const fbq = (window as Window & { fbq?: FbqFn }).fbq;
|
|
10
|
+
if (!fbq) {
|
|
11
|
+
console.debug('[MetaPixel] InitiateCheckout skipped — fbq not loaded');
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
console.debug('[MetaPixel] InitiateCheckout');
|
|
15
|
+
fbq('track', 'InitiateCheckout');
|
|
16
|
+
}
|
|
@@ -5,5 +5,10 @@
|
|
|
5
5
|
export function trackMetaLead(eventId: string | undefined): void {
|
|
6
6
|
if (typeof window === 'undefined' || !eventId) return;
|
|
7
7
|
const fbq = (window as Window & { fbq?: (method: string, eventName: string, params?: object, options?: { eventID?: string }) => void }).fbq;
|
|
8
|
-
if (fbq)
|
|
8
|
+
if (!fbq) {
|
|
9
|
+
console.debug('[MetaPixel] Lead skipped — fbq not loaded', { eventId });
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
console.debug('[MetaPixel] Lead', { eventId });
|
|
13
|
+
fbq('track', 'Lead', {}, { eventID: eventId });
|
|
9
14
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
type FbqFn = (method: string, eventName: string, params?: object) => void;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fires the client-side Meta Pixel ViewContent event.
|
|
5
|
+
* Call this on service/offer detail pages so Meta knows which content is most engaging.
|
|
6
|
+
*/
|
|
7
|
+
export function trackViewContent(contentName?: string, contentCategory?: string): void {
|
|
8
|
+
if (typeof window === 'undefined') return;
|
|
9
|
+
const fbq = (window as Window & { fbq?: FbqFn }).fbq;
|
|
10
|
+
if (!fbq) {
|
|
11
|
+
console.debug('[MetaPixel] ViewContent skipped — fbq not loaded', { contentName, contentCategory });
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const params: Record<string, string> = {};
|
|
15
|
+
if (contentName) params.content_name = contentName;
|
|
16
|
+
if (contentCategory) params.content_category = contentCategory;
|
|
17
|
+
console.debug('[MetaPixel] ViewContent', params);
|
|
18
|
+
fbq('track', 'ViewContent', params);
|
|
19
|
+
}
|