@salesmind-ai/design-system 1.3.0 → 1.4.0
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/dist/{chunk-2VVRZBUR.cjs → chunk-DXFMZ4ME.cjs} +79 -15
- package/dist/chunk-DXFMZ4ME.cjs.map +1 -0
- package/dist/{chunk-K526GN7P.js → chunk-FZJLTJZS.js} +79 -16
- package/dist/chunk-FZJLTJZS.js.map +1 -0
- package/dist/web/client/index.cjs +13 -9
- package/dist/web/client/index.d.cts +18 -8
- package/dist/web/client/index.d.ts +18 -8
- package/dist/web/client/index.js +1 -1
- package/dist/web/index.cjs +13 -9
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.d.cts +1 -1
- package/dist/web/index.d.ts +1 -1
- package/dist/web/index.js +1 -1
- package/dist/web/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-2VVRZBUR.cjs.map +0 -1
- package/dist/chunk-K526GN7P.js.map +0 -1
|
@@ -46,6 +46,48 @@ var loadClarity = createAnalyticsLoader({
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
});
|
|
49
|
+
|
|
50
|
+
// src/web/analytics/region.ts
|
|
51
|
+
var NON_EEA_EUROPE_TIMEZONES = /* @__PURE__ */ new Set([
|
|
52
|
+
// Russia
|
|
53
|
+
"Europe/Moscow",
|
|
54
|
+
"Europe/Volgograd",
|
|
55
|
+
"Europe/Samara",
|
|
56
|
+
"Europe/Saratov",
|
|
57
|
+
"Europe/Ulyanovsk",
|
|
58
|
+
"Europe/Kaliningrad",
|
|
59
|
+
"Europe/Astrakhan",
|
|
60
|
+
"Europe/Kirov",
|
|
61
|
+
"Europe/Simferopol",
|
|
62
|
+
// Turkey
|
|
63
|
+
"Europe/Istanbul",
|
|
64
|
+
// Belarus
|
|
65
|
+
"Europe/Minsk",
|
|
66
|
+
// Western Balkans (non-EU)
|
|
67
|
+
"Europe/Belgrade",
|
|
68
|
+
"Europe/Sarajevo",
|
|
69
|
+
"Europe/Skopje",
|
|
70
|
+
"Europe/Tirane",
|
|
71
|
+
"Europe/Podgorica",
|
|
72
|
+
"Europe/Pristina",
|
|
73
|
+
// Moldova
|
|
74
|
+
"Europe/Chisinau",
|
|
75
|
+
// Ukraine
|
|
76
|
+
"Europe/Kyiv",
|
|
77
|
+
"Europe/Uzhgorod",
|
|
78
|
+
"Europe/Zaporozhye"
|
|
79
|
+
]);
|
|
80
|
+
function isEEAUser() {
|
|
81
|
+
if (typeof window === "undefined") return true;
|
|
82
|
+
try {
|
|
83
|
+
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
84
|
+
if (!tz) return true;
|
|
85
|
+
if (!tz.startsWith("Europe/")) return false;
|
|
86
|
+
return !NON_EEA_EUROPE_TIMEZONES.has(tz);
|
|
87
|
+
} catch {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
49
91
|
var COOKIE_CONSENT_EVENT = "cookie_consent_granted";
|
|
50
92
|
var COOKIE_CONSENT_KEY = "cookie_consent";
|
|
51
93
|
function CookieConsent({
|
|
@@ -59,12 +101,21 @@ function CookieConsent({
|
|
|
59
101
|
const [state, setState] = react.useState("idle");
|
|
60
102
|
react.useEffect(() => {
|
|
61
103
|
const consent = localStorage.getItem(COOKIE_CONSENT_KEY);
|
|
62
|
-
if (
|
|
63
|
-
|
|
64
|
-
return
|
|
65
|
-
}
|
|
104
|
+
if (consent) {
|
|
105
|
+
setState("closed");
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (!isEEAUser()) {
|
|
109
|
+
try {
|
|
110
|
+
localStorage.setItem(COOKIE_CONSENT_KEY, "granted");
|
|
111
|
+
window.dispatchEvent(new Event(COOKIE_CONSENT_EVENT));
|
|
112
|
+
} catch {
|
|
113
|
+
}
|
|
66
114
|
setState("closed");
|
|
115
|
+
return;
|
|
67
116
|
}
|
|
117
|
+
const timer = setTimeout(() => setState("open"), delay);
|
|
118
|
+
return () => clearTimeout(timer);
|
|
68
119
|
}, [delay]);
|
|
69
120
|
const handleDismiss = react.useCallback(() => {
|
|
70
121
|
setState("closing");
|
|
@@ -122,17 +173,29 @@ function CookieConsent({
|
|
|
122
173
|
);
|
|
123
174
|
}
|
|
124
175
|
function useCookieConsent() {
|
|
125
|
-
const [status, setStatus] = react.useState(
|
|
126
|
-
if (typeof window === "undefined") return null;
|
|
127
|
-
const stored = localStorage.getItem(COOKIE_CONSENT_KEY);
|
|
128
|
-
if (stored === "granted") return "granted";
|
|
129
|
-
if (stored === "denied") return "denied";
|
|
130
|
-
return null;
|
|
131
|
-
});
|
|
176
|
+
const [status, setStatus] = react.useState(null);
|
|
132
177
|
react.useEffect(() => {
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
178
|
+
const stored = localStorage.getItem(COOKIE_CONSENT_KEY);
|
|
179
|
+
if (stored === "granted") {
|
|
180
|
+
setStatus("granted");
|
|
181
|
+
} else if (stored === "denied") {
|
|
182
|
+
setStatus("denied");
|
|
183
|
+
} else if (!isEEAUser()) {
|
|
184
|
+
setStatus("granted");
|
|
185
|
+
}
|
|
186
|
+
const handleGranted = () => setStatus("granted");
|
|
187
|
+
const handleStorage = (e) => {
|
|
188
|
+
if (e.key !== COOKIE_CONSENT_KEY) return;
|
|
189
|
+
if (e.newValue === "granted") setStatus("granted");
|
|
190
|
+
else if (e.newValue === "denied") setStatus("denied");
|
|
191
|
+
else setStatus(isEEAUser() ? null : "granted");
|
|
192
|
+
};
|
|
193
|
+
window.addEventListener(COOKIE_CONSENT_EVENT, handleGranted);
|
|
194
|
+
window.addEventListener("storage", handleStorage);
|
|
195
|
+
return () => {
|
|
196
|
+
window.removeEventListener(COOKIE_CONSENT_EVENT, handleGranted);
|
|
197
|
+
window.removeEventListener("storage", handleStorage);
|
|
198
|
+
};
|
|
136
199
|
}, []);
|
|
137
200
|
return status;
|
|
138
201
|
}
|
|
@@ -155,8 +218,9 @@ exports.COOKIE_CONSENT_EVENT = COOKIE_CONSENT_EVENT;
|
|
|
155
218
|
exports.COOKIE_CONSENT_KEY = COOKIE_CONSENT_KEY;
|
|
156
219
|
exports.CookieConsent = CookieConsent;
|
|
157
220
|
exports.createAnalyticsLoader = createAnalyticsLoader;
|
|
221
|
+
exports.isEEAUser = isEEAUser;
|
|
158
222
|
exports.loadClarity = loadClarity;
|
|
159
223
|
exports.loadGoogleAnalytics = loadGoogleAnalytics;
|
|
160
224
|
exports.useCookieConsent = useCookieConsent;
|
|
161
225
|
//# sourceMappingURL=out.js.map
|
|
162
|
-
//# sourceMappingURL=chunk-
|
|
226
|
+
//# sourceMappingURL=chunk-DXFMZ4ME.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/web/analytics/create-analytics-loader.ts","../src/web/analytics/region.ts","../src/web/CookieConsent/CookieConsent.tsx","../src/web/analytics/use-cookie-consent.ts","../src/web/analytics/analytics-provider.tsx"],"names":["useState","useEffect","useCallback","jsx"],"mappings":"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","sourcesContent":["/**\n * Generic analytics script loader.\n *\n * Framework-agnostic: injects a `<script>` tag into `<head>` once,\n * guards against double-loading, and respects cookie consent.\n *\n * @example\n * ```ts\n * import { createAnalyticsLoader } from '@salesmind-ai/design-system/web';\n *\n * const loadGA = createAnalyticsLoader({\n * id: 'ga-script',\n * src: (gaId) => `https://www.googletagmanager.com/gtag/js?id=${gaId}`,\n * onLoad: (gaId) => {\n * window.dataLayer = window.dataLayer || [];\n * function gtag(...args: unknown[]) { window.dataLayer.push(args); }\n * gtag('js', new Date());\n * gtag('config', gaId);\n * },\n * });\n *\n * // Later, after consent:\n * loadGA('G-XXXXXX');\n * ```\n */\n\nexport interface AnalyticsLoaderConfig<TId extends string = string> {\n /** Unique DOM id for the script element (prevents double-loading) */\n id: string;\n /** Build the script src URL from the tracking ID */\n src: (trackingId: TId) => string;\n /** Async attribute on the script tag (default: true) */\n async?: boolean;\n /** Called after the script is appended to the DOM */\n onLoad?: (trackingId: TId) => void;\n /** Custom inline script content instead of an external src */\n inlineScript?: (trackingId: TId) => string;\n}\n\n/**\n * Create a reusable analytics loader function.\n * The returned function injects the script once and is safe to call multiple times.\n */\nexport function createAnalyticsLoader<TId extends string = string>(\n config: AnalyticsLoaderConfig<TId>,\n): (trackingId: TId) => void {\n return (trackingId: TId) => {\n if (typeof document === 'undefined') return;\n if (document.getElementById(config.id)) return;\n\n const script = document.createElement('script');\n script.id = config.id;\n\n if (config.inlineScript) {\n script.textContent = config.inlineScript(trackingId);\n } else {\n script.async = config.async ?? true;\n script.src = config.src(trackingId);\n }\n\n document.head.appendChild(script);\n config.onLoad?.(trackingId);\n };\n}\n\n// ─── Pre-built Loaders ──────────────────────────────────────────────────────\n\ndeclare global {\n interface Window {\n dataLayer: unknown[];\n clarity?: (...args: unknown[]) => void;\n }\n}\n\n/**\n * Load Google Analytics (gtag.js).\n * Call with your GA measurement ID after cookie consent.\n */\nexport const loadGoogleAnalytics = createAnalyticsLoader({\n id: 'ga-script',\n src: (gaId) => `https://www.googletagmanager.com/gtag/js?id=${gaId}`,\n onLoad: (gaId) => {\n window.dataLayer = window.dataLayer || [];\n // Use array push directly to avoid @ts-ignore issues with gtag signature\n window.dataLayer.push(['js', new Date()]);\n window.dataLayer.push(['config', gaId]);\n },\n});\n\n/**\n * Load Microsoft Clarity.\n * Call with your Clarity project ID after cookie consent.\n */\nexport const loadClarity = createAnalyticsLoader({\n id: 'clarity-script',\n src: (clarityId) => `https://www.clarity.ms/tag/${clarityId}`,\n onLoad: () => {\n // Clarity auto-initializes from the script tag.\n // Expose the global `clarity` queue function for custom events.\n if (!window.clarity) {\n const queue: unknown[][] = [];\n window.clarity = (...args: unknown[]) => {\n queue.push(args);\n };\n (window.clarity as unknown as { q: unknown[][] }).q = queue;\n }\n },\n});\n","\"use client\";\n\n/**\n * EEA timezone-based detection for soft-consent flows.\n *\n * Returns `true` if the user's resolved timezone is in the European\n * Economic Area / UK / Switzerland (where GDPR strict consent applies).\n * Returns `false` for everyone else.\n *\n * Server-side and timezone-resolution failures return `true` (safe\n * default — assume GDPR applies).\n *\n * @example\n * ```ts\n * if (!isEEAUser()) {\n * // Auto-grant analytics consent for non-EEA visitors\n * localStorage.setItem('cookie_consent', 'granted');\n * }\n * ```\n */\n\n/**\n * Europe/* timezones that are NOT in the EEA and where GDPR-strict consent\n * rules do not apply. Includes Russia (multi-tz), Turkey, Ukraine, Belarus,\n * Moldova, and the Western Balkans (non-EU).\n */\nconst NON_EEA_EUROPE_TIMEZONES = new Set<string>([\n // Russia\n \"Europe/Moscow\",\n \"Europe/Volgograd\",\n \"Europe/Samara\",\n \"Europe/Saratov\",\n \"Europe/Ulyanovsk\",\n \"Europe/Kaliningrad\",\n \"Europe/Astrakhan\",\n \"Europe/Kirov\",\n \"Europe/Simferopol\",\n // Turkey\n \"Europe/Istanbul\",\n // Belarus\n \"Europe/Minsk\",\n // Western Balkans (non-EU)\n \"Europe/Belgrade\",\n \"Europe/Sarajevo\",\n \"Europe/Skopje\",\n \"Europe/Tirane\",\n \"Europe/Podgorica\",\n \"Europe/Pristina\",\n // Moldova\n \"Europe/Chisinau\",\n // Ukraine\n \"Europe/Kyiv\",\n \"Europe/Uzhgorod\",\n \"Europe/Zaporozhye\",\n]);\n\nexport function isEEAUser(): boolean {\n if (typeof window === \"undefined\") return true;\n try {\n const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;\n if (!tz) return true;\n if (!tz.startsWith(\"Europe/\")) return false;\n return !NON_EEA_EUROPE_TIMEZONES.has(tz);\n } catch {\n return true; // fail safe\n }\n}\n","\"use client\";\nimport { useState, useEffect, useCallback } from 'react';\nimport { Button } from '../../components/Button/Button';\nimport { OutboundLink } from '../../components/OutboundLink/OutboundLink';\nimport { X } from 'lucide-react';\nimport './CookieConsent.css';\nimport { isEEAUser } from '../analytics/region';\n\n/** Cookie consent state */\nexport type ConsentStatus = 'granted' | 'denied' | null;\n\n/** Event name dispatched on window when consent changes */\nexport const COOKIE_CONSENT_EVENT = 'cookie_consent_granted';\n\n/** localStorage key for cookie consent */\nexport const COOKIE_CONSENT_KEY = 'cookie_consent';\n\n/** Labels for i18n support */\nexport interface CookieConsentLabels {\n title?: string;\n description?: string;\n privacyLinkText?: string;\n acceptLabel?: string;\n declineLabel?: string;\n}\n\nexport interface CookieConsentProps {\n /** Delay in ms before showing the banner (default: 1000) */\n delay?: number;\n /** URL to the privacy policy page (default: \"/legal/privacy\") */\n privacyUrl?: string;\n /** Called when the user accepts cookies */\n onAccept?: () => void;\n /** Called when the user declines cookies */\n onDecline?: () => void;\n /** Override default labels for i18n */\n labels?: CookieConsentLabels;\n /** Custom className for the container */\n className?: string;\n}\n\n/**\n * CookieConsent — GDPR-compliant cookie consent banner.\n *\n * - Animated entrance/exit via CSS transitions (no framer-motion)\n * - Respects prior consent stored in localStorage\n * - Dispatches a `cookie_consent_granted` event on the window for analytics loaders\n * - Uses DS Button component\n *\n * @example\n * ```tsx\n * import { CookieConsent } from '@salesmind-ai/design-system/web';\n *\n * <CookieConsent\n * privacyUrl=\"/legal/privacy\"\n * onAccept={() => loadGoogleAnalytics('G-XXXX')}\n * />\n * ```\n */\nexport function CookieConsent({\n delay = 1000,\n privacyUrl = '/legal/privacy',\n onAccept,\n onDecline,\n labels,\n className,\n}: CookieConsentProps) {\n // 'idle' = not yet determined, 'open' = visible, 'closing' = exit animation, 'closed' = hidden\n const [state, setState] = useState<'idle' | 'open' | 'closing' | 'closed'>('idle');\n\n useEffect(() => {\n const consent = localStorage.getItem(COOKIE_CONSENT_KEY);\n if (consent) {\n setState('closed');\n return;\n }\n // Soft consent: non-EEA visitors auto-grant analytics. Persist the\n // implicit choice so cross-tab/cross-page state stays consistent,\n // skip the banner, and notify any subscribed hooks/listeners.\n if (!isEEAUser()) {\n try {\n localStorage.setItem(COOKIE_CONSENT_KEY, 'granted');\n window.dispatchEvent(new Event(COOKIE_CONSENT_EVENT));\n } catch {\n // localStorage may be unavailable (private mode, etc.) — fall through.\n }\n setState('closed');\n return;\n }\n const timer = setTimeout(() => setState('open'), delay);\n return () => clearTimeout(timer);\n }, [delay]);\n\n const handleDismiss = useCallback(() => {\n setState('closing');\n }, []);\n\n const handleAccept = useCallback(() => {\n localStorage.setItem(COOKIE_CONSENT_KEY, 'granted');\n handleDismiss();\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new Event(COOKIE_CONSENT_EVENT));\n }\n onAccept?.();\n }, [onAccept, handleDismiss]);\n\n const handleDecline = useCallback(() => {\n localStorage.setItem(COOKIE_CONSENT_KEY, 'denied');\n handleDismiss();\n onDecline?.();\n }, [onDecline, handleDismiss]);\n\n const handleTransitionEnd = useCallback(() => {\n if (state === 'closing') {\n setState('closed');\n }\n }, [state]);\n\n // Don't render anything until we know consent status, or after fully closed\n if (state === 'idle' || state === 'closed') return null;\n\n return (\n <div\n className={`ds-cookie-consent ${className ?? ''}`}\n data-state={state === 'open' ? 'open' : 'closed'}\n onTransitionEnd={handleTransitionEnd}\n role=\"dialog\"\n aria-label={labels?.title ?? 'Cookie consent'}\n >\n <div className=\"ds-cookie-consent__inner\">\n <div className=\"ds-cookie-consent__content\">\n <h3 className=\"ds-cookie-consent__title\">\n {labels?.title ?? 'We use cookies'}\n </h3>\n <p className=\"ds-cookie-consent__description\">\n {labels?.description ??\n 'We use tracking cookies to understand how you use the product and help us improve it.'}\n <OutboundLink href={privacyUrl} context=\"cookie-consent-privacy\" className=\"ds-cookie-consent__link\">\n {labels?.privacyLinkText ?? 'Privacy Policy'}\n </OutboundLink>\n </p>\n </div>\n <div className=\"ds-cookie-consent__actions\">\n <Button variant=\"outline\" size=\"sm\" onClick={handleDecline}>\n {labels?.declineLabel ?? 'Decline'}\n </Button>\n <Button variant=\"primary\" size=\"sm\" onClick={handleAccept}>\n {labels?.acceptLabel ?? 'Accept'}\n </Button>\n </div>\n <button\n onClick={handleDecline}\n className=\"ds-cookie-consent__close\"\n aria-label=\"Close\"\n >\n <X size={16} />\n </button>\n </div>\n </div>\n );\n}\n","\"use client\";\nimport { useState, useEffect } from 'react';\nimport { COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, type ConsentStatus } from '../CookieConsent/CookieConsent';\nimport { isEEAUser } from './region';\n\n/**\n * React hook that tracks cookie consent status.\n *\n * Returns `'granted'` once the user has granted cookie consent,\n * `'denied'` if denied, `null` if not yet decided.\n *\n * SOFT CONSENT: For non-EEA users (timezone-based detection), the default\n * resolves to `'granted'` after mount so analytics fire without requiring a\n * banner interaction. EEA users retain the strict `null → granted` flow via\n * the banner.\n *\n * Server-side renders as `null` (no window). The mount effect computes the\n * real status; this avoids hydration mismatches.\n *\n * Listens for the `cookie_consent_granted` window event dispatched by the\n * CookieConsent component, plus the cross-tab `storage` event.\n *\n * @example\n * ```tsx\n * const consent = useCookieConsent();\n *\n * useEffect(() => {\n * if (consent === 'granted') loadGoogleAnalytics('G-XXXX');\n * }, [consent]);\n * ```\n */\nexport function useCookieConsent(): ConsentStatus {\n const [status, setStatus] = useState<ConsentStatus>(null);\n\n useEffect(() => {\n // Compute initial status on mount only (avoids SSR mismatch).\n const stored = localStorage.getItem(COOKIE_CONSENT_KEY);\n if (stored === 'granted') {\n setStatus('granted');\n } else if (stored === 'denied') {\n setStatus('denied');\n } else if (!isEEAUser()) {\n // Soft consent: non-EEA users auto-grant on first visit.\n setStatus('granted');\n }\n // else: leave as null until the user interacts with the banner.\n\n const handleGranted = () => setStatus('granted');\n const handleStorage = (e: StorageEvent) => {\n if (e.key !== COOKIE_CONSENT_KEY) return;\n if (e.newValue === 'granted') setStatus('granted');\n else if (e.newValue === 'denied') setStatus('denied');\n else setStatus(isEEAUser() ? null : 'granted');\n };\n\n window.addEventListener(COOKIE_CONSENT_EVENT, handleGranted);\n window.addEventListener('storage', handleStorage);\n return () => {\n window.removeEventListener(COOKIE_CONSENT_EVENT, handleGranted);\n window.removeEventListener('storage', handleStorage);\n };\n }, []);\n\n return status;\n}\n","\"use client\";\nimport { useCallback, useMemo } from 'react';\nimport type { ReactNode } from 'react';\nimport { AnalyticsContext } from './analytics-context';\nimport type { TrackFn } from './analytics-context';\n\n/* ============================================================================\n ANALYTICS PROVIDER — Event Tracking Context\n ============================================================================\n\n Provides a unified event tracking interface for all marketing/conversion\n components. Components call `useAnalytics().track(...)` without knowing\n which analytics backend is active.\n\n When no provider is present, all calls are no-ops — safe for Storybook,\n tests, and SSR.\n\n Usage:\n // In your app root:\n <AnalyticsProvider onTrack={(event, props) => gtag('event', event, props)}>\n <App />\n </AnalyticsProvider>\n\n // In any DS component:\n const { track } = useAnalytics();\n track('cta_click', { location: 'hero', label: 'Book Demo' });\n\n ============================================================================ */\n\n/** Props for the AnalyticsProvider component */\nexport interface AnalyticsProviderProps {\n /** Callback invoked on every track() call. Wire this to your analytics backend. */\n onTrack: TrackFn;\n /** Enable console logging in development (default: false) */\n debug?: boolean;\n children: ReactNode;\n}\n\n/**\n * Provides analytics event tracking to all descendant DS components.\n *\n * @example\n * ```tsx\n * <AnalyticsProvider\n * onTrack={(event, props) => {\n * window.gtag?.('event', event, props);\n * }}\n * >\n * <App />\n * </AnalyticsProvider>\n * ```\n */\nexport function AnalyticsProvider({ onTrack, debug = false, children }: AnalyticsProviderProps) {\n const track: TrackFn = useCallback(\n (event, props) => {\n if (debug && typeof console !== 'undefined') {\n // eslint-disable-next-line no-console\n console.log('[DS Analytics]', event, props);\n }\n onTrack(event, props);\n },\n [onTrack, debug],\n );\n\n const value = useMemo(() => ({ track }), [track]);\n\n return <AnalyticsContext.Provider value={value}>{children}</AnalyticsContext.Provider>;\n}\n"]}
|
|
@@ -44,6 +44,48 @@ var loadClarity = createAnalyticsLoader({
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
});
|
|
47
|
+
|
|
48
|
+
// src/web/analytics/region.ts
|
|
49
|
+
var NON_EEA_EUROPE_TIMEZONES = /* @__PURE__ */ new Set([
|
|
50
|
+
// Russia
|
|
51
|
+
"Europe/Moscow",
|
|
52
|
+
"Europe/Volgograd",
|
|
53
|
+
"Europe/Samara",
|
|
54
|
+
"Europe/Saratov",
|
|
55
|
+
"Europe/Ulyanovsk",
|
|
56
|
+
"Europe/Kaliningrad",
|
|
57
|
+
"Europe/Astrakhan",
|
|
58
|
+
"Europe/Kirov",
|
|
59
|
+
"Europe/Simferopol",
|
|
60
|
+
// Turkey
|
|
61
|
+
"Europe/Istanbul",
|
|
62
|
+
// Belarus
|
|
63
|
+
"Europe/Minsk",
|
|
64
|
+
// Western Balkans (non-EU)
|
|
65
|
+
"Europe/Belgrade",
|
|
66
|
+
"Europe/Sarajevo",
|
|
67
|
+
"Europe/Skopje",
|
|
68
|
+
"Europe/Tirane",
|
|
69
|
+
"Europe/Podgorica",
|
|
70
|
+
"Europe/Pristina",
|
|
71
|
+
// Moldova
|
|
72
|
+
"Europe/Chisinau",
|
|
73
|
+
// Ukraine
|
|
74
|
+
"Europe/Kyiv",
|
|
75
|
+
"Europe/Uzhgorod",
|
|
76
|
+
"Europe/Zaporozhye"
|
|
77
|
+
]);
|
|
78
|
+
function isEEAUser() {
|
|
79
|
+
if (typeof window === "undefined") return true;
|
|
80
|
+
try {
|
|
81
|
+
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
82
|
+
if (!tz) return true;
|
|
83
|
+
if (!tz.startsWith("Europe/")) return false;
|
|
84
|
+
return !NON_EEA_EUROPE_TIMEZONES.has(tz);
|
|
85
|
+
} catch {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
47
89
|
var COOKIE_CONSENT_EVENT = "cookie_consent_granted";
|
|
48
90
|
var COOKIE_CONSENT_KEY = "cookie_consent";
|
|
49
91
|
function CookieConsent({
|
|
@@ -57,12 +99,21 @@ function CookieConsent({
|
|
|
57
99
|
const [state, setState] = useState("idle");
|
|
58
100
|
useEffect(() => {
|
|
59
101
|
const consent = localStorage.getItem(COOKIE_CONSENT_KEY);
|
|
60
|
-
if (
|
|
61
|
-
|
|
62
|
-
return
|
|
63
|
-
}
|
|
102
|
+
if (consent) {
|
|
103
|
+
setState("closed");
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (!isEEAUser()) {
|
|
107
|
+
try {
|
|
108
|
+
localStorage.setItem(COOKIE_CONSENT_KEY, "granted");
|
|
109
|
+
window.dispatchEvent(new Event(COOKIE_CONSENT_EVENT));
|
|
110
|
+
} catch {
|
|
111
|
+
}
|
|
64
112
|
setState("closed");
|
|
113
|
+
return;
|
|
65
114
|
}
|
|
115
|
+
const timer = setTimeout(() => setState("open"), delay);
|
|
116
|
+
return () => clearTimeout(timer);
|
|
66
117
|
}, [delay]);
|
|
67
118
|
const handleDismiss = useCallback(() => {
|
|
68
119
|
setState("closing");
|
|
@@ -120,17 +171,29 @@ function CookieConsent({
|
|
|
120
171
|
);
|
|
121
172
|
}
|
|
122
173
|
function useCookieConsent() {
|
|
123
|
-
const [status, setStatus] = useState(
|
|
124
|
-
if (typeof window === "undefined") return null;
|
|
125
|
-
const stored = localStorage.getItem(COOKIE_CONSENT_KEY);
|
|
126
|
-
if (stored === "granted") return "granted";
|
|
127
|
-
if (stored === "denied") return "denied";
|
|
128
|
-
return null;
|
|
129
|
-
});
|
|
174
|
+
const [status, setStatus] = useState(null);
|
|
130
175
|
useEffect(() => {
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
176
|
+
const stored = localStorage.getItem(COOKIE_CONSENT_KEY);
|
|
177
|
+
if (stored === "granted") {
|
|
178
|
+
setStatus("granted");
|
|
179
|
+
} else if (stored === "denied") {
|
|
180
|
+
setStatus("denied");
|
|
181
|
+
} else if (!isEEAUser()) {
|
|
182
|
+
setStatus("granted");
|
|
183
|
+
}
|
|
184
|
+
const handleGranted = () => setStatus("granted");
|
|
185
|
+
const handleStorage = (e) => {
|
|
186
|
+
if (e.key !== COOKIE_CONSENT_KEY) return;
|
|
187
|
+
if (e.newValue === "granted") setStatus("granted");
|
|
188
|
+
else if (e.newValue === "denied") setStatus("denied");
|
|
189
|
+
else setStatus(isEEAUser() ? null : "granted");
|
|
190
|
+
};
|
|
191
|
+
window.addEventListener(COOKIE_CONSENT_EVENT, handleGranted);
|
|
192
|
+
window.addEventListener("storage", handleStorage);
|
|
193
|
+
return () => {
|
|
194
|
+
window.removeEventListener(COOKIE_CONSENT_EVENT, handleGranted);
|
|
195
|
+
window.removeEventListener("storage", handleStorage);
|
|
196
|
+
};
|
|
134
197
|
}, []);
|
|
135
198
|
return status;
|
|
136
199
|
}
|
|
@@ -148,6 +211,6 @@ function AnalyticsProvider({ onTrack, debug = false, children }) {
|
|
|
148
211
|
return /* @__PURE__ */ jsx(AnalyticsContext.Provider, { value, children });
|
|
149
212
|
}
|
|
150
213
|
|
|
151
|
-
export { AnalyticsProvider, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, CookieConsent, createAnalyticsLoader, loadClarity, loadGoogleAnalytics, useCookieConsent };
|
|
214
|
+
export { AnalyticsProvider, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, CookieConsent, createAnalyticsLoader, isEEAUser, loadClarity, loadGoogleAnalytics, useCookieConsent };
|
|
152
215
|
//# sourceMappingURL=out.js.map
|
|
153
|
-
//# sourceMappingURL=chunk-
|
|
216
|
+
//# sourceMappingURL=chunk-FZJLTJZS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/web/analytics/create-analytics-loader.ts","../src/web/analytics/region.ts","../src/web/CookieConsent/CookieConsent.tsx","../src/web/analytics/use-cookie-consent.ts","../src/web/analytics/analytics-provider.tsx"],"names":["useState","useEffect","useCallback","jsx"],"mappings":"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","sourcesContent":["/**\n * Generic analytics script loader.\n *\n * Framework-agnostic: injects a `<script>` tag into `<head>` once,\n * guards against double-loading, and respects cookie consent.\n *\n * @example\n * ```ts\n * import { createAnalyticsLoader } from '@salesmind-ai/design-system/web';\n *\n * const loadGA = createAnalyticsLoader({\n * id: 'ga-script',\n * src: (gaId) => `https://www.googletagmanager.com/gtag/js?id=${gaId}`,\n * onLoad: (gaId) => {\n * window.dataLayer = window.dataLayer || [];\n * function gtag(...args: unknown[]) { window.dataLayer.push(args); }\n * gtag('js', new Date());\n * gtag('config', gaId);\n * },\n * });\n *\n * // Later, after consent:\n * loadGA('G-XXXXXX');\n * ```\n */\n\nexport interface AnalyticsLoaderConfig<TId extends string = string> {\n /** Unique DOM id for the script element (prevents double-loading) */\n id: string;\n /** Build the script src URL from the tracking ID */\n src: (trackingId: TId) => string;\n /** Async attribute on the script tag (default: true) */\n async?: boolean;\n /** Called after the script is appended to the DOM */\n onLoad?: (trackingId: TId) => void;\n /** Custom inline script content instead of an external src */\n inlineScript?: (trackingId: TId) => string;\n}\n\n/**\n * Create a reusable analytics loader function.\n * The returned function injects the script once and is safe to call multiple times.\n */\nexport function createAnalyticsLoader<TId extends string = string>(\n config: AnalyticsLoaderConfig<TId>,\n): (trackingId: TId) => void {\n return (trackingId: TId) => {\n if (typeof document === 'undefined') return;\n if (document.getElementById(config.id)) return;\n\n const script = document.createElement('script');\n script.id = config.id;\n\n if (config.inlineScript) {\n script.textContent = config.inlineScript(trackingId);\n } else {\n script.async = config.async ?? true;\n script.src = config.src(trackingId);\n }\n\n document.head.appendChild(script);\n config.onLoad?.(trackingId);\n };\n}\n\n// ─── Pre-built Loaders ──────────────────────────────────────────────────────\n\ndeclare global {\n interface Window {\n dataLayer: unknown[];\n clarity?: (...args: unknown[]) => void;\n }\n}\n\n/**\n * Load Google Analytics (gtag.js).\n * Call with your GA measurement ID after cookie consent.\n */\nexport const loadGoogleAnalytics = createAnalyticsLoader({\n id: 'ga-script',\n src: (gaId) => `https://www.googletagmanager.com/gtag/js?id=${gaId}`,\n onLoad: (gaId) => {\n window.dataLayer = window.dataLayer || [];\n // Use array push directly to avoid @ts-ignore issues with gtag signature\n window.dataLayer.push(['js', new Date()]);\n window.dataLayer.push(['config', gaId]);\n },\n});\n\n/**\n * Load Microsoft Clarity.\n * Call with your Clarity project ID after cookie consent.\n */\nexport const loadClarity = createAnalyticsLoader({\n id: 'clarity-script',\n src: (clarityId) => `https://www.clarity.ms/tag/${clarityId}`,\n onLoad: () => {\n // Clarity auto-initializes from the script tag.\n // Expose the global `clarity` queue function for custom events.\n if (!window.clarity) {\n const queue: unknown[][] = [];\n window.clarity = (...args: unknown[]) => {\n queue.push(args);\n };\n (window.clarity as unknown as { q: unknown[][] }).q = queue;\n }\n },\n});\n","\"use client\";\n\n/**\n * EEA timezone-based detection for soft-consent flows.\n *\n * Returns `true` if the user's resolved timezone is in the European\n * Economic Area / UK / Switzerland (where GDPR strict consent applies).\n * Returns `false` for everyone else.\n *\n * Server-side and timezone-resolution failures return `true` (safe\n * default — assume GDPR applies).\n *\n * @example\n * ```ts\n * if (!isEEAUser()) {\n * // Auto-grant analytics consent for non-EEA visitors\n * localStorage.setItem('cookie_consent', 'granted');\n * }\n * ```\n */\n\n/**\n * Europe/* timezones that are NOT in the EEA and where GDPR-strict consent\n * rules do not apply. Includes Russia (multi-tz), Turkey, Ukraine, Belarus,\n * Moldova, and the Western Balkans (non-EU).\n */\nconst NON_EEA_EUROPE_TIMEZONES = new Set<string>([\n // Russia\n \"Europe/Moscow\",\n \"Europe/Volgograd\",\n \"Europe/Samara\",\n \"Europe/Saratov\",\n \"Europe/Ulyanovsk\",\n \"Europe/Kaliningrad\",\n \"Europe/Astrakhan\",\n \"Europe/Kirov\",\n \"Europe/Simferopol\",\n // Turkey\n \"Europe/Istanbul\",\n // Belarus\n \"Europe/Minsk\",\n // Western Balkans (non-EU)\n \"Europe/Belgrade\",\n \"Europe/Sarajevo\",\n \"Europe/Skopje\",\n \"Europe/Tirane\",\n \"Europe/Podgorica\",\n \"Europe/Pristina\",\n // Moldova\n \"Europe/Chisinau\",\n // Ukraine\n \"Europe/Kyiv\",\n \"Europe/Uzhgorod\",\n \"Europe/Zaporozhye\",\n]);\n\nexport function isEEAUser(): boolean {\n if (typeof window === \"undefined\") return true;\n try {\n const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;\n if (!tz) return true;\n if (!tz.startsWith(\"Europe/\")) return false;\n return !NON_EEA_EUROPE_TIMEZONES.has(tz);\n } catch {\n return true; // fail safe\n }\n}\n","\"use client\";\nimport { useState, useEffect, useCallback } from 'react';\nimport { Button } from '../../components/Button/Button';\nimport { OutboundLink } from '../../components/OutboundLink/OutboundLink';\nimport { X } from 'lucide-react';\nimport './CookieConsent.css';\nimport { isEEAUser } from '../analytics/region';\n\n/** Cookie consent state */\nexport type ConsentStatus = 'granted' | 'denied' | null;\n\n/** Event name dispatched on window when consent changes */\nexport const COOKIE_CONSENT_EVENT = 'cookie_consent_granted';\n\n/** localStorage key for cookie consent */\nexport const COOKIE_CONSENT_KEY = 'cookie_consent';\n\n/** Labels for i18n support */\nexport interface CookieConsentLabels {\n title?: string;\n description?: string;\n privacyLinkText?: string;\n acceptLabel?: string;\n declineLabel?: string;\n}\n\nexport interface CookieConsentProps {\n /** Delay in ms before showing the banner (default: 1000) */\n delay?: number;\n /** URL to the privacy policy page (default: \"/legal/privacy\") */\n privacyUrl?: string;\n /** Called when the user accepts cookies */\n onAccept?: () => void;\n /** Called when the user declines cookies */\n onDecline?: () => void;\n /** Override default labels for i18n */\n labels?: CookieConsentLabels;\n /** Custom className for the container */\n className?: string;\n}\n\n/**\n * CookieConsent — GDPR-compliant cookie consent banner.\n *\n * - Animated entrance/exit via CSS transitions (no framer-motion)\n * - Respects prior consent stored in localStorage\n * - Dispatches a `cookie_consent_granted` event on the window for analytics loaders\n * - Uses DS Button component\n *\n * @example\n * ```tsx\n * import { CookieConsent } from '@salesmind-ai/design-system/web';\n *\n * <CookieConsent\n * privacyUrl=\"/legal/privacy\"\n * onAccept={() => loadGoogleAnalytics('G-XXXX')}\n * />\n * ```\n */\nexport function CookieConsent({\n delay = 1000,\n privacyUrl = '/legal/privacy',\n onAccept,\n onDecline,\n labels,\n className,\n}: CookieConsentProps) {\n // 'idle' = not yet determined, 'open' = visible, 'closing' = exit animation, 'closed' = hidden\n const [state, setState] = useState<'idle' | 'open' | 'closing' | 'closed'>('idle');\n\n useEffect(() => {\n const consent = localStorage.getItem(COOKIE_CONSENT_KEY);\n if (consent) {\n setState('closed');\n return;\n }\n // Soft consent: non-EEA visitors auto-grant analytics. Persist the\n // implicit choice so cross-tab/cross-page state stays consistent,\n // skip the banner, and notify any subscribed hooks/listeners.\n if (!isEEAUser()) {\n try {\n localStorage.setItem(COOKIE_CONSENT_KEY, 'granted');\n window.dispatchEvent(new Event(COOKIE_CONSENT_EVENT));\n } catch {\n // localStorage may be unavailable (private mode, etc.) — fall through.\n }\n setState('closed');\n return;\n }\n const timer = setTimeout(() => setState('open'), delay);\n return () => clearTimeout(timer);\n }, [delay]);\n\n const handleDismiss = useCallback(() => {\n setState('closing');\n }, []);\n\n const handleAccept = useCallback(() => {\n localStorage.setItem(COOKIE_CONSENT_KEY, 'granted');\n handleDismiss();\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new Event(COOKIE_CONSENT_EVENT));\n }\n onAccept?.();\n }, [onAccept, handleDismiss]);\n\n const handleDecline = useCallback(() => {\n localStorage.setItem(COOKIE_CONSENT_KEY, 'denied');\n handleDismiss();\n onDecline?.();\n }, [onDecline, handleDismiss]);\n\n const handleTransitionEnd = useCallback(() => {\n if (state === 'closing') {\n setState('closed');\n }\n }, [state]);\n\n // Don't render anything until we know consent status, or after fully closed\n if (state === 'idle' || state === 'closed') return null;\n\n return (\n <div\n className={`ds-cookie-consent ${className ?? ''}`}\n data-state={state === 'open' ? 'open' : 'closed'}\n onTransitionEnd={handleTransitionEnd}\n role=\"dialog\"\n aria-label={labels?.title ?? 'Cookie consent'}\n >\n <div className=\"ds-cookie-consent__inner\">\n <div className=\"ds-cookie-consent__content\">\n <h3 className=\"ds-cookie-consent__title\">\n {labels?.title ?? 'We use cookies'}\n </h3>\n <p className=\"ds-cookie-consent__description\">\n {labels?.description ??\n 'We use tracking cookies to understand how you use the product and help us improve it.'}\n <OutboundLink href={privacyUrl} context=\"cookie-consent-privacy\" className=\"ds-cookie-consent__link\">\n {labels?.privacyLinkText ?? 'Privacy Policy'}\n </OutboundLink>\n </p>\n </div>\n <div className=\"ds-cookie-consent__actions\">\n <Button variant=\"outline\" size=\"sm\" onClick={handleDecline}>\n {labels?.declineLabel ?? 'Decline'}\n </Button>\n <Button variant=\"primary\" size=\"sm\" onClick={handleAccept}>\n {labels?.acceptLabel ?? 'Accept'}\n </Button>\n </div>\n <button\n onClick={handleDecline}\n className=\"ds-cookie-consent__close\"\n aria-label=\"Close\"\n >\n <X size={16} />\n </button>\n </div>\n </div>\n );\n}\n","\"use client\";\nimport { useState, useEffect } from 'react';\nimport { COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, type ConsentStatus } from '../CookieConsent/CookieConsent';\nimport { isEEAUser } from './region';\n\n/**\n * React hook that tracks cookie consent status.\n *\n * Returns `'granted'` once the user has granted cookie consent,\n * `'denied'` if denied, `null` if not yet decided.\n *\n * SOFT CONSENT: For non-EEA users (timezone-based detection), the default\n * resolves to `'granted'` after mount so analytics fire without requiring a\n * banner interaction. EEA users retain the strict `null → granted` flow via\n * the banner.\n *\n * Server-side renders as `null` (no window). The mount effect computes the\n * real status; this avoids hydration mismatches.\n *\n * Listens for the `cookie_consent_granted` window event dispatched by the\n * CookieConsent component, plus the cross-tab `storage` event.\n *\n * @example\n * ```tsx\n * const consent = useCookieConsent();\n *\n * useEffect(() => {\n * if (consent === 'granted') loadGoogleAnalytics('G-XXXX');\n * }, [consent]);\n * ```\n */\nexport function useCookieConsent(): ConsentStatus {\n const [status, setStatus] = useState<ConsentStatus>(null);\n\n useEffect(() => {\n // Compute initial status on mount only (avoids SSR mismatch).\n const stored = localStorage.getItem(COOKIE_CONSENT_KEY);\n if (stored === 'granted') {\n setStatus('granted');\n } else if (stored === 'denied') {\n setStatus('denied');\n } else if (!isEEAUser()) {\n // Soft consent: non-EEA users auto-grant on first visit.\n setStatus('granted');\n }\n // else: leave as null until the user interacts with the banner.\n\n const handleGranted = () => setStatus('granted');\n const handleStorage = (e: StorageEvent) => {\n if (e.key !== COOKIE_CONSENT_KEY) return;\n if (e.newValue === 'granted') setStatus('granted');\n else if (e.newValue === 'denied') setStatus('denied');\n else setStatus(isEEAUser() ? null : 'granted');\n };\n\n window.addEventListener(COOKIE_CONSENT_EVENT, handleGranted);\n window.addEventListener('storage', handleStorage);\n return () => {\n window.removeEventListener(COOKIE_CONSENT_EVENT, handleGranted);\n window.removeEventListener('storage', handleStorage);\n };\n }, []);\n\n return status;\n}\n","\"use client\";\nimport { useCallback, useMemo } from 'react';\nimport type { ReactNode } from 'react';\nimport { AnalyticsContext } from './analytics-context';\nimport type { TrackFn } from './analytics-context';\n\n/* ============================================================================\n ANALYTICS PROVIDER — Event Tracking Context\n ============================================================================\n\n Provides a unified event tracking interface for all marketing/conversion\n components. Components call `useAnalytics().track(...)` without knowing\n which analytics backend is active.\n\n When no provider is present, all calls are no-ops — safe for Storybook,\n tests, and SSR.\n\n Usage:\n // In your app root:\n <AnalyticsProvider onTrack={(event, props) => gtag('event', event, props)}>\n <App />\n </AnalyticsProvider>\n\n // In any DS component:\n const { track } = useAnalytics();\n track('cta_click', { location: 'hero', label: 'Book Demo' });\n\n ============================================================================ */\n\n/** Props for the AnalyticsProvider component */\nexport interface AnalyticsProviderProps {\n /** Callback invoked on every track() call. Wire this to your analytics backend. */\n onTrack: TrackFn;\n /** Enable console logging in development (default: false) */\n debug?: boolean;\n children: ReactNode;\n}\n\n/**\n * Provides analytics event tracking to all descendant DS components.\n *\n * @example\n * ```tsx\n * <AnalyticsProvider\n * onTrack={(event, props) => {\n * window.gtag?.('event', event, props);\n * }}\n * >\n * <App />\n * </AnalyticsProvider>\n * ```\n */\nexport function AnalyticsProvider({ onTrack, debug = false, children }: AnalyticsProviderProps) {\n const track: TrackFn = useCallback(\n (event, props) => {\n if (debug && typeof console !== 'undefined') {\n // eslint-disable-next-line no-console\n console.log('[DS Analytics]', event, props);\n }\n onTrack(event, props);\n },\n [onTrack, debug],\n );\n\n const value = useMemo(() => ({ track }), [track]);\n\n return <AnalyticsContext.Provider value={value}>{children}</AnalyticsContext.Provider>;\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var
|
|
4
|
+
var chunkDXFMZ4ME_cjs = require('../../chunk-DXFMZ4ME.cjs');
|
|
5
5
|
require('../../chunk-7EUR3AKV.cjs');
|
|
6
6
|
var chunkVC5LMUVQ_cjs = require('../../chunk-VC5LMUVQ.cjs');
|
|
7
7
|
require('../../chunk-LJADZITX.cjs');
|
|
@@ -10,35 +10,39 @@ require('../../chunk-LJADZITX.cjs');
|
|
|
10
10
|
|
|
11
11
|
Object.defineProperty(exports, "AnalyticsProvider", {
|
|
12
12
|
enumerable: true,
|
|
13
|
-
get: function () { return
|
|
13
|
+
get: function () { return chunkDXFMZ4ME_cjs.AnalyticsProvider; }
|
|
14
14
|
});
|
|
15
15
|
Object.defineProperty(exports, "COOKIE_CONSENT_EVENT", {
|
|
16
16
|
enumerable: true,
|
|
17
|
-
get: function () { return
|
|
17
|
+
get: function () { return chunkDXFMZ4ME_cjs.COOKIE_CONSENT_EVENT; }
|
|
18
18
|
});
|
|
19
19
|
Object.defineProperty(exports, "COOKIE_CONSENT_KEY", {
|
|
20
20
|
enumerable: true,
|
|
21
|
-
get: function () { return
|
|
21
|
+
get: function () { return chunkDXFMZ4ME_cjs.COOKIE_CONSENT_KEY; }
|
|
22
22
|
});
|
|
23
23
|
Object.defineProperty(exports, "CookieConsent", {
|
|
24
24
|
enumerable: true,
|
|
25
|
-
get: function () { return
|
|
25
|
+
get: function () { return chunkDXFMZ4ME_cjs.CookieConsent; }
|
|
26
26
|
});
|
|
27
27
|
Object.defineProperty(exports, "createAnalyticsLoader", {
|
|
28
28
|
enumerable: true,
|
|
29
|
-
get: function () { return
|
|
29
|
+
get: function () { return chunkDXFMZ4ME_cjs.createAnalyticsLoader; }
|
|
30
|
+
});
|
|
31
|
+
Object.defineProperty(exports, "isEEAUser", {
|
|
32
|
+
enumerable: true,
|
|
33
|
+
get: function () { return chunkDXFMZ4ME_cjs.isEEAUser; }
|
|
30
34
|
});
|
|
31
35
|
Object.defineProperty(exports, "loadClarity", {
|
|
32
36
|
enumerable: true,
|
|
33
|
-
get: function () { return
|
|
37
|
+
get: function () { return chunkDXFMZ4ME_cjs.loadClarity; }
|
|
34
38
|
});
|
|
35
39
|
Object.defineProperty(exports, "loadGoogleAnalytics", {
|
|
36
40
|
enumerable: true,
|
|
37
|
-
get: function () { return
|
|
41
|
+
get: function () { return chunkDXFMZ4ME_cjs.loadGoogleAnalytics; }
|
|
38
42
|
});
|
|
39
43
|
Object.defineProperty(exports, "useCookieConsent", {
|
|
40
44
|
enumerable: true,
|
|
41
|
-
get: function () { return
|
|
45
|
+
get: function () { return chunkDXFMZ4ME_cjs.useCookieConsent; }
|
|
42
46
|
});
|
|
43
47
|
Object.defineProperty(exports, "useAnalytics", {
|
|
44
48
|
enumerable: true,
|
|
@@ -111,23 +111,33 @@ declare function CookieConsent({ delay, privacyUrl, onAccept, onDecline, labels,
|
|
|
111
111
|
/**
|
|
112
112
|
* React hook that tracks cookie consent status.
|
|
113
113
|
*
|
|
114
|
-
* Returns `
|
|
115
|
-
* `
|
|
114
|
+
* Returns `'granted'` once the user has granted cookie consent,
|
|
115
|
+
* `'denied'` if denied, `null` if not yet decided.
|
|
116
116
|
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
117
|
+
* SOFT CONSENT: For non-EEA users (timezone-based detection), the default
|
|
118
|
+
* resolves to `'granted'` after mount so analytics fire without requiring a
|
|
119
|
+
* banner interaction. EEA users retain the strict `null → granted` flow via
|
|
120
|
+
* the banner.
|
|
121
|
+
*
|
|
122
|
+
* Server-side renders as `null` (no window). The mount effect computes the
|
|
123
|
+
* real status; this avoids hydration mismatches.
|
|
124
|
+
*
|
|
125
|
+
* Listens for the `cookie_consent_granted` window event dispatched by the
|
|
126
|
+
* CookieConsent component, plus the cross-tab `storage` event.
|
|
119
127
|
*
|
|
120
128
|
* @example
|
|
121
129
|
* ```tsx
|
|
122
|
-
* const
|
|
130
|
+
* const consent = useCookieConsent();
|
|
123
131
|
*
|
|
124
132
|
* useEffect(() => {
|
|
125
|
-
* if (
|
|
126
|
-
* }, [
|
|
133
|
+
* if (consent === 'granted') loadGoogleAnalytics('G-XXXX');
|
|
134
|
+
* }, [consent]);
|
|
127
135
|
* ```
|
|
128
136
|
*/
|
|
129
137
|
declare function useCookieConsent(): ConsentStatus;
|
|
130
138
|
|
|
139
|
+
declare function isEEAUser(): boolean;
|
|
140
|
+
|
|
131
141
|
/** Arbitrary properties bag attached to every analytics event. */
|
|
132
142
|
type AnalyticsEventProps = Record<string, string | number | boolean | undefined>;
|
|
133
143
|
/** Signature for the track function provided by AnalyticsProvider. */
|
|
@@ -169,4 +179,4 @@ declare function AnalyticsProvider({ onTrack, debug, children }: AnalyticsProvid
|
|
|
169
179
|
*/
|
|
170
180
|
declare function useAnalytics(): AnalyticsContextValue;
|
|
171
181
|
|
|
172
|
-
export { type AnalyticsContextValue, type AnalyticsEventProps, type AnalyticsLoaderConfig, AnalyticsProvider, type AnalyticsProviderProps, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, type ConsentStatus, CookieConsent, type CookieConsentLabels, type CookieConsentProps, type TrackFn, createAnalyticsLoader, loadClarity, loadGoogleAnalytics, useAnalytics, useCookieConsent };
|
|
182
|
+
export { type AnalyticsContextValue, type AnalyticsEventProps, type AnalyticsLoaderConfig, AnalyticsProvider, type AnalyticsProviderProps, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, type ConsentStatus, CookieConsent, type CookieConsentLabels, type CookieConsentProps, type TrackFn, createAnalyticsLoader, isEEAUser, loadClarity, loadGoogleAnalytics, useAnalytics, useCookieConsent };
|
|
@@ -111,23 +111,33 @@ declare function CookieConsent({ delay, privacyUrl, onAccept, onDecline, labels,
|
|
|
111
111
|
/**
|
|
112
112
|
* React hook that tracks cookie consent status.
|
|
113
113
|
*
|
|
114
|
-
* Returns `
|
|
115
|
-
* `
|
|
114
|
+
* Returns `'granted'` once the user has granted cookie consent,
|
|
115
|
+
* `'denied'` if denied, `null` if not yet decided.
|
|
116
116
|
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
117
|
+
* SOFT CONSENT: For non-EEA users (timezone-based detection), the default
|
|
118
|
+
* resolves to `'granted'` after mount so analytics fire without requiring a
|
|
119
|
+
* banner interaction. EEA users retain the strict `null → granted` flow via
|
|
120
|
+
* the banner.
|
|
121
|
+
*
|
|
122
|
+
* Server-side renders as `null` (no window). The mount effect computes the
|
|
123
|
+
* real status; this avoids hydration mismatches.
|
|
124
|
+
*
|
|
125
|
+
* Listens for the `cookie_consent_granted` window event dispatched by the
|
|
126
|
+
* CookieConsent component, plus the cross-tab `storage` event.
|
|
119
127
|
*
|
|
120
128
|
* @example
|
|
121
129
|
* ```tsx
|
|
122
|
-
* const
|
|
130
|
+
* const consent = useCookieConsent();
|
|
123
131
|
*
|
|
124
132
|
* useEffect(() => {
|
|
125
|
-
* if (
|
|
126
|
-
* }, [
|
|
133
|
+
* if (consent === 'granted') loadGoogleAnalytics('G-XXXX');
|
|
134
|
+
* }, [consent]);
|
|
127
135
|
* ```
|
|
128
136
|
*/
|
|
129
137
|
declare function useCookieConsent(): ConsentStatus;
|
|
130
138
|
|
|
139
|
+
declare function isEEAUser(): boolean;
|
|
140
|
+
|
|
131
141
|
/** Arbitrary properties bag attached to every analytics event. */
|
|
132
142
|
type AnalyticsEventProps = Record<string, string | number | boolean | undefined>;
|
|
133
143
|
/** Signature for the track function provided by AnalyticsProvider. */
|
|
@@ -169,4 +179,4 @@ declare function AnalyticsProvider({ onTrack, debug, children }: AnalyticsProvid
|
|
|
169
179
|
*/
|
|
170
180
|
declare function useAnalytics(): AnalyticsContextValue;
|
|
171
181
|
|
|
172
|
-
export { type AnalyticsContextValue, type AnalyticsEventProps, type AnalyticsLoaderConfig, AnalyticsProvider, type AnalyticsProviderProps, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, type ConsentStatus, CookieConsent, type CookieConsentLabels, type CookieConsentProps, type TrackFn, createAnalyticsLoader, loadClarity, loadGoogleAnalytics, useAnalytics, useCookieConsent };
|
|
182
|
+
export { type AnalyticsContextValue, type AnalyticsEventProps, type AnalyticsLoaderConfig, AnalyticsProvider, type AnalyticsProviderProps, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, type ConsentStatus, CookieConsent, type CookieConsentLabels, type CookieConsentProps, type TrackFn, createAnalyticsLoader, isEEAUser, loadClarity, loadGoogleAnalytics, useAnalytics, useCookieConsent };
|
package/dist/web/client/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
export { AnalyticsProvider, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, CookieConsent, createAnalyticsLoader, loadClarity, loadGoogleAnalytics, useCookieConsent } from '../../chunk-
|
|
2
|
+
export { AnalyticsProvider, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, CookieConsent, createAnalyticsLoader, isEEAUser, loadClarity, loadGoogleAnalytics, useCookieConsent } from '../../chunk-FZJLTJZS.js';
|
|
3
3
|
import '../../chunk-KJHPOB3J.js';
|
|
4
4
|
export { useAnalytics } from '../../chunk-2GARWEJK.js';
|
|
5
5
|
import '../../chunk-KJ2OXQF4.js';
|
package/dist/web/index.cjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
var chunkK7NQ7TQG_cjs = require('../chunk-K7NQ7TQG.cjs');
|
|
5
|
-
var
|
|
5
|
+
var chunkDXFMZ4ME_cjs = require('../chunk-DXFMZ4ME.cjs');
|
|
6
6
|
var chunkMQDEE7HC_cjs = require('../chunk-MQDEE7HC.cjs');
|
|
7
7
|
require('../chunk-7EUR3AKV.cjs');
|
|
8
8
|
var chunkVC5LMUVQ_cjs = require('../chunk-VC5LMUVQ.cjs');
|
|
@@ -39,35 +39,39 @@ Object.defineProperty(exports, "createSchemaGenerators", {
|
|
|
39
39
|
});
|
|
40
40
|
Object.defineProperty(exports, "AnalyticsProvider", {
|
|
41
41
|
enumerable: true,
|
|
42
|
-
get: function () { return
|
|
42
|
+
get: function () { return chunkDXFMZ4ME_cjs.AnalyticsProvider; }
|
|
43
43
|
});
|
|
44
44
|
Object.defineProperty(exports, "COOKIE_CONSENT_EVENT", {
|
|
45
45
|
enumerable: true,
|
|
46
|
-
get: function () { return
|
|
46
|
+
get: function () { return chunkDXFMZ4ME_cjs.COOKIE_CONSENT_EVENT; }
|
|
47
47
|
});
|
|
48
48
|
Object.defineProperty(exports, "COOKIE_CONSENT_KEY", {
|
|
49
49
|
enumerable: true,
|
|
50
|
-
get: function () { return
|
|
50
|
+
get: function () { return chunkDXFMZ4ME_cjs.COOKIE_CONSENT_KEY; }
|
|
51
51
|
});
|
|
52
52
|
Object.defineProperty(exports, "CookieConsent", {
|
|
53
53
|
enumerable: true,
|
|
54
|
-
get: function () { return
|
|
54
|
+
get: function () { return chunkDXFMZ4ME_cjs.CookieConsent; }
|
|
55
55
|
});
|
|
56
56
|
Object.defineProperty(exports, "createAnalyticsLoader", {
|
|
57
57
|
enumerable: true,
|
|
58
|
-
get: function () { return
|
|
58
|
+
get: function () { return chunkDXFMZ4ME_cjs.createAnalyticsLoader; }
|
|
59
|
+
});
|
|
60
|
+
Object.defineProperty(exports, "isEEAUser", {
|
|
61
|
+
enumerable: true,
|
|
62
|
+
get: function () { return chunkDXFMZ4ME_cjs.isEEAUser; }
|
|
59
63
|
});
|
|
60
64
|
Object.defineProperty(exports, "loadClarity", {
|
|
61
65
|
enumerable: true,
|
|
62
|
-
get: function () { return
|
|
66
|
+
get: function () { return chunkDXFMZ4ME_cjs.loadClarity; }
|
|
63
67
|
});
|
|
64
68
|
Object.defineProperty(exports, "loadGoogleAnalytics", {
|
|
65
69
|
enumerable: true,
|
|
66
|
-
get: function () { return
|
|
70
|
+
get: function () { return chunkDXFMZ4ME_cjs.loadGoogleAnalytics; }
|
|
67
71
|
});
|
|
68
72
|
Object.defineProperty(exports, "useCookieConsent", {
|
|
69
73
|
enumerable: true,
|
|
70
|
-
get: function () { return
|
|
74
|
+
get: function () { return chunkDXFMZ4ME_cjs.useCookieConsent; }
|
|
71
75
|
});
|
|
72
76
|
Object.defineProperty(exports, "UTM_CAMPAIGNS", {
|
|
73
77
|
enumerable: true,
|
package/dist/web/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/web/utm/UtmContext.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/web/utm/UtmContext.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCS;AADF,SAAS,YAAY,EAAE,QAAQ,SAAS,GAAqB;AAClE,SAAO,oBAAC,WAAW,UAAX,EAAoB,OAAO,QAAS,UAAS;AACvD","sourcesContent":["import { type ReactNode } from 'react';\nimport { UtmContext } from './utm-context';\n\n/* ============================================================================\n UTM Context — Page-Level Default UTM Parameters\n ============================================================================\n Provides default UtmParams to all downstream components (e.g. OutboundLink).\n Wrap a layout or page with <UtmProvider> so every outbound link on that page\n inherits governed UTM parameters without explicit prop drilling.\n\n Priority resolution in OutboundLink:\n 1. Explicit `utmParams` prop (highest)\n 2. UtmContext value (this provider)\n 3. Legacy fallback (lowest — deprecated)\n ============================================================================ */\n\nexport interface UtmProviderProps {\n /** Default UTM parameters for all descendants. */\n params: import('./types').UtmParams;\n children: ReactNode;\n}\n\n/**\n * Provides governed UTM defaults to all `OutboundLink` and UTM-aware\n * components in the subtree.\n *\n * @example\n * ```tsx\n * <UtmProvider params={{ source: 'website', medium: 'webBlogPost', campaign: 'demo' }}>\n * <BlogPostPage />\n * </UtmProvider>\n * ```\n */\nexport function UtmProvider({ params, children }: UtmProviderProps) {\n return <UtmContext.Provider value={params}>{children}</UtmContext.Provider>;\n}\n"]}
|
package/dist/web/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { ArticleMeta, BrandConfig, CareerForSchema, CustomerOrganizationMeta, FAQItem, GlossaryTermForSchema, HowToMeta, HowToStep, ItemForList, JsonLd, JsonLdProps, PersonMeta, PricingPlan, SeoBreadcrumbItem, ServiceMotion, TestimonialForSchema, TranscriptForSchema, VideoObjectMeta, aggregateRatingFromTestimonials, buildPageGraph, canonicalUrl, createEntityIds, createSchemaGenerators } from './server/index.cjs';
|
|
2
|
-
export { AnalyticsContextValue, AnalyticsEventProps, AnalyticsLoaderConfig, AnalyticsProvider, AnalyticsProviderProps, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, ConsentStatus, CookieConsent, CookieConsentLabels, CookieConsentProps, TrackFn, createAnalyticsLoader, loadClarity, loadGoogleAnalytics, useAnalytics, useCookieConsent } from './client/index.cjs';
|
|
2
|
+
export { AnalyticsContextValue, AnalyticsEventProps, AnalyticsLoaderConfig, AnalyticsProvider, AnalyticsProviderProps, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, ConsentStatus, CookieConsent, CookieConsentLabels, CookieConsentProps, TrackFn, createAnalyticsLoader, isEEAUser, loadClarity, loadGoogleAnalytics, useAnalytics, useCookieConsent } from './client/index.cjs';
|
|
3
3
|
import { U as UtmParams } from '../types-D5bPCZi6.cjs';
|
|
4
4
|
export { F as FirstTouchAttribution, a as UrlClassification, b as UtmAuditEntry, c as UtmBlockedError, d as UtmCampaign, e as UtmComplianceResult, f as UtmComplianceStatus, g as UtmConfidence, h as UtmContent, i as UtmMedium, j as UtmMediumAppPage, k as UtmMediumMessaging, l as UtmMediumWebPage, m as UtmOverrides, n as UtmSource, o as UtmSourceRequiringSeller, p as UtmTerm } from '../types-D5bPCZi6.cjs';
|
|
5
5
|
export { U as UTM_CAMPAIGNS, a as UTM_CONTENTS, b as UTM_MEDIUMS_ALL, c as UTM_MEDIUMS_APP, d as UTM_MEDIUMS_MESSAGING, e as UTM_MEDIUMS_WEB, f as UTM_SOURCES, g as UTM_SOURCES_REQUIRING_SELLER, h as UTM_TERMS, i as buildBlockedError, j as buildUtmUrl, k as classifyAndEnforce, l as classifyUrl, m as createAuditEntry, n as isUtmExempt, o as isValidUtmParams, p as parseUtmParams, r as requiresSellerAttribution, q as requiresUtm, t as toFirstTouchAttribution, v as validateCompliance, s as validateUtmField } from '../audit-CQvTUO39.cjs';
|
package/dist/web/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { ArticleMeta, BrandConfig, CareerForSchema, CustomerOrganizationMeta, FAQItem, GlossaryTermForSchema, HowToMeta, HowToStep, ItemForList, JsonLd, JsonLdProps, PersonMeta, PricingPlan, SeoBreadcrumbItem, ServiceMotion, TestimonialForSchema, TranscriptForSchema, VideoObjectMeta, aggregateRatingFromTestimonials, buildPageGraph, canonicalUrl, createEntityIds, createSchemaGenerators } from './server/index.js';
|
|
2
|
-
export { AnalyticsContextValue, AnalyticsEventProps, AnalyticsLoaderConfig, AnalyticsProvider, AnalyticsProviderProps, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, ConsentStatus, CookieConsent, CookieConsentLabels, CookieConsentProps, TrackFn, createAnalyticsLoader, loadClarity, loadGoogleAnalytics, useAnalytics, useCookieConsent } from './client/index.js';
|
|
2
|
+
export { AnalyticsContextValue, AnalyticsEventProps, AnalyticsLoaderConfig, AnalyticsProvider, AnalyticsProviderProps, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, ConsentStatus, CookieConsent, CookieConsentLabels, CookieConsentProps, TrackFn, createAnalyticsLoader, isEEAUser, loadClarity, loadGoogleAnalytics, useAnalytics, useCookieConsent } from './client/index.js';
|
|
3
3
|
import { U as UtmParams } from '../types-D5bPCZi6.js';
|
|
4
4
|
export { F as FirstTouchAttribution, a as UrlClassification, b as UtmAuditEntry, c as UtmBlockedError, d as UtmCampaign, e as UtmComplianceResult, f as UtmComplianceStatus, g as UtmConfidence, h as UtmContent, i as UtmMedium, j as UtmMediumAppPage, k as UtmMediumMessaging, l as UtmMediumWebPage, m as UtmOverrides, n as UtmSource, o as UtmSourceRequiringSeller, p as UtmTerm } from '../types-D5bPCZi6.js';
|
|
5
5
|
export { U as UTM_CAMPAIGNS, a as UTM_CONTENTS, b as UTM_MEDIUMS_ALL, c as UTM_MEDIUMS_APP, d as UTM_MEDIUMS_MESSAGING, e as UTM_MEDIUMS_WEB, f as UTM_SOURCES, g as UTM_SOURCES_REQUIRING_SELLER, h as UTM_TERMS, i as buildBlockedError, j as buildUtmUrl, k as classifyAndEnforce, l as classifyUrl, m as createAuditEntry, n as isUtmExempt, o as isValidUtmParams, p as parseUtmParams, r as requiresSellerAttribution, q as requiresUtm, t as toFirstTouchAttribution, v as validateCompliance, s as validateUtmField } from '../audit-oRc-Dccv.js';
|
package/dist/web/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
export { JsonLd, aggregateRatingFromTestimonials, buildPageGraph, canonicalUrl, createEntityIds, createSchemaGenerators } from '../chunk-25TUCNN6.js';
|
|
3
|
-
export { AnalyticsProvider, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, CookieConsent, createAnalyticsLoader, loadClarity, loadGoogleAnalytics, useCookieConsent } from '../chunk-
|
|
3
|
+
export { AnalyticsProvider, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, CookieConsent, createAnalyticsLoader, isEEAUser, loadClarity, loadGoogleAnalytics, useCookieConsent } from '../chunk-FZJLTJZS.js';
|
|
4
4
|
export { UTM_CAMPAIGNS, UTM_CONTENTS, UTM_MEDIUMS_ALL, UTM_MEDIUMS_APP, UTM_MEDIUMS_MESSAGING, UTM_MEDIUMS_WEB, UTM_SOURCES, UTM_SOURCES_REQUIRING_SELLER, UTM_TERMS, buildBlockedError, classifyAndEnforce, createAuditEntry, isValidUtmParams, parseUtmParams, requiresSellerAttribution, toFirstTouchAttribution, validateCompliance, validateUtmField } from '../chunk-BILT5KD3.js';
|
|
5
5
|
import '../chunk-KJHPOB3J.js';
|
|
6
6
|
export { useAnalytics } from '../chunk-2GARWEJK.js';
|
package/dist/web/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/web/utm/UtmContext.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/web/utm/UtmContext.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCS;AADF,SAAS,YAAY,EAAE,QAAQ,SAAS,GAAqB;AAClE,SAAO,oBAAC,WAAW,UAAX,EAAoB,OAAO,QAAS,UAAS;AACvD","sourcesContent":["import { type ReactNode } from 'react';\nimport { UtmContext } from './utm-context';\n\n/* ============================================================================\n UTM Context — Page-Level Default UTM Parameters\n ============================================================================\n Provides default UtmParams to all downstream components (e.g. OutboundLink).\n Wrap a layout or page with <UtmProvider> so every outbound link on that page\n inherits governed UTM parameters without explicit prop drilling.\n\n Priority resolution in OutboundLink:\n 1. Explicit `utmParams` prop (highest)\n 2. UtmContext value (this provider)\n 3. Legacy fallback (lowest — deprecated)\n ============================================================================ */\n\nexport interface UtmProviderProps {\n /** Default UTM parameters for all descendants. */\n params: import('./types').UtmParams;\n children: ReactNode;\n}\n\n/**\n * Provides governed UTM defaults to all `OutboundLink` and UTM-aware\n * components in the subtree.\n *\n * @example\n * ```tsx\n * <UtmProvider params={{ source: 'website', medium: 'webBlogPost', campaign: 'demo' }}>\n * <BlogPostPage />\n * </UtmProvider>\n * ```\n */\nexport function UtmProvider({ params, children }: UtmProviderProps) {\n return <UtmContext.Provider value={params}>{children}</UtmContext.Provider>;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/web/analytics/create-analytics-loader.ts","../src/web/CookieConsent/CookieConsent.tsx","../src/web/analytics/use-cookie-consent.ts","../src/web/analytics/analytics-provider.tsx"],"names":["useState","useEffect","useCallback","jsx"],"mappings":"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","sourcesContent":["/**\n * Generic analytics script loader.\n *\n * Framework-agnostic: injects a `<script>` tag into `<head>` once,\n * guards against double-loading, and respects cookie consent.\n *\n * @example\n * ```ts\n * import { createAnalyticsLoader } from '@salesmind-ai/design-system/web';\n *\n * const loadGA = createAnalyticsLoader({\n * id: 'ga-script',\n * src: (gaId) => `https://www.googletagmanager.com/gtag/js?id=${gaId}`,\n * onLoad: (gaId) => {\n * window.dataLayer = window.dataLayer || [];\n * function gtag(...args: unknown[]) { window.dataLayer.push(args); }\n * gtag('js', new Date());\n * gtag('config', gaId);\n * },\n * });\n *\n * // Later, after consent:\n * loadGA('G-XXXXXX');\n * ```\n */\n\nexport interface AnalyticsLoaderConfig<TId extends string = string> {\n /** Unique DOM id for the script element (prevents double-loading) */\n id: string;\n /** Build the script src URL from the tracking ID */\n src: (trackingId: TId) => string;\n /** Async attribute on the script tag (default: true) */\n async?: boolean;\n /** Called after the script is appended to the DOM */\n onLoad?: (trackingId: TId) => void;\n /** Custom inline script content instead of an external src */\n inlineScript?: (trackingId: TId) => string;\n}\n\n/**\n * Create a reusable analytics loader function.\n * The returned function injects the script once and is safe to call multiple times.\n */\nexport function createAnalyticsLoader<TId extends string = string>(\n config: AnalyticsLoaderConfig<TId>,\n): (trackingId: TId) => void {\n return (trackingId: TId) => {\n if (typeof document === 'undefined') return;\n if (document.getElementById(config.id)) return;\n\n const script = document.createElement('script');\n script.id = config.id;\n\n if (config.inlineScript) {\n script.textContent = config.inlineScript(trackingId);\n } else {\n script.async = config.async ?? true;\n script.src = config.src(trackingId);\n }\n\n document.head.appendChild(script);\n config.onLoad?.(trackingId);\n };\n}\n\n// ─── Pre-built Loaders ──────────────────────────────────────────────────────\n\ndeclare global {\n interface Window {\n dataLayer: unknown[];\n clarity?: (...args: unknown[]) => void;\n }\n}\n\n/**\n * Load Google Analytics (gtag.js).\n * Call with your GA measurement ID after cookie consent.\n */\nexport const loadGoogleAnalytics = createAnalyticsLoader({\n id: 'ga-script',\n src: (gaId) => `https://www.googletagmanager.com/gtag/js?id=${gaId}`,\n onLoad: (gaId) => {\n window.dataLayer = window.dataLayer || [];\n // Use array push directly to avoid @ts-ignore issues with gtag signature\n window.dataLayer.push(['js', new Date()]);\n window.dataLayer.push(['config', gaId]);\n },\n});\n\n/**\n * Load Microsoft Clarity.\n * Call with your Clarity project ID after cookie consent.\n */\nexport const loadClarity = createAnalyticsLoader({\n id: 'clarity-script',\n src: (clarityId) => `https://www.clarity.ms/tag/${clarityId}`,\n onLoad: () => {\n // Clarity auto-initializes from the script tag.\n // Expose the global `clarity` queue function for custom events.\n if (!window.clarity) {\n const queue: unknown[][] = [];\n window.clarity = (...args: unknown[]) => {\n queue.push(args);\n };\n (window.clarity as unknown as { q: unknown[][] }).q = queue;\n }\n },\n});\n","\"use client\";\nimport { useState, useEffect, useCallback } from 'react';\nimport { Button } from '../../components/Button/Button';\nimport { OutboundLink } from '../../components/OutboundLink/OutboundLink';\nimport { X } from 'lucide-react';\nimport './CookieConsent.css';\n\n/** Cookie consent state */\nexport type ConsentStatus = 'granted' | 'denied' | null;\n\n/** Event name dispatched on window when consent changes */\nexport const COOKIE_CONSENT_EVENT = 'cookie_consent_granted';\n\n/** localStorage key for cookie consent */\nexport const COOKIE_CONSENT_KEY = 'cookie_consent';\n\n/** Labels for i18n support */\nexport interface CookieConsentLabels {\n title?: string;\n description?: string;\n privacyLinkText?: string;\n acceptLabel?: string;\n declineLabel?: string;\n}\n\nexport interface CookieConsentProps {\n /** Delay in ms before showing the banner (default: 1000) */\n delay?: number;\n /** URL to the privacy policy page (default: \"/legal/privacy\") */\n privacyUrl?: string;\n /** Called when the user accepts cookies */\n onAccept?: () => void;\n /** Called when the user declines cookies */\n onDecline?: () => void;\n /** Override default labels for i18n */\n labels?: CookieConsentLabels;\n /** Custom className for the container */\n className?: string;\n}\n\n/**\n * CookieConsent — GDPR-compliant cookie consent banner.\n *\n * - Animated entrance/exit via CSS transitions (no framer-motion)\n * - Respects prior consent stored in localStorage\n * - Dispatches a `cookie_consent_granted` event on the window for analytics loaders\n * - Uses DS Button component\n *\n * @example\n * ```tsx\n * import { CookieConsent } from '@salesmind-ai/design-system/web';\n *\n * <CookieConsent\n * privacyUrl=\"/legal/privacy\"\n * onAccept={() => loadGoogleAnalytics('G-XXXX')}\n * />\n * ```\n */\nexport function CookieConsent({\n delay = 1000,\n privacyUrl = '/legal/privacy',\n onAccept,\n onDecline,\n labels,\n className,\n}: CookieConsentProps) {\n // 'idle' = not yet determined, 'open' = visible, 'closing' = exit animation, 'closed' = hidden\n const [state, setState] = useState<'idle' | 'open' | 'closing' | 'closed'>('idle');\n\n useEffect(() => {\n const consent = localStorage.getItem(COOKIE_CONSENT_KEY);\n if (!consent) {\n const timer = setTimeout(() => setState('open'), delay);\n return () => clearTimeout(timer);\n } else {\n setState('closed');\n }\n }, [delay]);\n\n const handleDismiss = useCallback(() => {\n setState('closing');\n }, []);\n\n const handleAccept = useCallback(() => {\n localStorage.setItem(COOKIE_CONSENT_KEY, 'granted');\n handleDismiss();\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new Event(COOKIE_CONSENT_EVENT));\n }\n onAccept?.();\n }, [onAccept, handleDismiss]);\n\n const handleDecline = useCallback(() => {\n localStorage.setItem(COOKIE_CONSENT_KEY, 'denied');\n handleDismiss();\n onDecline?.();\n }, [onDecline, handleDismiss]);\n\n const handleTransitionEnd = useCallback(() => {\n if (state === 'closing') {\n setState('closed');\n }\n }, [state]);\n\n // Don't render anything until we know consent status, or after fully closed\n if (state === 'idle' || state === 'closed') return null;\n\n return (\n <div\n className={`ds-cookie-consent ${className ?? ''}`}\n data-state={state === 'open' ? 'open' : 'closed'}\n onTransitionEnd={handleTransitionEnd}\n role=\"dialog\"\n aria-label={labels?.title ?? 'Cookie consent'}\n >\n <div className=\"ds-cookie-consent__inner\">\n <div className=\"ds-cookie-consent__content\">\n <h3 className=\"ds-cookie-consent__title\">\n {labels?.title ?? 'We use cookies'}\n </h3>\n <p className=\"ds-cookie-consent__description\">\n {labels?.description ??\n 'We use tracking cookies to understand how you use the product and help us improve it.'}\n <OutboundLink href={privacyUrl} context=\"cookie-consent-privacy\" className=\"ds-cookie-consent__link\">\n {labels?.privacyLinkText ?? 'Privacy Policy'}\n </OutboundLink>\n </p>\n </div>\n <div className=\"ds-cookie-consent__actions\">\n <Button variant=\"outline\" size=\"sm\" onClick={handleDecline}>\n {labels?.declineLabel ?? 'Decline'}\n </Button>\n <Button variant=\"primary\" size=\"sm\" onClick={handleAccept}>\n {labels?.acceptLabel ?? 'Accept'}\n </Button>\n </div>\n <button\n onClick={handleDecline}\n className=\"ds-cookie-consent__close\"\n aria-label=\"Close\"\n >\n <X size={16} />\n </button>\n </div>\n </div>\n );\n}\n","\"use client\";\nimport { useState, useEffect } from 'react';\nimport { COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, type ConsentStatus } from '../CookieConsent/CookieConsent';\n\n/**\n * React hook that tracks cookie consent status.\n *\n * Returns `true` once the user has granted cookie consent,\n * `false` if denied, `null` if not yet decided.\n *\n * Listens for the `cookie_consent_granted` window event dispatched\n * by the CookieConsent component.\n *\n * @example\n * ```tsx\n * const hasConsent = useCookieConsent();\n *\n * useEffect(() => {\n * if (hasConsent) loadGoogleAnalytics('G-XXXX');\n * }, [hasConsent]);\n * ```\n */\nexport function useCookieConsent(): ConsentStatus {\n const [status, setStatus] = useState<ConsentStatus>(() => {\n if (typeof window === 'undefined') return null;\n const stored = localStorage.getItem(COOKIE_CONSENT_KEY);\n if (stored === 'granted') return 'granted';\n if (stored === 'denied') return 'denied';\n return null;\n });\n\n useEffect(() => {\n const handleConsent = () => setStatus('granted');\n window.addEventListener(COOKIE_CONSENT_EVENT, handleConsent);\n return () => window.removeEventListener(COOKIE_CONSENT_EVENT, handleConsent);\n }, []);\n\n return status;\n}\n","\"use client\";\nimport { useCallback, useMemo } from 'react';\nimport type { ReactNode } from 'react';\nimport { AnalyticsContext } from './analytics-context';\nimport type { TrackFn } from './analytics-context';\n\n/* ============================================================================\n ANALYTICS PROVIDER — Event Tracking Context\n ============================================================================\n\n Provides a unified event tracking interface for all marketing/conversion\n components. Components call `useAnalytics().track(...)` without knowing\n which analytics backend is active.\n\n When no provider is present, all calls are no-ops — safe for Storybook,\n tests, and SSR.\n\n Usage:\n // In your app root:\n <AnalyticsProvider onTrack={(event, props) => gtag('event', event, props)}>\n <App />\n </AnalyticsProvider>\n\n // In any DS component:\n const { track } = useAnalytics();\n track('cta_click', { location: 'hero', label: 'Book Demo' });\n\n ============================================================================ */\n\n/** Props for the AnalyticsProvider component */\nexport interface AnalyticsProviderProps {\n /** Callback invoked on every track() call. Wire this to your analytics backend. */\n onTrack: TrackFn;\n /** Enable console logging in development (default: false) */\n debug?: boolean;\n children: ReactNode;\n}\n\n/**\n * Provides analytics event tracking to all descendant DS components.\n *\n * @example\n * ```tsx\n * <AnalyticsProvider\n * onTrack={(event, props) => {\n * window.gtag?.('event', event, props);\n * }}\n * >\n * <App />\n * </AnalyticsProvider>\n * ```\n */\nexport function AnalyticsProvider({ onTrack, debug = false, children }: AnalyticsProviderProps) {\n const track: TrackFn = useCallback(\n (event, props) => {\n if (debug && typeof console !== 'undefined') {\n // eslint-disable-next-line no-console\n console.log('[DS Analytics]', event, props);\n }\n onTrack(event, props);\n },\n [onTrack, debug],\n );\n\n const value = useMemo(() => ({ track }), [track]);\n\n return <AnalyticsContext.Provider value={value}>{children}</AnalyticsContext.Provider>;\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/web/analytics/create-analytics-loader.ts","../src/web/CookieConsent/CookieConsent.tsx","../src/web/analytics/use-cookie-consent.ts","../src/web/analytics/analytics-provider.tsx"],"names":["useState","useEffect","useCallback","jsx"],"mappings":"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","sourcesContent":["/**\n * Generic analytics script loader.\n *\n * Framework-agnostic: injects a `<script>` tag into `<head>` once,\n * guards against double-loading, and respects cookie consent.\n *\n * @example\n * ```ts\n * import { createAnalyticsLoader } from '@salesmind-ai/design-system/web';\n *\n * const loadGA = createAnalyticsLoader({\n * id: 'ga-script',\n * src: (gaId) => `https://www.googletagmanager.com/gtag/js?id=${gaId}`,\n * onLoad: (gaId) => {\n * window.dataLayer = window.dataLayer || [];\n * function gtag(...args: unknown[]) { window.dataLayer.push(args); }\n * gtag('js', new Date());\n * gtag('config', gaId);\n * },\n * });\n *\n * // Later, after consent:\n * loadGA('G-XXXXXX');\n * ```\n */\n\nexport interface AnalyticsLoaderConfig<TId extends string = string> {\n /** Unique DOM id for the script element (prevents double-loading) */\n id: string;\n /** Build the script src URL from the tracking ID */\n src: (trackingId: TId) => string;\n /** Async attribute on the script tag (default: true) */\n async?: boolean;\n /** Called after the script is appended to the DOM */\n onLoad?: (trackingId: TId) => void;\n /** Custom inline script content instead of an external src */\n inlineScript?: (trackingId: TId) => string;\n}\n\n/**\n * Create a reusable analytics loader function.\n * The returned function injects the script once and is safe to call multiple times.\n */\nexport function createAnalyticsLoader<TId extends string = string>(\n config: AnalyticsLoaderConfig<TId>,\n): (trackingId: TId) => void {\n return (trackingId: TId) => {\n if (typeof document === 'undefined') return;\n if (document.getElementById(config.id)) return;\n\n const script = document.createElement('script');\n script.id = config.id;\n\n if (config.inlineScript) {\n script.textContent = config.inlineScript(trackingId);\n } else {\n script.async = config.async ?? true;\n script.src = config.src(trackingId);\n }\n\n document.head.appendChild(script);\n config.onLoad?.(trackingId);\n };\n}\n\n// ─── Pre-built Loaders ──────────────────────────────────────────────────────\n\ndeclare global {\n interface Window {\n dataLayer: unknown[];\n clarity?: (...args: unknown[]) => void;\n }\n}\n\n/**\n * Load Google Analytics (gtag.js).\n * Call with your GA measurement ID after cookie consent.\n */\nexport const loadGoogleAnalytics = createAnalyticsLoader({\n id: 'ga-script',\n src: (gaId) => `https://www.googletagmanager.com/gtag/js?id=${gaId}`,\n onLoad: (gaId) => {\n window.dataLayer = window.dataLayer || [];\n // Use array push directly to avoid @ts-ignore issues with gtag signature\n window.dataLayer.push(['js', new Date()]);\n window.dataLayer.push(['config', gaId]);\n },\n});\n\n/**\n * Load Microsoft Clarity.\n * Call with your Clarity project ID after cookie consent.\n */\nexport const loadClarity = createAnalyticsLoader({\n id: 'clarity-script',\n src: (clarityId) => `https://www.clarity.ms/tag/${clarityId}`,\n onLoad: () => {\n // Clarity auto-initializes from the script tag.\n // Expose the global `clarity` queue function for custom events.\n if (!window.clarity) {\n const queue: unknown[][] = [];\n window.clarity = (...args: unknown[]) => {\n queue.push(args);\n };\n (window.clarity as unknown as { q: unknown[][] }).q = queue;\n }\n },\n});\n","\"use client\";\nimport { useState, useEffect, useCallback } from 'react';\nimport { Button } from '../../components/Button/Button';\nimport { OutboundLink } from '../../components/OutboundLink/OutboundLink';\nimport { X } from 'lucide-react';\nimport './CookieConsent.css';\n\n/** Cookie consent state */\nexport type ConsentStatus = 'granted' | 'denied' | null;\n\n/** Event name dispatched on window when consent changes */\nexport const COOKIE_CONSENT_EVENT = 'cookie_consent_granted';\n\n/** localStorage key for cookie consent */\nexport const COOKIE_CONSENT_KEY = 'cookie_consent';\n\n/** Labels for i18n support */\nexport interface CookieConsentLabels {\n title?: string;\n description?: string;\n privacyLinkText?: string;\n acceptLabel?: string;\n declineLabel?: string;\n}\n\nexport interface CookieConsentProps {\n /** Delay in ms before showing the banner (default: 1000) */\n delay?: number;\n /** URL to the privacy policy page (default: \"/legal/privacy\") */\n privacyUrl?: string;\n /** Called when the user accepts cookies */\n onAccept?: () => void;\n /** Called when the user declines cookies */\n onDecline?: () => void;\n /** Override default labels for i18n */\n labels?: CookieConsentLabels;\n /** Custom className for the container */\n className?: string;\n}\n\n/**\n * CookieConsent — GDPR-compliant cookie consent banner.\n *\n * - Animated entrance/exit via CSS transitions (no framer-motion)\n * - Respects prior consent stored in localStorage\n * - Dispatches a `cookie_consent_granted` event on the window for analytics loaders\n * - Uses DS Button component\n *\n * @example\n * ```tsx\n * import { CookieConsent } from '@salesmind-ai/design-system/web';\n *\n * <CookieConsent\n * privacyUrl=\"/legal/privacy\"\n * onAccept={() => loadGoogleAnalytics('G-XXXX')}\n * />\n * ```\n */\nexport function CookieConsent({\n delay = 1000,\n privacyUrl = '/legal/privacy',\n onAccept,\n onDecline,\n labels,\n className,\n}: CookieConsentProps) {\n // 'idle' = not yet determined, 'open' = visible, 'closing' = exit animation, 'closed' = hidden\n const [state, setState] = useState<'idle' | 'open' | 'closing' | 'closed'>('idle');\n\n useEffect(() => {\n const consent = localStorage.getItem(COOKIE_CONSENT_KEY);\n if (!consent) {\n const timer = setTimeout(() => setState('open'), delay);\n return () => clearTimeout(timer);\n } else {\n setState('closed');\n }\n }, [delay]);\n\n const handleDismiss = useCallback(() => {\n setState('closing');\n }, []);\n\n const handleAccept = useCallback(() => {\n localStorage.setItem(COOKIE_CONSENT_KEY, 'granted');\n handleDismiss();\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new Event(COOKIE_CONSENT_EVENT));\n }\n onAccept?.();\n }, [onAccept, handleDismiss]);\n\n const handleDecline = useCallback(() => {\n localStorage.setItem(COOKIE_CONSENT_KEY, 'denied');\n handleDismiss();\n onDecline?.();\n }, [onDecline, handleDismiss]);\n\n const handleTransitionEnd = useCallback(() => {\n if (state === 'closing') {\n setState('closed');\n }\n }, [state]);\n\n // Don't render anything until we know consent status, or after fully closed\n if (state === 'idle' || state === 'closed') return null;\n\n return (\n <div\n className={`ds-cookie-consent ${className ?? ''}`}\n data-state={state === 'open' ? 'open' : 'closed'}\n onTransitionEnd={handleTransitionEnd}\n role=\"dialog\"\n aria-label={labels?.title ?? 'Cookie consent'}\n >\n <div className=\"ds-cookie-consent__inner\">\n <div className=\"ds-cookie-consent__content\">\n <h3 className=\"ds-cookie-consent__title\">\n {labels?.title ?? 'We use cookies'}\n </h3>\n <p className=\"ds-cookie-consent__description\">\n {labels?.description ??\n 'We use tracking cookies to understand how you use the product and help us improve it.'}\n <OutboundLink href={privacyUrl} context=\"cookie-consent-privacy\" className=\"ds-cookie-consent__link\">\n {labels?.privacyLinkText ?? 'Privacy Policy'}\n </OutboundLink>\n </p>\n </div>\n <div className=\"ds-cookie-consent__actions\">\n <Button variant=\"outline\" size=\"sm\" onClick={handleDecline}>\n {labels?.declineLabel ?? 'Decline'}\n </Button>\n <Button variant=\"primary\" size=\"sm\" onClick={handleAccept}>\n {labels?.acceptLabel ?? 'Accept'}\n </Button>\n </div>\n <button\n onClick={handleDecline}\n className=\"ds-cookie-consent__close\"\n aria-label=\"Close\"\n >\n <X size={16} />\n </button>\n </div>\n </div>\n );\n}\n","\"use client\";\nimport { useState, useEffect } from 'react';\nimport { COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, type ConsentStatus } from '../CookieConsent/CookieConsent';\n\n/**\n * React hook that tracks cookie consent status.\n *\n * Returns `true` once the user has granted cookie consent,\n * `false` if denied, `null` if not yet decided.\n *\n * Listens for the `cookie_consent_granted` window event dispatched\n * by the CookieConsent component.\n *\n * @example\n * ```tsx\n * const hasConsent = useCookieConsent();\n *\n * useEffect(() => {\n * if (hasConsent) loadGoogleAnalytics('G-XXXX');\n * }, [hasConsent]);\n * ```\n */\nexport function useCookieConsent(): ConsentStatus {\n const [status, setStatus] = useState<ConsentStatus>(() => {\n if (typeof window === 'undefined') return null;\n const stored = localStorage.getItem(COOKIE_CONSENT_KEY);\n if (stored === 'granted') return 'granted';\n if (stored === 'denied') return 'denied';\n return null;\n });\n\n useEffect(() => {\n const handleConsent = () => setStatus('granted');\n window.addEventListener(COOKIE_CONSENT_EVENT, handleConsent);\n return () => window.removeEventListener(COOKIE_CONSENT_EVENT, handleConsent);\n }, []);\n\n return status;\n}\n","\"use client\";\nimport { useCallback, useMemo } from 'react';\nimport type { ReactNode } from 'react';\nimport { AnalyticsContext } from './analytics-context';\nimport type { TrackFn } from './analytics-context';\n\n/* ============================================================================\n ANALYTICS PROVIDER — Event Tracking Context\n ============================================================================\n\n Provides a unified event tracking interface for all marketing/conversion\n components. Components call `useAnalytics().track(...)` without knowing\n which analytics backend is active.\n\n When no provider is present, all calls are no-ops — safe for Storybook,\n tests, and SSR.\n\n Usage:\n // In your app root:\n <AnalyticsProvider onTrack={(event, props) => gtag('event', event, props)}>\n <App />\n </AnalyticsProvider>\n\n // In any DS component:\n const { track } = useAnalytics();\n track('cta_click', { location: 'hero', label: 'Book Demo' });\n\n ============================================================================ */\n\n/** Props for the AnalyticsProvider component */\nexport interface AnalyticsProviderProps {\n /** Callback invoked on every track() call. Wire this to your analytics backend. */\n onTrack: TrackFn;\n /** Enable console logging in development (default: false) */\n debug?: boolean;\n children: ReactNode;\n}\n\n/**\n * Provides analytics event tracking to all descendant DS components.\n *\n * @example\n * ```tsx\n * <AnalyticsProvider\n * onTrack={(event, props) => {\n * window.gtag?.('event', event, props);\n * }}\n * >\n * <App />\n * </AnalyticsProvider>\n * ```\n */\nexport function AnalyticsProvider({ onTrack, debug = false, children }: AnalyticsProviderProps) {\n const track: TrackFn = useCallback(\n (event, props) => {\n if (debug && typeof console !== 'undefined') {\n // eslint-disable-next-line no-console\n console.log('[DS Analytics]', event, props);\n }\n onTrack(event, props);\n },\n [onTrack, debug],\n );\n\n const value = useMemo(() => ({ track }), [track]);\n\n return <AnalyticsContext.Provider value={value}>{children}</AnalyticsContext.Provider>;\n}\n"]}
|