keystone-design-bootstrap 1.0.21 → 1.0.22

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "keystone-design-bootstrap",
3
- "version": "1.0.21",
3
+ "version": "1.0.22",
4
4
  "description": "Keystone Design Bootstrap - Sections, Elements, and Theme System for customer websites",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -12,6 +12,7 @@
12
12
  "./logo": "./src/design_system/logo/keystone-logo.tsx",
13
13
  "./hooks": "./src/lib/hooks/index.ts",
14
14
  "./contexts": "./src/contexts/index.ts",
15
+ "./tracking": "./src/tracking/index.ts",
15
16
  "./lib/server-api": "./src/lib/server-api.ts",
16
17
  "./lib/component-registry": "./src/lib/component-registry.ts",
17
18
  "./styles/*": "./src/styles/*",
@@ -2,6 +2,7 @@
2
2
 
3
3
  import React, { useRef, useState } from 'react';
4
4
  import { Checkbox, Form, Input, InputGroup, NativeSelect, Textarea, Button } from '../elements';
5
+ import { trackMetaLead } from '../../tracking/trackMetaLead';
5
6
  import countries, { phoneCodeOptions } from '../../utils/countries';
6
7
 
7
8
  interface ContactSectionFormProps {
@@ -87,6 +88,7 @@ export const ContactSectionForm = ({
87
88
  setStatusMessage(result.message || successMessage);
88
89
  formRef.current?.reset();
89
90
  onSuccess?.();
91
+ trackMetaLead(result.eventId);
90
92
  // Reset status after 5 seconds
91
93
  setTimeout(() => setSubmitStatus('idle'), 5000);
92
94
  } else {
@@ -12,6 +12,8 @@ interface ContactFormResult {
12
12
  success: boolean;
13
13
  message?: string;
14
14
  error?: string;
15
+ /** Present on lead form success; use for Meta Pixel dedup: fbq('track', 'Lead', {}, { eventID: eventId }) */
16
+ eventId?: string;
15
17
  }
16
18
 
17
19
  /**
@@ -126,10 +128,13 @@ export async function submitLeadFormAction(formData: FormData): Promise<ContactF
126
128
  error: data.error || 'Failed to submit lead form. Please try again.',
127
129
  };
128
130
  }
129
-
131
+
132
+ const eventId = data.data?.event_id ?? undefined;
133
+
130
134
  return {
131
135
  success: true,
132
136
  message: data.message || 'Thank you! We\'ll be in touch soon.',
137
+ ...(eventId && { eventId }),
133
138
  };
134
139
  } catch (error) {
135
140
  console.error('Lead form submission error:', error);
@@ -60,6 +60,18 @@ export async function getCompanyInformation() {
60
60
  return serverFetch('/public/company_information', defaultOptions);
61
61
  }
62
62
 
63
+ /** Ads config (e.g. Meta Pixel). Returns { meta_pixel_id?: string } or {}. Only present when account has connected Meta Ads. */
64
+ export async function getAdsConfig(): Promise<{ meta_pixel_id?: string } | null> {
65
+ const data = await serverFetch<{ meta_pixel_id?: string }>('/public/ads_config', defaultOptions);
66
+ return data ?? null;
67
+ }
68
+
69
+ /** Extract Meta Pixel ID from ads config for use with <MetaPixel pixelId={...} />. */
70
+ export function getMetaPixelId(adsConfig: { meta_pixel_id?: string } | null | undefined): string | null {
71
+ const id = adsConfig && typeof adsConfig === 'object' && 'meta_pixel_id' in adsConfig && adsConfig.meta_pixel_id;
72
+ return id != null && id !== '' ? String(id) : null;
73
+ }
74
+
63
75
  export async function getServices() {
64
76
  return serverFetch('/public/services', defaultOptions);
65
77
  }
@@ -0,0 +1,54 @@
1
+ 'use client';
2
+
3
+ import Script from 'next/script';
4
+
5
+ const FBEVENTS_URL = 'https://connect.facebook.net/en_US/fbevents.js';
6
+
7
+ const PIXEL_SCRIPT = (pixelId: string) => `
8
+ !function(f,b,e,v,n,t,s)
9
+ {if(f.fbq)return;n=f.fbq=function(){n.callMethod?
10
+ n.callMethod.apply(n,arguments):n.queue.push(arguments)};
11
+ if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
12
+ n.queue=[];t=b.createElement(e);t.async=!0;
13
+ t.src=v;s=b.getElementsByTagName(e)[0];
14
+ s.parentNode.insertBefore(t,s)}(window, document,'script','${FBEVENTS_URL}');
15
+ fbq('init', '${pixelId.replace(/'/g, "\\'")}');
16
+ fbq('track', 'PageView');
17
+ `;
18
+
19
+ export type MetaPixelProps = {
20
+ /** Meta Pixel ID. When null/undefined, nothing is rendered. */
21
+ pixelId: string | null | undefined;
22
+ };
23
+
24
+ /**
25
+ * Renders the Meta (Facebook) Pixel base code: loads fbevents.js, initializes
26
+ * the pixel, and tracks PageView. Use in the root layout when ads config
27
+ * provides a meta_pixel_id.
28
+ */
29
+ export function MetaPixel({ pixelId }: MetaPixelProps) {
30
+ if (!pixelId || typeof pixelId !== 'string' || pixelId.trim() === '') {
31
+ return null;
32
+ }
33
+
34
+ const id = pixelId.trim();
35
+
36
+ return (
37
+ <>
38
+ <Script
39
+ id="meta-pixel"
40
+ strategy="afterInteractive"
41
+ dangerouslySetInnerHTML={{ __html: PIXEL_SCRIPT(id) }}
42
+ />
43
+ <noscript>
44
+ <img
45
+ height={1}
46
+ width={1}
47
+ style={{ display: 'none' }}
48
+ src={`https://www.facebook.com/tr?id=${id}&ev=PageView&noscript=1`}
49
+ alt=""
50
+ />
51
+ </noscript>
52
+ </>
53
+ );
54
+ }
@@ -0,0 +1,3 @@
1
+ export { MetaPixel } from './MetaPixel';
2
+ export type { MetaPixelProps } from './MetaPixel';
3
+ export { trackMetaLead } from './trackMetaLead';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Fires the client-side Meta Pixel Lead event with the same eventID as server CAPI
3
+ * so Meta can deduplicate browser + server events.
4
+ */
5
+ export function trackMetaLead(eventId: string | undefined): void {
6
+ if (typeof window === 'undefined' || !eventId) return;
7
+ const fbq = (window as Window & { fbq?: (method: string, eventName: string, params?: object, options?: { eventID?: string }) => void }).fbq;
8
+ if (fbq) fbq('track', 'Lead', {}, { eventID: eventId });
9
+ }