keystone-design-bootstrap 1.0.97 → 1.0.99
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/contexts/index.js +134 -1
- package/dist/contexts/index.js.map +1 -1
- package/dist/design_system/components/DynamicFormFields.js +130 -3
- package/dist/design_system/components/DynamicFormFields.js.map +1 -1
- package/dist/design_system/elements/index.js +130 -3
- package/dist/design_system/elements/index.js.map +1 -1
- package/dist/design_system/sections/index.js +150 -9
- package/dist/design_system/sections/index.js.map +1 -1
- package/dist/index.js +184 -21
- package/dist/index.js.map +1 -1
- package/dist/lib/server-api.js +49 -2
- package/dist/lib/server-api.js.map +1 -1
- package/dist/tracking/index.js +212 -185
- package/dist/tracking/index.js.map +1 -1
- package/package.json +1 -1
- package/src/contexts/ThemeContext.tsx +2 -1
- package/src/design_system/chat/useRealtimeReplyOrchestrator.ts +24 -5
- package/src/design_system/components/ChatWidget.tsx +10 -3
- package/src/design_system/elements/map/GoogleMap.tsx +9 -2
- package/src/design_system/sections/blog-post.tsx +13 -3
- package/src/design_system/sections/home-hero-component.tsx +4 -1
- package/src/lib/server-api.ts +6 -2
- package/src/lib/server-log.ts +39 -0
- package/src/next/routes/chat.ts +3 -2
- package/src/next/routes/form.ts +2 -1
- package/src/tracking/MetaPixel.tsx +10 -1
- package/src/tracking/firePixelEvent.ts +7 -2
- package/src/tracking/logging.ts +39 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/tracking/MetaPixel.tsx","../../src/tracking/MetaPixelTracker.tsx","../../src/tracking/firePixelEvent.ts","../../src/tracking/PostHogProvider.tsx","../../src/tracking/logging.ts","../../src/tracking/KeystoneAnalyticsTracker.tsx","../../src/tracking/captureEvent.ts","../../src/tracking/GoogleTagManager.tsx"],"sourcesContent":["'use client';\n\nimport Script from 'next/script';\n\nconst FBEVENTS_URL = 'https://connect.facebook.net/en_US/fbevents.js';\n\nconst PIXEL_SCRIPT = (pixelId: string) => `\n !function(f,b,e,v,n,t,s)\n {if(f.fbq)return;n=f.fbq=function(){n.callMethod?\n n.callMethod.apply(n,arguments):n.queue.push(arguments)};\n if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';\n n.queue=[];t=b.createElement(e);t.async=!0;\n t.src=v;s=b.getElementsByTagName(e)[0];\n s.parentNode.insertBefore(t,s)}(window, document,'script','${FBEVENTS_URL}');\n fbq('init', '${pixelId.replace(/'/g, \"\\\\'\")}');\n fbq('track', 'PageView');\n window.__ks_pixel_ids = window.__ks_pixel_ids || [];\n if (window.__ks_pixel_ids.indexOf('${pixelId.replace(/'/g, \"\\\\'\")}') === -1) {\n window.__ks_pixel_ids.push('${pixelId.replace(/'/g, \"\\\\'\")}');\n }\n`;\n\nexport type MetaPixelProps = {\n /** Meta Pixel ID. When null/undefined, nothing is rendered. */\n pixelId: string | null | undefined;\n};\n\n/**\n * Renders the Meta (Facebook) Pixel base code: loads fbevents.js, initializes\n * the pixel, and tracks PageView. Use in the root layout when ads config\n * provides a meta_pixel_id.\n */\nexport function MetaPixel({ pixelId }: MetaPixelProps) {\n const raw = typeof pixelId === 'string' ? pixelId.trim() : '';\n const id = raw && raw !== 'null' && /^\\d+$/.test(raw) ? raw : '';\n if (!id) {\n return null;\n }\n\n return (\n <>\n <Script\n id=\"meta-pixel\"\n strategy=\"afterInteractive\"\n dangerouslySetInnerHTML={{ __html: PIXEL_SCRIPT(id) }}\n />\n <noscript>\n {/* eslint-disable-next-line @next/next/no-img-element -- 1x1 tracking pixel for no-JS fallback; next/image not applicable in noscript */}\n <img\n height={1}\n width={1}\n style={{ display: 'none' }}\n src={`https://www.facebook.com/tr?id=${id}&ev=PageView&noscript=1`}\n alt=\"\"\n />\n </noscript>\n </>\n );\n}\n","'use client';\n\nimport { useEffect } from 'react';\nimport { usePathname } from 'next/navigation';\nimport { firePixelEvent } from './firePixelEvent';\n\ntype RouteRule = {\n pattern: RegExp;\n getParams: (match: RegExpMatchArray) => { contentName: string; contentCategory: string };\n};\n\nfunction slugToTitle(slug: string): string {\n return slug\n .split('-')\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1))\n .join(' ');\n}\n\n// Checked in order — first match wins. More specific patterns come first.\nconst ROUTE_RULES: RouteRule[] = [\n {\n pattern: /^\\/services\\/(.+)$/,\n getParams: ([, slug]) => ({ contentName: slugToTitle(slug), contentCategory: 'Service' }),\n },\n {\n pattern: /^\\/locations\\/(.+)$/,\n getParams: ([, slug]) => ({ contentName: slugToTitle(slug), contentCategory: 'Location' }),\n },\n {\n pattern: /^\\/services$/,\n getParams: () => ({ contentName: 'Services', contentCategory: 'Services' }),\n },\n {\n pattern: /^\\/locations$/,\n getParams: () => ({ contentName: 'Locations', contentCategory: 'Locations' }),\n },\n {\n pattern: /^\\/portal$/,\n getParams: () => ({ contentName: 'Member Portal', contentCategory: 'Pricing' }),\n },\n {\n pattern: /^\\/service-menu$/,\n getParams: () => ({ contentName: 'Service Menu', contentCategory: 'Pricing' }),\n },\n {\n pattern: /^\\/faq$/,\n getParams: () => ({ contentName: 'FAQ', contentCategory: 'FAQ' }),\n },\n {\n pattern: /^\\/contact$/,\n getParams: () => ({ contentName: 'Contact', contentCategory: 'Contact' }),\n },\n];\n\ntype Props = {\n /** External booking URL. When set, fires InitiateCheckout on any click targeting that URL. */\n bookingUrl?: string | null;\n};\n\n/**\n * Single client-side tracker placed once in KeystoneRootLayout.\n * - Fires ViewContent on every route change for known page patterns.\n * - Fires InitiateCheckout whenever a visitor clicks a link to the external booking URL.\n *\n * Portal booking tab tracking is handled directly inside PortalPage via PortalTabTracker\n * since intent is only established when the Booking tab is explicitly opened.\n */\nexport function MetaPixelTracker({ bookingUrl }: Props) {\n const pathname = usePathname();\n\n useEffect(() => {\n for (const rule of ROUTE_RULES) {\n const match = pathname.match(rule.pattern);\n if (match) {\n const { contentName, contentCategory } = rule.getParams(match);\n firePixelEvent('ViewContent', { contentName, contentCategory });\n break;\n }\n }\n }, [pathname]);\n\n useEffect(() => {\n if (!bookingUrl) return;\n\n const handleClick = (e: MouseEvent) => {\n const anchor = (e.target as Element).closest('a');\n if (anchor?.href?.startsWith(bookingUrl)) {\n firePixelEvent('InitiateCheckout');\n }\n };\n\n document.addEventListener('click', handleClick);\n return () => document.removeEventListener('click', handleClick);\n }, [bookingUrl]);\n\n return null;\n}\n","type FbqFn = (method: string, ...args: unknown[]) => void;\n\nexport type PixelEvent = 'PageView' | 'ViewContent' | 'InitiateCheckout' | 'Lead';\n\nexport interface PixelEventParams {\n contentName?: string;\n contentCategory?: string;\n}\n\n/** Raw (unhashed) user identifiers for advanced matching. */\nexport interface PixelUserData {\n email?: string | null;\n phone?: string | null;\n}\n\n// Hashed user data stored in sessionStorage for the duration of the browsing session.\n// Keyed by a short namespace to avoid collisions.\nconst STORAGE_KEY = 'ks_pud';\n\nasync function sha256Hex(str: string): Promise<string> {\n const buffer = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(str));\n return Array.from(new Uint8Array(buffer))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\nfunction getFbq(): FbqFn | undefined {\n if (typeof window === 'undefined') return undefined;\n return (window as Window & { fbq?: FbqFn }).fbq;\n}\n\n/** Read the pixel IDs registered by MetaPixel via the inline init script. */\nfunction getRegisteredPixelIds(): string[] {\n return (window as Window & { __ks_pixel_ids?: string[] }).__ks_pixel_ids ?? [];\n}\n\n/** Apply stored hashed user data to every configured Meta Pixel. */\nfunction applyStoredUserData(fbq: FbqFn): void {\n try {\n const raw = sessionStorage.getItem(STORAGE_KEY);\n if (!raw) return;\n const hashed = JSON.parse(raw) as Record<string, string>;\n if (Object.keys(hashed).length === 0) return;\n getRegisteredPixelIds().forEach((id) => fbq('init', id, hashed));\n } catch {\n // sessionStorage unavailable or JSON malformed — safe to ignore\n }\n}\n\n/**\n * Hash and store user identifiers so they are automatically included in all\n * subsequent pixel events for this browser session. Call this as soon as\n * identity is known (e.g. contact form submission, portal login step 1).\n */\nexport async function setPixelUserData(userData: PixelUserData): Promise<void> {\n const hashed: Record<string, string> = {};\n\n if (userData.email) {\n hashed.em = await sha256Hex(userData.email.trim().toLowerCase());\n }\n if (userData.phone) {\n const digits = userData.phone.replace(/\\D/g, '');\n if (digits) hashed.ph = await sha256Hex(digits);\n }\n\n if (Object.keys(hashed).length === 0) return;\n\n try {\n sessionStorage.setItem(STORAGE_KEY, JSON.stringify(hashed));\n } catch {\n // sessionStorage unavailable — still apply to fbq for this page load\n }\n\n const fbq = getFbq();\n if (fbq) {\n getRegisteredPixelIds().forEach((id) => fbq('init', id, hashed));\n }\n}\n\n/**\n * Single entry point for all client-side Meta Pixel event fires.\n * Automatically applies any stored user identity before firing so that Meta\n * can match events to known users across the entire session.\n * Silently no-ops if fbq is not loaded (pixel not configured for this site).\n *\n * @param eventId - Optional server-side event ID for browser/server deduplication.\n * Pass the `eventId` returned by the form submission API so Meta can match and\n * deduplicate the browser Lead event against the server-side CAPI Lead event.\n * Format: fbq('track', event, params, { eventID: eventId })\n */\nexport function firePixelEvent(event: PixelEvent, params?: PixelEventParams, eventId?: string): void {\n const fbq = getFbq();\n if (!fbq) {\n console.debug('[MetaPixel] skipped — fbq not loaded', { event });\n return;\n }\n\n // Re-apply stored identity before every event so user data is included\n // even on events that fire after a client-side navigation (PageView, ViewContent, etc.)\n applyStoredUserData(fbq);\n\n const normalized: Record<string, string> = {};\n if (params?.contentName) normalized.content_name = params.contentName;\n if (params?.contentCategory) normalized.content_category = params.contentCategory;\n\n const customData = Object.keys(normalized).length > 0 ? normalized : undefined;\n const eventData = eventId ? { eventID: eventId } : undefined;\n\n console.debug('[MetaPixel]', event, normalized, eventId ? { eventID: eventId } : '');\n fbq('track', event, customData, eventData);\n}\n","'use client';\n\nimport posthog from 'posthog-js';\nimport { PostHogProvider as PHProvider } from 'posthog-js/react';\nimport { useEffect, Suspense } from 'react';\nimport { usePathname, useSearchParams } from 'next/navigation';\nimport { error as logError, initializePostHogLogging } from './logging';\n\nconst DEFAULT_HOST = 'https://us.i.posthog.com';\n\nexport type PostHogProviderProps = {\n apiKey: string;\n apiHost?: string;\n /** Keystone account ID — attached to every event as a super property. */\n accountId?: number;\n /** Keystone account name (company_name) — attached to every event as a super property. */\n accountName?: string;\n /**\n * Environment identifier from KEYSTONE_ENV on the Rails server (e.g. \"production\", \"staging\",\n * \"development\"). Registered as a super property so events can be filtered by environment\n * in PostHog and the sync job only imports data for the matching environment.\n */\n environment?: string;\n children: React.ReactNode;\n};\n\n// ---------------------------------------------------------------------------\n// Page name resolution\n// ---------------------------------------------------------------------------\n\ntype PageInfo = { page_name: string; page_slug?: string };\n\nfunction resolvePageInfo(pathname: string): PageInfo {\n if (pathname === '/') return { page_name: 'home' };\n\n const patterns: Array<[RegExp, (m: RegExpMatchArray) => PageInfo]> = [\n [/^\\/services\\/(.+)$/, ([, slug]) => ({ page_name: 'service_detail', page_slug: slug })],\n [/^\\/locations\\/(.+)$/, ([, slug]) => ({ page_name: 'location_detail', page_slug: slug })],\n [/^\\/blog\\/(.+)$/, ([, slug]) => ({ page_name: 'blog_post', page_slug: slug })],\n [/^\\/jobs\\/(.+)$/, ([, slug]) => ({ page_name: 'job_detail', page_slug: slug })],\n [/^\\/packages\\/(.+)$/, ([, slug]) => ({ page_name: 'package_detail', page_slug: slug })],\n ];\n\n for (const [pattern, resolve] of patterns) {\n const match = pathname.match(pattern);\n if (match) return resolve(match);\n }\n\n const staticNames: Record<string, string> = {\n '/services': 'services',\n '/locations': 'locations',\n '/contact': 'contact',\n '/about': 'about',\n '/blog': 'blog',\n '/portal': 'portal',\n '/gallery': 'gallery',\n '/team': 'team',\n '/faq': 'faq',\n '/reviews': 'reviews',\n '/jobs': 'jobs',\n '/packages': 'packages',\n '/service-menu': 'service_menu',\n '/privacy-policy': 'privacy_policy',\n '/terms': 'terms_of_service',\n };\n\n return { page_name: staticNames[pathname] ?? 'unknown' };\n}\n\n// ---------------------------------------------------------------------------\n// Pageview tracker — must be wrapped in <Suspense> (useSearchParams)\n// ---------------------------------------------------------------------------\n\nfunction PostHogPageviewTracker() {\n const pathname = usePathname();\n const searchParams = useSearchParams();\n\n useEffect(() => {\n if (!pathname) return;\n\n const search = searchParams?.toString();\n const url = window.location.origin + pathname + (search ? `?${search}` : '');\n const { page_name, page_slug } = resolvePageInfo(pathname);\n\n posthog.capture('$pageview', {\n $current_url: url,\n page_name,\n page_path: pathname,\n ...(page_slug && { page_slug }),\n });\n }, [pathname, searchParams]);\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Provider\n// ---------------------------------------------------------------------------\n\n/**\n * Initialises PostHog, registers account-level super properties, and fires\n * an enriched `$pageview` on every App Router navigation.\n *\n * Super properties attached to every event automatically:\n * - account_id (Keystone account ID)\n * - account_name (company_name)\n * - site_domain (window.location.hostname)\n * - environment (KEYSTONE_ENV — e.g. \"production\", \"staging\", \"local_rahuljaswa\")\n *\n * Mount once in the root layout body. One project key covers all customer\n * sites — filter by account_name or site_domain in the PostHog dashboard.\n */\nexport function PostHogProvider({ apiKey, apiHost, accountId, accountName, environment, children }: PostHogProviderProps) {\n useEffect(() => {\n posthog.init(apiKey, {\n api_host: apiHost ?? DEFAULT_HOST,\n person_profiles: 'identified_only',\n capture_pageview: false,\n });\n\n posthog.register({\n ...(accountId !== undefined && { account_id: accountId }),\n ...(accountName && { account_name: accountName }),\n ...(environment && { environment }),\n site_domain: window.location.hostname,\n });\n\n initializePostHogLogging({\n apiKey,\n apiHost: apiHost ?? DEFAULT_HOST,\n serviceName: 'keystone-customer-site',\n environment,\n accountId,\n accountName,\n });\n }, [apiKey, apiHost, accountId, accountName, environment]);\n\n useEffect(() => {\n const onWindowError = (event: ErrorEvent) => {\n logError(\n 'global-client',\n 'WINDOW_ERROR',\n {\n message: event.message || 'unknown_error',\n filename: event.filename || 'unknown_file',\n lineno: event.lineno || 0,\n colno: event.colno || 0,\n stack: event.error instanceof Error ? event.error.stack || '' : '',\n },\n { shipToPostHog: true },\n );\n };\n\n const onUnhandledRejection = (event: PromiseRejectionEvent) => {\n const reason = event.reason;\n const payload =\n reason instanceof Error\n ? { message: reason.message, stack: reason.stack || '' }\n : { message: String(reason), stack: '' };\n\n logError('global-client', 'UNHANDLED_REJECTION', payload, { shipToPostHog: true });\n };\n\n window.addEventListener('error', onWindowError);\n window.addEventListener('unhandledrejection', onUnhandledRejection);\n return () => {\n window.removeEventListener('error', onWindowError);\n window.removeEventListener('unhandledrejection', onUnhandledRejection);\n };\n }, []);\n\n return (\n <PHProvider client={posthog}>\n <Suspense fallback={null}>\n <PostHogPageviewTracker />\n </Suspense>\n {children}\n </PHProvider>\n );\n}\n","'use client';\n\nimport { logs } from '@opentelemetry/api-logs';\nimport { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';\nimport { resourceFromAttributes } from '@opentelemetry/resources';\nimport { BatchLogRecordProcessor, LoggerProvider } from '@opentelemetry/sdk-logs';\n\nconst BUILD_CONSOLE_DISABLED = process.env.NEXT_PUBLIC_LOGGING_DISABLED === '1';\nconst BUILD_POSTHOG_DISABLED = process.env.NEXT_PUBLIC_POSTHOG_LOGGING_DISABLED === '1';\ninterface LoggingWindow extends Window {\n __loggingDisabled?: boolean;\n __posthogLoggingDisabled?: boolean;\n}\n\nexport type LogLevel = 'info' | 'warn' | 'error';\n\nexport type LogOptions = {\n /** Send this log payload to PostHog Logs product. */\n shipToPostHog?: boolean;\n};\n\ntype OTelInitOptions = {\n apiKey: string;\n apiHost?: string;\n serviceName?: string;\n environment?: string;\n accountId?: number;\n accountName?: string;\n};\nlet otelProvider: LoggerProvider | null = null;\nlet otelLogger: ReturnType<typeof logs.getLogger> | null = null;\nlet otelInitFingerprint = '';\n\n/**\n * Channel → accent colour. Unregistered channels fall through to gray.\n */\nexport const CHANNEL_COLORS: Record<string, string> = {\n // Shared diagnostics\n 'global-client': '#d2a8ff',\n\n // Section pins (desktop)\n 'every-channel-pin': '#9febd7',\n 'hero-pin': '#6ecc8b',\n 'pricing-pin': '#399587',\n 'product-screens-pin': '#4fafa0',\n 'social-proof-pin': '#ffbb8a',\n 'value-props-pin': '#e0a733',\n 'work-pin': '#f57e56',\n\n // Section pins (mobile)\n 'mobile-every-channel-pin': '#7ed9c6',\n 'mobile-hero-pin': '#f0eee6',\n 'mobile-pricing-pin': '#80d4ff',\n 'mobile-product-screens-pin': '#3a9085',\n 'mobile-social-proof-pin': '#ffd580',\n 'mobile-value-props-pin': '#4fafa0',\n};\n\nfunction flattenAttributes(input: Record<string, unknown>, prefix = ''): Record<string, string | number | boolean> {\n const output: Record<string, string | number | boolean> = {};\n\n for (const [key, value] of Object.entries(input)) {\n const nextKey = prefix ? `${prefix}.${key}` : key;\n if (value === null || value === undefined) continue;\n\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n output[nextKey] = value;\n continue;\n }\n\n if (Array.isArray(value)) {\n output[nextKey] = JSON.stringify(value);\n continue;\n }\n\n if (typeof value === 'object') {\n Object.assign(output, flattenAttributes(value as Record<string, unknown>, nextKey));\n continue;\n }\n\n output[nextKey] = String(value);\n }\n\n return output;\n}\n\nfunction stripTrailingSlash(host: string): string {\n return host.replace(/\\/+$/, '');\n}\n\nexport function initializePostHogLogging(options: OTelInitOptions): void {\n if (typeof window === 'undefined') return;\n if (!options.apiKey?.trim()) return;\n\n const apiHost = stripTrailingSlash(options.apiHost?.trim() || 'https://us.i.posthog.com');\n const serviceName = options.serviceName?.trim() || 'keystone-customer-site';\n const environment = options.environment?.trim() || 'production';\n const fingerprint = `${apiHost}|${options.apiKey}|${serviceName}|${environment}|${options.accountId ?? ''}|${options.accountName ?? ''}`;\n if (otelLogger && otelInitFingerprint === fingerprint) return;\n\n if (otelProvider) {\n // Cleanly replace logger provider when config changes.\n void otelProvider.shutdown().catch(() => {});\n otelProvider = null;\n otelLogger = null;\n }\n\n const baseAttributes: Record<string, string | number | boolean> = {\n 'service.name': serviceName,\n environment,\n site_domain: window.location.hostname,\n };\n if (options.accountId !== undefined) baseAttributes.account_id = options.accountId;\n if (options.accountName) baseAttributes.account_name = options.accountName;\n\n const exporter = new OTLPLogExporter({\n url: `${apiHost}/i/v1/logs?token=${encodeURIComponent(options.apiKey)}`,\n headers: {\n // Keep request \"simple\" to avoid CORS preflight in browser.\n 'Content-Type': 'text/plain',\n },\n });\n\n otelProvider = new LoggerProvider({\n resource: resourceFromAttributes(baseAttributes),\n processors: [new BatchLogRecordProcessor(exporter)],\n });\n\n logs.setGlobalLoggerProvider(otelProvider);\n otelLogger = logs.getLogger(serviceName);\n otelInitFingerprint = fingerprint;\n}\n\nfunction isConsoleEnabled(): boolean {\n if (BUILD_CONSOLE_DISABLED) return false;\n if (typeof window === 'undefined') return true;\n return (window as LoggingWindow).__loggingDisabled !== true;\n}\n\nfunction isPostHogShippingEnabled(): boolean {\n if (BUILD_POSTHOG_DISABLED) return false;\n if (typeof window === 'undefined') return false;\n return (window as LoggingWindow).__posthogLoggingDisabled !== true;\n}\n\nfunction formatDetail(detail: Record<string, unknown>): string {\n return Object.entries(detail)\n .map(([k, v]) => `${k}=${typeof v === 'number' ? v.toFixed(4) : String(v)}`)\n .join(' ');\n}\n\nfunction emitConsole(\n level: LogLevel,\n channel: string,\n event: string,\n detail: Record<string, unknown>,\n): void {\n const color = CHANNEL_COLORS[channel] ?? '#aaa';\n const detailStr = formatDetail(detail);\n const message = `%c[${channel}] %c${event}%c ${detailStr}`;\n const channelStyle = `color:${color}; font-weight:bold`;\n const eventStyle = level === 'error'\n ? 'color:#e94e4e; font-weight:bold'\n : level === 'warn'\n ? 'color:#f5a623; font-weight:bold'\n : 'color:#fff; font-weight:bold';\n const detailStyle = 'color:#999; font-weight:normal';\n\n if (level === 'error') {\n console.error(message, channelStyle, eventStyle, detailStyle);\n return;\n }\n if (level === 'warn') {\n console.warn(message, channelStyle, eventStyle, detailStyle);\n return;\n }\n console.log(message, channelStyle, eventStyle, detailStyle);\n}\n\nfunction emitToPostHog(level: LogLevel, channel: string, event: string, detail: Record<string, unknown>): void {\n if (!otelLogger) return;\n\n const severityText = level === 'warn' ? 'WARNING' : level === 'error' ? 'ERROR' : 'INFO';\n const attributes = flattenAttributes({ channel, ...detail });\n\n otelLogger.emit({\n severityText,\n body: event,\n attributes,\n });\n}\n\nfunction emit(level: LogLevel, channel: string, event: string, detail: Record<string, unknown>, options?: LogOptions): void {\n const shouldShip = (options?.shipToPostHog ?? level !== 'info') && isPostHogShippingEnabled();\n // Keep warn/error visible even when regular logs are muted.\n if (level !== 'info' || isConsoleEnabled()) {\n emitConsole(level, channel, event, detail);\n }\n if (shouldShip) {\n emitToPostHog(level, channel, event, detail);\n }\n}\n\nexport function log(channel: string, event: string, detail: Record<string, unknown> = {}, options?: LogOptions): void {\n emit('info', channel, event, detail, options);\n}\n\nexport function warn(channel: string, event: string, detail: Record<string, unknown> = {}, options?: LogOptions): void {\n emit('warn', channel, event, detail, options);\n}\n\nexport function error(channel: string, event: string, detail: Record<string, unknown> = {}, options?: LogOptions): void {\n emit('error', channel, event, detail, options);\n}\n","'use client';\n\nimport { useEffect } from 'react';\nimport { usePathname } from 'next/navigation';\nimport { captureEvent } from './captureEvent';\n\ntype Props = {\n /** External booking / portal URL. When set, fires booking_cta_clicked on any click to that URL. */\n bookingUrl?: string | null;\n};\n\n/**\n * Page-level PostHog event tracker. Mount once inside PostHogProvider in\n * KeystoneRootLayout alongside MetaPixelTracker.\n *\n * Responsibilities:\n * - booking_cta_clicked: fires when any link to the booking URL is clicked,\n * capturing the page the visitor was on when they clicked.\n */\nexport function KeystoneAnalyticsTracker({ bookingUrl }: Props) {\n const pathname = usePathname();\n\n useEffect(() => {\n if (!bookingUrl) return;\n\n const handleClick = (e: MouseEvent) => {\n const anchor = (e.target as Element).closest('a');\n if (anchor?.href?.startsWith(bookingUrl)) {\n captureEvent('booking_cta_clicked', {\n source_path: pathname,\n booking_url: bookingUrl,\n });\n }\n };\n\n document.addEventListener('click', handleClick);\n return () => document.removeEventListener('click', handleClick);\n }, [bookingUrl, pathname]);\n\n return null;\n}\n","/**\n * PostHog event capture — Keystone customer sites.\n *\n * ## Naming convention\n * All events use snake_case `object_action` format.\n * Properties use snake_case as well.\n *\n * ## Event taxonomy\n * Add new events here: define the name in `KsEventName` and its required\n * properties in `KsEventProperties`. Every callsite is then type-checked.\n *\n * ## Usage\n *\n * import { captureEvent } from 'keystone-design-bootstrap/tracking';\n *\n * captureEvent('form_submitted', { form_type: 'lead' });\n * captureEvent('booking_cta_clicked', { source_path: '/services/massage', booking_url: url });\n *\n * All calls are safe no-ops when PostHog has not been initialised (e.g. no\n * POSTHOG_API_KEY configured on the server).\n *\n * ## Super properties\n * account_id, account_name, and site_domain are registered as super properties\n * by PostHogProvider and are automatically attached to every event — you do not\n * need to include them in individual captureEvent calls.\n */\n\nimport posthog from 'posthog-js';\n\n// ---------------------------------------------------------------------------\n// Event taxonomy\n// ---------------------------------------------------------------------------\n\nexport type KsEventName =\n // Booking / conversion\n | 'booking_cta_clicked'\n // Forms\n | 'form_submitted'\n | 'form_failed'\n // Chat widget\n | 'chat_opened'\n | 'chat_message_sent'\n | 'chat_message_failed'\n // Member portal — tab navigation\n | 'portal_tab_viewed'\n // Member portal — authentication flow\n | 'portal_login_started'\n | 'portal_login_step_viewed'\n | 'portal_login_step_submitted'\n | 'portal_login_step_advanced'\n | 'portal_login_back_clicked'\n | 'portal_login_identified'\n | 'portal_login_completed'\n | 'portal_login_failed';\n\nexport type KsEventProperties = {\n /** Fired when a visitor clicks any CTA that links to the external booking URL. */\n booking_cta_clicked: {\n source_path: string;\n booking_url: string;\n };\n\n /** Fired when a Keystone form is successfully submitted. */\n form_submitted: {\n /** One of: lead | job_application | marketing_list_signup */\n form_type: string;\n /** Server-generated event ID for CAPI deduplication (when present). */\n event_id?: string;\n };\n\n /** Fired when a form submission fails (validation error or network error). */\n form_failed: {\n form_type: string;\n error: string;\n };\n\n /** Fired when the chat widget is first opened by the visitor. */\n chat_opened: Record<string, never>;\n\n /** Fired when a chat message is successfully sent. */\n chat_message_sent: {\n /** Whether the visitor is authenticated (contactId present). */\n is_authenticated: boolean;\n };\n\n /** Fired when a chat message fails to send. */\n chat_message_failed: {\n error: string;\n };\n\n /** Fired when a member portal tab is opened. */\n portal_tab_viewed: {\n tab: string;\n };\n\n /** Fired when the portal login modal is opened / login flow starts. */\n portal_login_started: Record<string, never>;\n\n /** Fired whenever a login step becomes visible to the user. */\n portal_login_step_viewed: {\n step: 'identifier' | 'signin' | 'signup';\n };\n\n /** Fired when the user submits a specific login step. */\n portal_login_step_submitted: {\n step: 'identifier' | 'signin' | 'signup';\n };\n\n /** Fired when the flow transitions from one step to another state. */\n portal_login_step_advanced: {\n from_step: 'identifier' | 'signin' | 'signup';\n to_step: 'signin' | 'signup' | 'authenticated';\n reason: string;\n };\n\n /** Fired when the user explicitly navigates back to the identifier step. */\n portal_login_back_clicked: {\n from_step: 'signin' | 'signup';\n to_step: 'identifier';\n };\n\n /**\n * Fired after the identifier step resolves — we know whether the user\n * already has an account.\n */\n portal_login_identified: {\n method: 'email' | 'phone' | 'email_and_phone';\n user_exists: boolean;\n };\n\n /** Fired after the user successfully signs in or creates an account. */\n portal_login_completed: {\n flow: 'signin' | 'signup';\n };\n\n /** Fired when any step of the login flow returns an error. */\n portal_login_failed: {\n step: 'identifier' | 'signin' | 'signup';\n reason: string;\n };\n};\n\n// ---------------------------------------------------------------------------\n// Capture helper\n// ---------------------------------------------------------------------------\n\n/**\n * Captures a typed Keystone analytics event via PostHog.\n * Safe no-op when PostHog has not been initialised.\n */\nexport function captureEvent<E extends KsEventName>(\n event: E,\n ...args: KsEventProperties[E] extends Record<string, never>\n ? []\n : [properties: KsEventProperties[E]]\n): void {\n posthog.capture(event, args[0] as Record<string, unknown>);\n}\n\n/**\n * Captures an app-specific (\"custom\") analytics event via PostHog.\n *\n * Unlike `captureEvent` — which is restricted to the shared, type-checked\n * Keystone taxonomy (`KsEventName`) — this is a thin, open-ended escape hatch\n * for product-specific funnels that don't belong in the shared taxonomy\n * (e.g. the Grader's `grader_scan_started`, or any one-off consumer app event).\n * It keeps those events out of the shared union while still flowing through the\n * same initialised PostHog instance, so account super properties and config are\n * applied identically.\n *\n * Conventions (not enforced, but please follow):\n * - snake_case `object_action` names, prefixed with your app/feature so events\n * are easy to group and never collide with the shared taxonomy\n * (e.g. `grader_scan_started`; `portal_*`, `form_*`, `chat_*`, `booking_*`\n * are reserved by `captureEvent`).\n * - snake_case property keys.\n *\n * `account_id` / `account_name` / `site_domain` super properties are attached\n * automatically by PostHogProvider. Safe no-op when PostHog isn't initialised.\n *\n * For events that are reused across multiple sites, promote them into the typed\n * `KsEventName` taxonomy above and use `captureEvent` instead.\n */\nexport function captureCustomEvent(\n event: string,\n properties?: Record<string, unknown>,\n): void {\n posthog.capture(event, properties);\n}\n","const GTM_SCRIPT = (containerId: string) => `\n (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':\n new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],\n j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=\n 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);\n })(window,document,'script','dataLayer','${containerId.replace(/'/g, \"\\\\'\")}');\n`;\n\nexport type GoogleTagManagerProps = {\n /** GTM container public ID (e.g. GTM-ABC123). */\n containerId: string | null | undefined;\n};\n\n/**\n * Renders Google Tag Manager script + noscript fallback.\n * Mount once at root layout level when a container is provisioned.\n */\nexport function GoogleTagManager({ containerId }: GoogleTagManagerProps) {\n const raw = typeof containerId === 'string' ? containerId.trim() : '';\n const id = raw && raw !== 'null' && /^GTM-[A-Z0-9]+$/i.test(raw) ? raw : '';\n\n if (!id) {\n return null;\n }\n\n return (\n <>\n <script id=\"google-tag-manager\" dangerouslySetInnerHTML={{ __html: GTM_SCRIPT(id) }} />\n <noscript>\n <iframe\n src={`https://www.googletagmanager.com/ns.html?id=${id}`}\n height=\"0\"\n width=\"0\"\n style={{ display: 'none', visibility: 'hidden' }}\n title=\"google-tag-manager\"\n />\n </noscript>\n </>\n );\n}\n"],"mappings":"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","names":["useEffect","usePathname","usePathname","useEffect","useEffect","usePathname","posthog","usePathname","useEffect"]}
|
|
1
|
+
{"version":3,"sources":["../../src/tracking/MetaPixel.tsx","../../src/tracking/logging.ts","../../src/tracking/firePixelEvent.ts","../../src/tracking/MetaPixelTracker.tsx","../../src/tracking/PostHogProvider.tsx","../../src/tracking/KeystoneAnalyticsTracker.tsx","../../src/tracking/captureEvent.ts","../../src/tracking/GoogleTagManager.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect } from 'react';\nimport Script from 'next/script';\nimport { firePixelEvent } from './firePixelEvent';\nimport { log } from './logging';\n\nconst FBEVENTS_URL = 'https://connect.facebook.net/en_US/fbevents.js';\n\nconst PIXEL_SCRIPT = (pixelId: string) => `\n !function(f,b,e,v,n,t,s)\n {if(f.fbq)return;n=f.fbq=function(){n.callMethod?\n n.callMethod.apply(n,arguments):n.queue.push(arguments)};\n if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';\n n.queue=[];t=b.createElement(e);t.async=!0;\n t.src=v;s=b.getElementsByTagName(e)[0];\n s.parentNode.insertBefore(t,s)}(window, document,'script','${FBEVENTS_URL}');\n fbq('init', '${pixelId.replace(/'/g, \"\\\\'\")}');\n window.__ks_pixel_ids = window.__ks_pixel_ids || [];\n if (window.__ks_pixel_ids.indexOf('${pixelId.replace(/'/g, \"\\\\'\")}') === -1) {\n window.__ks_pixel_ids.push('${pixelId.replace(/'/g, \"\\\\'\")}');\n }\n`;\n\nexport type MetaPixelProps = {\n /** Meta Pixel ID. When null/undefined, nothing is rendered. */\n pixelId: string | null | undefined;\n};\n\n/**\n * Renders the Meta (Facebook) Pixel base code: loads fbevents.js, initializes\n * the pixel, and tracks PageView. Use in the root layout when ads config\n * provides a meta_pixel_id.\n */\nexport function MetaPixel({ pixelId }: MetaPixelProps) {\n const raw = typeof pixelId === 'string' ? pixelId.trim() : '';\n const id = raw && raw !== 'null' && /^\\d+$/.test(raw) ? raw : '';\n\n useEffect(() => {\n if (!id) return;\n log('meta-pixel', 'PIXEL_INIT', { pixelId: id }, { shipToPostHog: true });\n firePixelEvent('PageView');\n }, [id]);\n\n if (!id) {\n return null;\n }\n\n return (\n <>\n <Script\n id=\"meta-pixel\"\n strategy=\"afterInteractive\"\n dangerouslySetInnerHTML={{ __html: PIXEL_SCRIPT(id) }}\n />\n <noscript>\n {/* eslint-disable-next-line @next/next/no-img-element -- 1x1 tracking pixel for no-JS fallback; next/image not applicable in noscript */}\n <img\n height={1}\n width={1}\n style={{ display: 'none' }}\n src={`https://www.facebook.com/tr?id=${id}&ev=PageView&noscript=1`}\n alt=\"\"\n />\n </noscript>\n </>\n );\n}\n","'use client';\n\nimport { logs } from '@opentelemetry/api-logs';\nimport { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';\nimport { resourceFromAttributes } from '@opentelemetry/resources';\nimport { BatchLogRecordProcessor, LoggerProvider } from '@opentelemetry/sdk-logs';\n\nconst BUILD_CONSOLE_DISABLED = process.env.NEXT_PUBLIC_LOGGING_DISABLED === '1';\nconst BUILD_POSTHOG_DISABLED = process.env.NEXT_PUBLIC_POSTHOG_LOGGING_DISABLED === '1';\ninterface LoggingWindow extends Window {\n __loggingDisabled?: boolean;\n __posthogLoggingDisabled?: boolean;\n}\n\nexport type LogLevel = 'info' | 'warn' | 'error';\n\nexport type LogOptions = {\n /** Send this log payload to PostHog Logs product. */\n shipToPostHog?: boolean;\n};\n\ntype OTelInitOptions = {\n apiKey: string;\n apiHost?: string;\n serviceName?: string;\n environment?: string;\n accountId?: number;\n accountName?: string;\n};\n\ntype PendingShipLog = {\n level: LogLevel;\n channel: string;\n event: string;\n detail: Record<string, unknown>;\n};\n\nconst MAX_PENDING_POSTHOG_LOGS = 500;\nlet otelProvider: LoggerProvider | null = null;\nlet otelLogger: ReturnType<typeof logs.getLogger> | null = null;\nlet otelInitFingerprint = '';\nconst pendingPostHogLogs: PendingShipLog[] = [];\nconst browserConsole = globalThis?.['console'];\n\n/**\n * Channel → accent colour. Unregistered channels fall through to gray.\n */\nexport const CHANNEL_COLORS: Record<string, string> = {\n // Shared diagnostics\n 'global-client': '#d2a8ff',\n\n // Section pins (desktop)\n 'every-channel-pin': '#9febd7',\n 'hero-pin': '#6ecc8b',\n 'pricing-pin': '#399587',\n 'product-screens-pin': '#4fafa0',\n 'social-proof-pin': '#ffbb8a',\n 'value-props-pin': '#e0a733',\n 'work-pin': '#f57e56',\n\n // Section pins (mobile)\n 'mobile-every-channel-pin': '#7ed9c6',\n 'mobile-hero-pin': '#f0eee6',\n 'mobile-pricing-pin': '#80d4ff',\n 'mobile-product-screens-pin': '#3a9085',\n 'mobile-social-proof-pin': '#ffd580',\n 'mobile-value-props-pin': '#4fafa0',\n};\n\nfunction flattenAttributes(input: Record<string, unknown>, prefix = ''): Record<string, string | number | boolean> {\n const output: Record<string, string | number | boolean> = {};\n\n for (const [key, value] of Object.entries(input)) {\n const nextKey = prefix ? `${prefix}.${key}` : key;\n if (value === null || value === undefined) continue;\n\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n output[nextKey] = value;\n continue;\n }\n\n if (Array.isArray(value)) {\n output[nextKey] = JSON.stringify(value);\n continue;\n }\n\n if (typeof value === 'object') {\n Object.assign(output, flattenAttributes(value as Record<string, unknown>, nextKey));\n continue;\n }\n\n output[nextKey] = String(value);\n }\n\n return output;\n}\n\nfunction stripTrailingSlash(host: string): string {\n return host.replace(/\\/+$/, '');\n}\n\nfunction enqueuePendingPostHogLog(entry: PendingShipLog): void {\n if (pendingPostHogLogs.length >= MAX_PENDING_POSTHOG_LOGS) {\n pendingPostHogLogs.shift();\n }\n pendingPostHogLogs.push(entry);\n}\n\nfunction flushPendingPostHogLogs(): void {\n if (!otelLogger || pendingPostHogLogs.length === 0) return;\n\n while (pendingPostHogLogs.length > 0) {\n const entry = pendingPostHogLogs.shift();\n if (!entry) break;\n emitToPostHog(entry.level, entry.channel, entry.event, entry.detail);\n }\n}\n\nexport function initializePostHogLogging(options: OTelInitOptions): void {\n if (typeof window === 'undefined') return;\n if (!options.apiKey?.trim()) return;\n\n const apiHost = stripTrailingSlash(options.apiHost?.trim() || 'https://us.i.posthog.com');\n const serviceName = options.serviceName?.trim() || 'keystone-customer-site';\n const environment = options.environment?.trim() || 'production';\n const fingerprint = `${apiHost}|${options.apiKey}|${serviceName}|${environment}|${options.accountId ?? ''}|${options.accountName ?? ''}`;\n if (otelLogger && otelInitFingerprint === fingerprint) return;\n\n if (otelProvider) {\n // Cleanly replace logger provider when config changes.\n void otelProvider.shutdown().catch(() => {});\n otelProvider = null;\n otelLogger = null;\n }\n\n const baseAttributes: Record<string, string | number | boolean> = {\n 'service.name': serviceName,\n environment,\n site_domain: window.location.hostname,\n };\n if (options.accountId !== undefined) baseAttributes.account_id = options.accountId;\n if (options.accountName) baseAttributes.account_name = options.accountName;\n\n const exporter = new OTLPLogExporter({\n url: `${apiHost}/i/v1/logs?token=${encodeURIComponent(options.apiKey)}`,\n headers: {\n // Keep request \"simple\" to avoid CORS preflight in browser.\n 'Content-Type': 'text/plain',\n },\n });\n\n otelProvider = new LoggerProvider({\n resource: resourceFromAttributes(baseAttributes),\n processors: [new BatchLogRecordProcessor(exporter)],\n });\n\n logs.setGlobalLoggerProvider(otelProvider);\n otelLogger = logs.getLogger(serviceName);\n otelInitFingerprint = fingerprint;\n flushPendingPostHogLogs();\n}\n\nfunction isConsoleEnabled(): boolean {\n if (BUILD_CONSOLE_DISABLED) return false;\n if (typeof window === 'undefined') return true;\n return (window as LoggingWindow).__loggingDisabled !== true;\n}\n\nfunction isPostHogShippingEnabled(): boolean {\n if (BUILD_POSTHOG_DISABLED) return false;\n if (typeof window === 'undefined') return false;\n return (window as LoggingWindow).__posthogLoggingDisabled !== true;\n}\n\nfunction formatDetail(detail: Record<string, unknown>): string {\n return Object.entries(detail)\n .map(([k, v]) => `${k}=${typeof v === 'number' ? v.toFixed(4) : String(v)}`)\n .join(' ');\n}\n\nfunction emitConsole(\n level: LogLevel,\n channel: string,\n event: string,\n detail: Record<string, unknown>,\n): void {\n const color = CHANNEL_COLORS[channel] ?? '#aaa';\n const detailStr = formatDetail(detail);\n const message = `%c[${channel}] %c${event}%c ${detailStr}`;\n const channelStyle = `color:${color}; font-weight:bold`;\n const eventStyle = level === 'error'\n ? 'color:#e94e4e; font-weight:bold'\n : level === 'warn'\n ? 'color:#f5a623; font-weight:bold'\n : 'color:#fff; font-weight:bold';\n const detailStyle = 'color:#999; font-weight:normal';\n\n if (level === 'error') {\n browserConsole?.error?.(message, channelStyle, eventStyle, detailStyle);\n return;\n }\n if (level === 'warn') {\n browserConsole?.warn?.(message, channelStyle, eventStyle, detailStyle);\n return;\n }\n browserConsole?.log?.(message, channelStyle, eventStyle, detailStyle);\n}\n\nfunction emitToPostHog(level: LogLevel, channel: string, event: string, detail: Record<string, unknown>): boolean {\n if (!otelLogger) return false;\n\n const severityText = level === 'warn' ? 'WARNING' : level === 'error' ? 'ERROR' : 'INFO';\n const attributes = flattenAttributes({ channel, ...detail });\n\n otelLogger.emit({\n severityText,\n body: event,\n attributes,\n });\n return true;\n}\n\nfunction emit(level: LogLevel, channel: string, event: string, detail: Record<string, unknown>, options?: LogOptions): void {\n const shouldShip = (options?.shipToPostHog ?? level !== 'info') && isPostHogShippingEnabled();\n // Keep warn/error visible even when regular logs are muted.\n if (level !== 'info' || isConsoleEnabled()) {\n emitConsole(level, channel, event, detail);\n }\n if (shouldShip) {\n const shipped = emitToPostHog(level, channel, event, detail);\n if (!shipped) {\n enqueuePendingPostHogLog({ level, channel, event, detail });\n }\n }\n}\n\nexport function log(channel: string, event: string, detail: Record<string, unknown> = {}, options?: LogOptions): void {\n emit('info', channel, event, detail, options);\n}\n\nexport function warn(channel: string, event: string, detail: Record<string, unknown> = {}, options?: LogOptions): void {\n emit('warn', channel, event, detail, options);\n}\n\nexport function error(channel: string, event: string, detail: Record<string, unknown> = {}, options?: LogOptions): void {\n emit('error', channel, event, detail, options);\n}\n","type FbqFn = (method: string, ...args: unknown[]) => void;\nimport { log, warn } from './logging';\n\nexport type PixelEvent = 'PageView' | 'ViewContent' | 'InitiateCheckout' | 'Lead';\n\nexport interface PixelEventParams {\n contentName?: string;\n contentCategory?: string;\n}\n\n/** Raw (unhashed) user identifiers for advanced matching. */\nexport interface PixelUserData {\n email?: string | null;\n phone?: string | null;\n}\n\n// Hashed user data stored in sessionStorage for the duration of the browsing session.\n// Keyed by a short namespace to avoid collisions.\nconst STORAGE_KEY = 'ks_pud';\n\nasync function sha256Hex(str: string): Promise<string> {\n const buffer = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(str));\n return Array.from(new Uint8Array(buffer))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\nfunction getFbq(): FbqFn | undefined {\n if (typeof window === 'undefined') return undefined;\n return (window as Window & { fbq?: FbqFn }).fbq;\n}\n\n/** Read the pixel IDs registered by MetaPixel via the inline init script. */\nfunction getRegisteredPixelIds(): string[] {\n return (window as Window & { __ks_pixel_ids?: string[] }).__ks_pixel_ids ?? [];\n}\n\n/** Apply stored hashed user data to every configured Meta Pixel. */\nfunction applyStoredUserData(fbq: FbqFn): void {\n try {\n const raw = sessionStorage.getItem(STORAGE_KEY);\n if (!raw) return;\n const hashed = JSON.parse(raw) as Record<string, string>;\n if (Object.keys(hashed).length === 0) return;\n getRegisteredPixelIds().forEach((id) => fbq('init', id, hashed));\n } catch {\n // sessionStorage unavailable or JSON malformed — safe to ignore\n }\n}\n\n/**\n * Hash and store user identifiers so they are automatically included in all\n * subsequent pixel events for this browser session. Call this as soon as\n * identity is known (e.g. contact form submission, portal login step 1).\n */\nexport async function setPixelUserData(userData: PixelUserData): Promise<void> {\n const hashed: Record<string, string> = {};\n\n if (userData.email) {\n hashed.em = await sha256Hex(userData.email.trim().toLowerCase());\n }\n if (userData.phone) {\n const digits = userData.phone.replace(/\\D/g, '');\n if (digits) hashed.ph = await sha256Hex(digits);\n }\n\n if (Object.keys(hashed).length === 0) return;\n\n try {\n sessionStorage.setItem(STORAGE_KEY, JSON.stringify(hashed));\n } catch {\n // sessionStorage unavailable — still apply to fbq for this page load\n }\n\n const fbq = getFbq();\n if (fbq) {\n getRegisteredPixelIds().forEach((id) => fbq('init', id, hashed));\n }\n}\n\n/**\n * Single entry point for all client-side Meta Pixel event fires.\n * Automatically applies any stored user identity before firing so that Meta\n * can match events to known users across the entire session.\n * Silently no-ops if fbq is not loaded (pixel not configured for this site).\n *\n * @param eventId - Optional server-side event ID for browser/server deduplication.\n * Pass the `eventId` returned by the form submission API so Meta can match and\n * deduplicate the browser Lead event against the server-side CAPI Lead event.\n * Format: fbq('track', event, params, { eventID: eventId })\n */\nexport function firePixelEvent(event: PixelEvent, params?: PixelEventParams, eventId?: string): void {\n const fbq = getFbq();\n if (!fbq) {\n warn('meta-pixel', 'PIXEL_EVENT_SKIPPED_FBQ_NOT_LOADED', { event });\n return;\n }\n\n // Re-apply stored identity before every event so user data is included\n // even on events that fire after a client-side navigation (PageView, ViewContent, etc.)\n applyStoredUserData(fbq);\n\n const normalized: Record<string, string> = {};\n if (params?.contentName) normalized.content_name = params.contentName;\n if (params?.contentCategory) normalized.content_category = params.contentCategory;\n\n const customData = Object.keys(normalized).length > 0 ? normalized : undefined;\n const eventData = eventId ? { eventID: eventId } : undefined;\n\n log('meta-pixel', 'PIXEL_EVENT_FIRED', {\n event,\n ...normalized,\n ...(eventId ? { eventId } : {}),\n }, { shipToPostHog: true });\n fbq('track', event, customData, eventData);\n}\n","'use client';\n\nimport { useEffect } from 'react';\nimport { usePathname } from 'next/navigation';\nimport { firePixelEvent } from './firePixelEvent';\n\ntype RouteRule = {\n pattern: RegExp;\n getParams: (match: RegExpMatchArray) => { contentName: string; contentCategory: string };\n};\n\nfunction slugToTitle(slug: string): string {\n return slug\n .split('-')\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1))\n .join(' ');\n}\n\n// Checked in order — first match wins. More specific patterns come first.\nconst ROUTE_RULES: RouteRule[] = [\n {\n pattern: /^\\/services\\/(.+)$/,\n getParams: ([, slug]) => ({ contentName: slugToTitle(slug), contentCategory: 'Service' }),\n },\n {\n pattern: /^\\/locations\\/(.+)$/,\n getParams: ([, slug]) => ({ contentName: slugToTitle(slug), contentCategory: 'Location' }),\n },\n {\n pattern: /^\\/services$/,\n getParams: () => ({ contentName: 'Services', contentCategory: 'Services' }),\n },\n {\n pattern: /^\\/locations$/,\n getParams: () => ({ contentName: 'Locations', contentCategory: 'Locations' }),\n },\n {\n pattern: /^\\/portal$/,\n getParams: () => ({ contentName: 'Member Portal', contentCategory: 'Pricing' }),\n },\n {\n pattern: /^\\/service-menu$/,\n getParams: () => ({ contentName: 'Service Menu', contentCategory: 'Pricing' }),\n },\n {\n pattern: /^\\/faq$/,\n getParams: () => ({ contentName: 'FAQ', contentCategory: 'FAQ' }),\n },\n {\n pattern: /^\\/contact$/,\n getParams: () => ({ contentName: 'Contact', contentCategory: 'Contact' }),\n },\n];\n\ntype Props = {\n /** External booking URL. When set, fires InitiateCheckout on any click targeting that URL. */\n bookingUrl?: string | null;\n};\n\n/**\n * Single client-side tracker placed once in KeystoneRootLayout.\n * - Fires ViewContent on every route change for known page patterns.\n * - Fires InitiateCheckout whenever a visitor clicks a link to the external booking URL.\n *\n * Portal booking tab tracking is handled directly inside PortalPage via PortalTabTracker\n * since intent is only established when the Booking tab is explicitly opened.\n */\nexport function MetaPixelTracker({ bookingUrl }: Props) {\n const pathname = usePathname();\n\n useEffect(() => {\n for (const rule of ROUTE_RULES) {\n const match = pathname.match(rule.pattern);\n if (match) {\n const { contentName, contentCategory } = rule.getParams(match);\n firePixelEvent('ViewContent', { contentName, contentCategory });\n break;\n }\n }\n }, [pathname]);\n\n useEffect(() => {\n if (!bookingUrl) return;\n\n const handleClick = (e: MouseEvent) => {\n const anchor = (e.target as Element).closest('a');\n if (anchor?.href?.startsWith(bookingUrl)) {\n firePixelEvent('InitiateCheckout');\n }\n };\n\n document.addEventListener('click', handleClick);\n return () => document.removeEventListener('click', handleClick);\n }, [bookingUrl]);\n\n return null;\n}\n","'use client';\n\nimport posthog from 'posthog-js';\nimport { PostHogProvider as PHProvider } from 'posthog-js/react';\nimport { useEffect, Suspense } from 'react';\nimport { usePathname, useSearchParams } from 'next/navigation';\nimport { error as logError, initializePostHogLogging } from './logging';\n\nconst DEFAULT_HOST = 'https://us.i.posthog.com';\n\nexport type PostHogProviderProps = {\n apiKey: string;\n apiHost?: string;\n /** Keystone account ID — attached to every event as a super property. */\n accountId?: number;\n /** Keystone account name (company_name) — attached to every event as a super property. */\n accountName?: string;\n /**\n * Environment identifier from KEYSTONE_ENV on the Rails server (e.g. \"production\", \"staging\",\n * \"development\"). Registered as a super property so events can be filtered by environment\n * in PostHog and the sync job only imports data for the matching environment.\n */\n environment?: string;\n children: React.ReactNode;\n};\n\n// ---------------------------------------------------------------------------\n// Page name resolution\n// ---------------------------------------------------------------------------\n\ntype PageInfo = { page_name: string; page_slug?: string };\n\nfunction resolvePageInfo(pathname: string): PageInfo {\n if (pathname === '/') return { page_name: 'home' };\n\n const patterns: Array<[RegExp, (m: RegExpMatchArray) => PageInfo]> = [\n [/^\\/services\\/(.+)$/, ([, slug]) => ({ page_name: 'service_detail', page_slug: slug })],\n [/^\\/locations\\/(.+)$/, ([, slug]) => ({ page_name: 'location_detail', page_slug: slug })],\n [/^\\/blog\\/(.+)$/, ([, slug]) => ({ page_name: 'blog_post', page_slug: slug })],\n [/^\\/jobs\\/(.+)$/, ([, slug]) => ({ page_name: 'job_detail', page_slug: slug })],\n [/^\\/packages\\/(.+)$/, ([, slug]) => ({ page_name: 'package_detail', page_slug: slug })],\n ];\n\n for (const [pattern, resolve] of patterns) {\n const match = pathname.match(pattern);\n if (match) return resolve(match);\n }\n\n const staticNames: Record<string, string> = {\n '/services': 'services',\n '/locations': 'locations',\n '/contact': 'contact',\n '/about': 'about',\n '/blog': 'blog',\n '/portal': 'portal',\n '/gallery': 'gallery',\n '/team': 'team',\n '/faq': 'faq',\n '/reviews': 'reviews',\n '/jobs': 'jobs',\n '/packages': 'packages',\n '/service-menu': 'service_menu',\n '/privacy-policy': 'privacy_policy',\n '/terms': 'terms_of_service',\n };\n\n return { page_name: staticNames[pathname] ?? 'unknown' };\n}\n\n// ---------------------------------------------------------------------------\n// Pageview tracker — must be wrapped in <Suspense> (useSearchParams)\n// ---------------------------------------------------------------------------\n\nfunction PostHogPageviewTracker() {\n const pathname = usePathname();\n const searchParams = useSearchParams();\n\n useEffect(() => {\n if (!pathname) return;\n\n const search = searchParams?.toString();\n const url = window.location.origin + pathname + (search ? `?${search}` : '');\n const { page_name, page_slug } = resolvePageInfo(pathname);\n\n posthog.capture('$pageview', {\n $current_url: url,\n page_name,\n page_path: pathname,\n ...(page_slug && { page_slug }),\n });\n }, [pathname, searchParams]);\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Provider\n// ---------------------------------------------------------------------------\n\n/**\n * Initialises PostHog, registers account-level super properties, and fires\n * an enriched `$pageview` on every App Router navigation.\n *\n * Super properties attached to every event automatically:\n * - account_id (Keystone account ID)\n * - account_name (company_name)\n * - site_domain (window.location.hostname)\n * - environment (KEYSTONE_ENV — e.g. \"production\", \"staging\", \"local_rahuljaswa\")\n *\n * Mount once in the root layout body. One project key covers all customer\n * sites — filter by account_name or site_domain in the PostHog dashboard.\n */\nexport function PostHogProvider({ apiKey, apiHost, accountId, accountName, environment, children }: PostHogProviderProps) {\n useEffect(() => {\n posthog.init(apiKey, {\n api_host: apiHost ?? DEFAULT_HOST,\n person_profiles: 'identified_only',\n capture_pageview: false,\n });\n\n posthog.register({\n ...(accountId !== undefined && { account_id: accountId }),\n ...(accountName && { account_name: accountName }),\n ...(environment && { environment }),\n site_domain: window.location.hostname,\n });\n\n initializePostHogLogging({\n apiKey,\n apiHost: apiHost ?? DEFAULT_HOST,\n serviceName: 'keystone-customer-site',\n environment,\n accountId,\n accountName,\n });\n }, [apiKey, apiHost, accountId, accountName, environment]);\n\n useEffect(() => {\n const onWindowError = (event: ErrorEvent) => {\n logError(\n 'global-client',\n 'WINDOW_ERROR',\n {\n message: event.message || 'unknown_error',\n filename: event.filename || 'unknown_file',\n lineno: event.lineno || 0,\n colno: event.colno || 0,\n stack: event.error instanceof Error ? event.error.stack || '' : '',\n },\n { shipToPostHog: true },\n );\n };\n\n const onUnhandledRejection = (event: PromiseRejectionEvent) => {\n const reason = event.reason;\n const payload =\n reason instanceof Error\n ? { message: reason.message, stack: reason.stack || '' }\n : { message: String(reason), stack: '' };\n\n logError('global-client', 'UNHANDLED_REJECTION', payload, { shipToPostHog: true });\n };\n\n window.addEventListener('error', onWindowError);\n window.addEventListener('unhandledrejection', onUnhandledRejection);\n return () => {\n window.removeEventListener('error', onWindowError);\n window.removeEventListener('unhandledrejection', onUnhandledRejection);\n };\n }, []);\n\n return (\n <PHProvider client={posthog}>\n <Suspense fallback={null}>\n <PostHogPageviewTracker />\n </Suspense>\n {children}\n </PHProvider>\n );\n}\n","'use client';\n\nimport { useEffect } from 'react';\nimport { usePathname } from 'next/navigation';\nimport { captureEvent } from './captureEvent';\n\ntype Props = {\n /** External booking / portal URL. When set, fires booking_cta_clicked on any click to that URL. */\n bookingUrl?: string | null;\n};\n\n/**\n * Page-level PostHog event tracker. Mount once inside PostHogProvider in\n * KeystoneRootLayout alongside MetaPixelTracker.\n *\n * Responsibilities:\n * - booking_cta_clicked: fires when any link to the booking URL is clicked,\n * capturing the page the visitor was on when they clicked.\n */\nexport function KeystoneAnalyticsTracker({ bookingUrl }: Props) {\n const pathname = usePathname();\n\n useEffect(() => {\n if (!bookingUrl) return;\n\n const handleClick = (e: MouseEvent) => {\n const anchor = (e.target as Element).closest('a');\n if (anchor?.href?.startsWith(bookingUrl)) {\n captureEvent('booking_cta_clicked', {\n source_path: pathname,\n booking_url: bookingUrl,\n });\n }\n };\n\n document.addEventListener('click', handleClick);\n return () => document.removeEventListener('click', handleClick);\n }, [bookingUrl, pathname]);\n\n return null;\n}\n","/**\n * PostHog event capture — Keystone customer sites.\n *\n * ## Naming convention\n * All events use snake_case `object_action` format.\n * Properties use snake_case as well.\n *\n * ## Event taxonomy\n * Add new events here: define the name in `KsEventName` and its required\n * properties in `KsEventProperties`. Every callsite is then type-checked.\n *\n * ## Usage\n *\n * import { captureEvent } from 'keystone-design-bootstrap/tracking';\n *\n * captureEvent('form_submitted', { form_type: 'lead' });\n * captureEvent('booking_cta_clicked', { source_path: '/services/massage', booking_url: url });\n *\n * All calls are safe no-ops when PostHog has not been initialised (e.g. no\n * POSTHOG_API_KEY configured on the server).\n *\n * ## Super properties\n * account_id, account_name, and site_domain are registered as super properties\n * by PostHogProvider and are automatically attached to every event — you do not\n * need to include them in individual captureEvent calls.\n */\n\nimport posthog from 'posthog-js';\n\n// ---------------------------------------------------------------------------\n// Event taxonomy\n// ---------------------------------------------------------------------------\n\nexport type KsEventName =\n // Booking / conversion\n | 'booking_cta_clicked'\n // Forms\n | 'form_submitted'\n | 'form_failed'\n // Chat widget\n | 'chat_opened'\n | 'chat_message_sent'\n | 'chat_message_failed'\n // Member portal — tab navigation\n | 'portal_tab_viewed'\n // Member portal — authentication flow\n | 'portal_login_started'\n | 'portal_login_step_viewed'\n | 'portal_login_step_submitted'\n | 'portal_login_step_advanced'\n | 'portal_login_back_clicked'\n | 'portal_login_identified'\n | 'portal_login_completed'\n | 'portal_login_failed';\n\nexport type KsEventProperties = {\n /** Fired when a visitor clicks any CTA that links to the external booking URL. */\n booking_cta_clicked: {\n source_path: string;\n booking_url: string;\n };\n\n /** Fired when a Keystone form is successfully submitted. */\n form_submitted: {\n /** One of: lead | job_application | marketing_list_signup */\n form_type: string;\n /** Server-generated event ID for CAPI deduplication (when present). */\n event_id?: string;\n };\n\n /** Fired when a form submission fails (validation error or network error). */\n form_failed: {\n form_type: string;\n error: string;\n };\n\n /** Fired when the chat widget is first opened by the visitor. */\n chat_opened: Record<string, never>;\n\n /** Fired when a chat message is successfully sent. */\n chat_message_sent: {\n /** Whether the visitor is authenticated (contactId present). */\n is_authenticated: boolean;\n };\n\n /** Fired when a chat message fails to send. */\n chat_message_failed: {\n error: string;\n };\n\n /** Fired when a member portal tab is opened. */\n portal_tab_viewed: {\n tab: string;\n };\n\n /** Fired when the portal login modal is opened / login flow starts. */\n portal_login_started: Record<string, never>;\n\n /** Fired whenever a login step becomes visible to the user. */\n portal_login_step_viewed: {\n step: 'identifier' | 'signin' | 'signup';\n };\n\n /** Fired when the user submits a specific login step. */\n portal_login_step_submitted: {\n step: 'identifier' | 'signin' | 'signup';\n };\n\n /** Fired when the flow transitions from one step to another state. */\n portal_login_step_advanced: {\n from_step: 'identifier' | 'signin' | 'signup';\n to_step: 'signin' | 'signup' | 'authenticated';\n reason: string;\n };\n\n /** Fired when the user explicitly navigates back to the identifier step. */\n portal_login_back_clicked: {\n from_step: 'signin' | 'signup';\n to_step: 'identifier';\n };\n\n /**\n * Fired after the identifier step resolves — we know whether the user\n * already has an account.\n */\n portal_login_identified: {\n method: 'email' | 'phone' | 'email_and_phone';\n user_exists: boolean;\n };\n\n /** Fired after the user successfully signs in or creates an account. */\n portal_login_completed: {\n flow: 'signin' | 'signup';\n };\n\n /** Fired when any step of the login flow returns an error. */\n portal_login_failed: {\n step: 'identifier' | 'signin' | 'signup';\n reason: string;\n };\n};\n\n// ---------------------------------------------------------------------------\n// Capture helper\n// ---------------------------------------------------------------------------\n\n/**\n * Captures a typed Keystone analytics event via PostHog.\n * Safe no-op when PostHog has not been initialised.\n */\nexport function captureEvent<E extends KsEventName>(\n event: E,\n ...args: KsEventProperties[E] extends Record<string, never>\n ? []\n : [properties: KsEventProperties[E]]\n): void {\n posthog.capture(event, args[0] as Record<string, unknown>);\n}\n\n/**\n * Captures an app-specific (\"custom\") analytics event via PostHog.\n *\n * Unlike `captureEvent` — which is restricted to the shared, type-checked\n * Keystone taxonomy (`KsEventName`) — this is a thin, open-ended escape hatch\n * for product-specific funnels that don't belong in the shared taxonomy\n * (e.g. the Grader's `grader_scan_started`, or any one-off consumer app event).\n * It keeps those events out of the shared union while still flowing through the\n * same initialised PostHog instance, so account super properties and config are\n * applied identically.\n *\n * Conventions (not enforced, but please follow):\n * - snake_case `object_action` names, prefixed with your app/feature so events\n * are easy to group and never collide with the shared taxonomy\n * (e.g. `grader_scan_started`; `portal_*`, `form_*`, `chat_*`, `booking_*`\n * are reserved by `captureEvent`).\n * - snake_case property keys.\n *\n * `account_id` / `account_name` / `site_domain` super properties are attached\n * automatically by PostHogProvider. Safe no-op when PostHog isn't initialised.\n *\n * For events that are reused across multiple sites, promote them into the typed\n * `KsEventName` taxonomy above and use `captureEvent` instead.\n */\nexport function captureCustomEvent(\n event: string,\n properties?: Record<string, unknown>,\n): void {\n posthog.capture(event, properties);\n}\n","const GTM_SCRIPT = (containerId: string) => `\n (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':\n new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],\n j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=\n 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);\n })(window,document,'script','dataLayer','${containerId.replace(/'/g, \"\\\\'\")}');\n`;\n\nexport type GoogleTagManagerProps = {\n /** GTM container public ID (e.g. GTM-ABC123). */\n containerId: string | null | undefined;\n};\n\n/**\n * Renders Google Tag Manager script + noscript fallback.\n * Mount once at root layout level when a container is provisioned.\n */\nexport function GoogleTagManager({ containerId }: GoogleTagManagerProps) {\n const raw = typeof containerId === 'string' ? containerId.trim() : '';\n const id = raw && raw !== 'null' && /^GTM-[A-Z0-9]+$/i.test(raw) ? raw : '';\n\n if (!id) {\n return null;\n }\n\n return (\n <>\n <script id=\"google-tag-manager\" dangerouslySetInnerHTML={{ __html: GTM_SCRIPT(id) }} />\n <noscript>\n <iframe\n src={`https://www.googletagmanager.com/ns.html?id=${id}`}\n height=\"0\"\n width=\"0\"\n style={{ display: 'none', visibility: 'hidden' }}\n title=\"google-tag-manager\"\n />\n </noscript>\n </>\n );\n}\n"],"mappings":"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","names":["useEffect","useEffect","useEffect","usePathname","usePathname","useEffect","useEffect","usePathname","posthog","usePathname","useEffect"]}
|
package/package.json
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { createContext, useContext } from 'react';
|
|
4
4
|
import { Theme, isValidTheme } from '../themes';
|
|
5
|
+
import { warn } from '../tracking/logging';
|
|
5
6
|
|
|
6
7
|
interface ThemeContextValue {
|
|
7
8
|
theme: Theme;
|
|
@@ -18,7 +19,7 @@ export function ThemeProvider({
|
|
|
18
19
|
}) {
|
|
19
20
|
// Validate theme at runtime
|
|
20
21
|
if (!isValidTheme(theme)) {
|
|
21
|
-
|
|
22
|
+
warn('theme', 'INVALID_THEME_FALLBACK', { theme });
|
|
22
23
|
theme = 'classic';
|
|
23
24
|
}
|
|
24
25
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { useCallback, useEffect, useRef } from 'react';
|
|
9
9
|
import { createConsumer } from '@rails/actioncable';
|
|
10
|
+
import { error as logError, log, warn } from '../../tracking/logging';
|
|
10
11
|
|
|
11
12
|
export interface RealtimeSubscriptionData {
|
|
12
13
|
token: string;
|
|
@@ -72,16 +73,29 @@ export function useRealtimeReplyOrchestrator({
|
|
|
72
73
|
{ channel: 'ContactCommunicationsChannel', contact_id: String(contactIdForStream) },
|
|
73
74
|
{
|
|
74
75
|
connected: () => {
|
|
75
|
-
|
|
76
|
+
log('chat-realtime', 'REALTIME_CONNECTED', {
|
|
77
|
+
debugLabel,
|
|
78
|
+
contactId: contactIdForStream,
|
|
79
|
+
}, { shipToPostHog: true });
|
|
76
80
|
},
|
|
77
81
|
disconnected: () => {
|
|
78
|
-
|
|
82
|
+
warn('chat-realtime', 'REALTIME_DISCONNECTED', {
|
|
83
|
+
debugLabel,
|
|
84
|
+
contactId: contactIdForStream,
|
|
85
|
+
});
|
|
79
86
|
},
|
|
80
87
|
rejected: () => {
|
|
81
|
-
|
|
88
|
+
warn('chat-realtime', 'REALTIME_REJECTED', {
|
|
89
|
+
debugLabel,
|
|
90
|
+
contactId: contactIdForStream,
|
|
91
|
+
});
|
|
82
92
|
},
|
|
83
93
|
received: async (data: { type?: string }) => {
|
|
84
|
-
|
|
94
|
+
log('chat-realtime', 'REALTIME_RECEIVED', {
|
|
95
|
+
debugLabel,
|
|
96
|
+
type: data?.type ?? 'unknown',
|
|
97
|
+
contactId: contactIdForStream,
|
|
98
|
+
}, { shipToPostHog: true });
|
|
85
99
|
if (data?.type === 'agent_thinking') {
|
|
86
100
|
onAgentThinking?.();
|
|
87
101
|
return;
|
|
@@ -93,7 +107,12 @@ export function useRealtimeReplyOrchestrator({
|
|
|
93
107
|
resolveReply();
|
|
94
108
|
}
|
|
95
109
|
} catch (error) {
|
|
96
|
-
|
|
110
|
+
logError('chat-realtime', 'REALTIME_RECEIVE_HANDLER_ERROR', {
|
|
111
|
+
debugLabel,
|
|
112
|
+
contactId: contactIdForStream,
|
|
113
|
+
message: error instanceof Error ? error.message : String(error),
|
|
114
|
+
stack: error instanceof Error ? error.stack || '' : '',
|
|
115
|
+
});
|
|
97
116
|
}
|
|
98
117
|
},
|
|
99
118
|
}
|
|
@@ -6,6 +6,7 @@ import { Avatar } from '../elements/avatar/avatar';
|
|
|
6
6
|
import { cx } from '../../utils/cx';
|
|
7
7
|
import { captureEvent } from '../../tracking/captureEvent';
|
|
8
8
|
import { useRealtimeReplyOrchestrator, type RealtimeSubscriptionData } from '../chat/useRealtimeReplyOrchestrator';
|
|
9
|
+
import { error as logError } from '../../tracking/logging';
|
|
9
10
|
|
|
10
11
|
interface Message {
|
|
11
12
|
id: string;
|
|
@@ -113,7 +114,10 @@ export function ChatWidget({
|
|
|
113
114
|
return newMessages;
|
|
114
115
|
}
|
|
115
116
|
} catch (error) {
|
|
116
|
-
|
|
117
|
+
logError('chat-widget', 'LOAD_MESSAGES_FAILED', {
|
|
118
|
+
message: error instanceof Error ? error.message : String(error),
|
|
119
|
+
stack: error instanceof Error ? error.stack || '' : '',
|
|
120
|
+
});
|
|
117
121
|
}
|
|
118
122
|
return [];
|
|
119
123
|
}, [contactId, sessionId]);
|
|
@@ -236,14 +240,17 @@ export function ChatWidget({
|
|
|
236
240
|
} else {
|
|
237
241
|
setMessages(prev => prev.filter(m => m.id !== tempMessage.id));
|
|
238
242
|
captureEvent('chat_message_failed', { error: 'send_failed' });
|
|
239
|
-
|
|
243
|
+
logError('chat-widget', 'SEND_MESSAGE_NON_OK');
|
|
240
244
|
setIsLoading(false);
|
|
241
245
|
setWaitingForReply(false);
|
|
242
246
|
}
|
|
243
247
|
} catch (error) {
|
|
244
248
|
setMessages(prev => prev.filter(m => m.id !== tempMessage.id));
|
|
245
249
|
captureEvent('chat_message_failed', { error: 'network_error' });
|
|
246
|
-
|
|
250
|
+
logError('chat-widget', 'SEND_MESSAGE_NETWORK_ERROR', {
|
|
251
|
+
message: error instanceof Error ? error.message : String(error),
|
|
252
|
+
stack: error instanceof Error ? error.stack || '' : '',
|
|
253
|
+
});
|
|
247
254
|
setIsLoading(false);
|
|
248
255
|
setWaitingForReply(false);
|
|
249
256
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useEffect, useRef, useState, useCallback } from 'react';
|
|
4
|
+
import { error as logError, warn } from '../../../tracking/logging';
|
|
4
5
|
|
|
5
6
|
interface GoogleMapProps {
|
|
6
7
|
address: string;
|
|
@@ -93,7 +94,9 @@ export default function GoogleMap({ address, locationName, className = '' }: Goo
|
|
|
93
94
|
|
|
94
95
|
// Check if Geocoder is available (required for async loading)
|
|
95
96
|
if (!window.google.maps.Geocoder) {
|
|
96
|
-
|
|
97
|
+
warn('google-map', 'GEOCODER_NOT_READY_RETRYING', {
|
|
98
|
+
address,
|
|
99
|
+
});
|
|
97
100
|
setTimeout(() => {
|
|
98
101
|
if (mapRef.current && window.google?.maps?.Geocoder && initMapRef.current) {
|
|
99
102
|
void initMapRef.current();
|
|
@@ -174,7 +177,11 @@ export default function GoogleMap({ address, locationName, className = '' }: Goo
|
|
|
174
177
|
isInitializingRef.current = false;
|
|
175
178
|
});
|
|
176
179
|
} catch (error) {
|
|
177
|
-
|
|
180
|
+
logError('google-map', 'MAP_INITIALIZATION_FAILED', {
|
|
181
|
+
address,
|
|
182
|
+
message: error instanceof Error ? error.message : String(error),
|
|
183
|
+
stack: error instanceof Error ? error.stack || '' : '',
|
|
184
|
+
});
|
|
178
185
|
setMapError('Error loading map. Please try again later.');
|
|
179
186
|
isInitializingRef.current = false;
|
|
180
187
|
}
|
|
@@ -5,6 +5,7 @@ import { Link01, Copy01 } from '@untitledui/icons';
|
|
|
5
5
|
import { Badge, BadgeGroup, Button, Form, Input, Avatar, Breadcrumb, MarkdownRenderer, PhotoWithFallback } from '../elements';
|
|
6
6
|
import { extractTableOfContents } from '../../utils/markdown-toc';
|
|
7
7
|
import { getAvatarUrl } from '../../utils/photo-helpers';
|
|
8
|
+
import { error as logError, log } from '../../tracking/logging';
|
|
8
9
|
import type { BlogPost } from '../../types/api/blog-post';
|
|
9
10
|
|
|
10
11
|
interface ExtendedBlogPost extends BlogPost {
|
|
@@ -54,7 +55,10 @@ export const BlogPostSection = ({
|
|
|
54
55
|
setCopied(true);
|
|
55
56
|
setTimeout(() => setCopied(false), 2000);
|
|
56
57
|
} catch (err) {
|
|
57
|
-
|
|
58
|
+
logError('blog-post', 'COPY_LINK_FAILED', {
|
|
59
|
+
message: err instanceof Error ? err.message : String(err),
|
|
60
|
+
stack: err instanceof Error ? err.stack || '' : '',
|
|
61
|
+
});
|
|
58
62
|
}
|
|
59
63
|
};
|
|
60
64
|
|
|
@@ -64,7 +68,10 @@ export const BlogPostSection = ({
|
|
|
64
68
|
title: blogPost?.title,
|
|
65
69
|
url: window.location.href,
|
|
66
70
|
}).catch((err) => {
|
|
67
|
-
|
|
71
|
+
logError('blog-post', 'SHARE_LINK_FAILED', {
|
|
72
|
+
message: err instanceof Error ? err.message : String(err),
|
|
73
|
+
stack: err instanceof Error ? err.stack || '' : '',
|
|
74
|
+
});
|
|
68
75
|
});
|
|
69
76
|
} else {
|
|
70
77
|
// Fallback to copy if share is not available
|
|
@@ -194,7 +201,10 @@ export const BlogPostSection = ({
|
|
|
194
201
|
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
|
195
202
|
e.preventDefault();
|
|
196
203
|
const formData = new FormData(e.currentTarget);
|
|
197
|
-
|
|
204
|
+
const email = formData.get('email');
|
|
205
|
+
log('blog-post', 'NEWSLETTER_FORM_SUBMIT', {
|
|
206
|
+
email: typeof email === 'string' ? email : '',
|
|
207
|
+
}, { shipToPostHog: true });
|
|
198
208
|
}}
|
|
199
209
|
className="flex flex-col gap-4"
|
|
200
210
|
>
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
1
3
|
import { Fragment } from "react";
|
|
2
4
|
import Image from "next/image";
|
|
3
5
|
import { Button, Form, Input } from '../elements';
|
|
4
6
|
import { cx } from '../../utils/cx';
|
|
5
7
|
import IconComponent from '../elements/IconComponent';
|
|
6
8
|
import { getGradientUrl } from '../../utils/gradient-placeholder';
|
|
9
|
+
import { log } from '../../tracking/logging';
|
|
7
10
|
import type { WebsitePhotos } from '../../types/api/website-photos';
|
|
8
11
|
|
|
9
12
|
interface HomeHeroComponentProps {
|
|
@@ -146,7 +149,7 @@ export const HomeHeroComponent = ({
|
|
|
146
149
|
if (onEmailSubmit) {
|
|
147
150
|
onEmailSubmit(email);
|
|
148
151
|
} else {
|
|
149
|
-
|
|
152
|
+
log('home-hero', 'EMAIL_SIGNUP_SUBMIT', { email }, { shipToPostHog: true });
|
|
150
153
|
}
|
|
151
154
|
}}
|
|
152
155
|
className="mt-8 flex w-full flex-col items-stretch gap-4 md:mt-12 md:max-w-120 md:flex-row md:items-start"
|
package/src/lib/server-api.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { CompanyInformation } from '../types/api/company-information';
|
|
|
7
7
|
import type { Service } from '../types/api/service';
|
|
8
8
|
import type { Package } from '../types/api/package';
|
|
9
9
|
import type { WebsitePhotos } from '../types/api/website-photos';
|
|
10
|
+
import { serverError } from './server-log';
|
|
10
11
|
|
|
11
12
|
const API_URL = process.env.API_URL || 'http://localhost:3000/api/v1';
|
|
12
13
|
const API_KEY = process.env.API_KEY || '';
|
|
@@ -35,7 +36,10 @@ async function serverFetch<T>(endpoint: string, options: FetchOptions = {}): Pro
|
|
|
35
36
|
const response = await fetch(url, fetchOptions);
|
|
36
37
|
|
|
37
38
|
if (!response.ok) {
|
|
38
|
-
|
|
39
|
+
serverError('SERVER_API_NON_OK_RESPONSE', undefined, {
|
|
40
|
+
endpoint,
|
|
41
|
+
status: response.status,
|
|
42
|
+
});
|
|
39
43
|
return null;
|
|
40
44
|
}
|
|
41
45
|
|
|
@@ -44,7 +48,7 @@ async function serverFetch<T>(endpoint: string, options: FetchOptions = {}): Pro
|
|
|
44
48
|
// Rails API returns { data: [...], meta: {...} }
|
|
45
49
|
return json.data ?? json;
|
|
46
50
|
} catch (error) {
|
|
47
|
-
|
|
51
|
+
serverError('SERVER_API_FETCH_FAILED', error, { endpoint });
|
|
48
52
|
return null;
|
|
49
53
|
}
|
|
50
54
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
type ServerLogLevel = 'INFO' | 'WARN' | 'ERROR';
|
|
2
|
+
|
|
3
|
+
function normalizeError(error: unknown): Record<string, string> {
|
|
4
|
+
if (error instanceof Error) {
|
|
5
|
+
return {
|
|
6
|
+
message: error.message,
|
|
7
|
+
stack: error.stack || '',
|
|
8
|
+
name: error.name,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
return { message: String(error) };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function writeServerLog(level: ServerLogLevel, event: string, detail?: Record<string, unknown>): void {
|
|
15
|
+
const payload = {
|
|
16
|
+
level,
|
|
17
|
+
event,
|
|
18
|
+
detail: detail ?? {},
|
|
19
|
+
at: new Date().toISOString(),
|
|
20
|
+
};
|
|
21
|
+
const line = `[ks-server] ${JSON.stringify(payload)}\n`;
|
|
22
|
+
const stream = level === 'ERROR' ? process.stderr : process.stdout;
|
|
23
|
+
stream.write(line);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function serverLog(event: string, detail?: Record<string, unknown>): void {
|
|
27
|
+
writeServerLog('INFO', event, detail);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function serverWarn(event: string, detail?: Record<string, unknown>): void {
|
|
31
|
+
writeServerLog('WARN', event, detail);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function serverError(event: string, error?: unknown, detail?: Record<string, unknown>): void {
|
|
35
|
+
writeServerLog('ERROR', event, {
|
|
36
|
+
...(detail ?? {}),
|
|
37
|
+
...(error !== undefined ? { error: normalizeError(error) } : {}),
|
|
38
|
+
});
|
|
39
|
+
}
|
package/src/next/routes/chat.ts
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
// own `NextResponse` (from its own `next/server`) while we keep the core logic shared.
|
|
23
23
|
|
|
24
24
|
import { clientContextHeaders } from './proxy-headers';
|
|
25
|
+
import { serverError } from '../../lib/server-log';
|
|
25
26
|
|
|
26
27
|
const API_URL = process.env.API_URL || 'http://localhost:3000/api/v1';
|
|
27
28
|
const API_KEY = process.env.API_KEY || '';
|
|
@@ -82,7 +83,7 @@ export function createChatRouteHandlers(deps?: { NextResponse?: { json: JsonResp
|
|
|
82
83
|
const cableUrl = API_URL.replace(/\/api\/v1\/?$/, '').replace(/^http/, 'ws') + '/cable';
|
|
83
84
|
return json({ success: true, data: { ...responseData, cable_url: cableUrl } });
|
|
84
85
|
} catch (error) {
|
|
85
|
-
|
|
86
|
+
serverError('CHAT_ROUTE_POST_ERROR', error);
|
|
86
87
|
return json({ success: false, error: 'Network error. Please try again later.' }, { status: 500 });
|
|
87
88
|
}
|
|
88
89
|
},
|
|
@@ -156,7 +157,7 @@ export function createChatRouteHandlers(deps?: { NextResponse?: { json: JsonResp
|
|
|
156
157
|
}
|
|
157
158
|
return json({ success: true, data: data.data || [] });
|
|
158
159
|
} catch (error) {
|
|
159
|
-
|
|
160
|
+
serverError('CHAT_ROUTE_GET_ERROR', error);
|
|
160
161
|
return json({ success: false, error: 'Network error. Please try again later.' }, { status: 500 });
|
|
161
162
|
}
|
|
162
163
|
},
|
package/src/next/routes/form.ts
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
// `next/server`) to avoid type identity conflicts when used as a local file dependency.
|
|
33
33
|
|
|
34
34
|
import { clientContextHeaders } from './proxy-headers';
|
|
35
|
+
import { serverError } from '../../lib/server-log';
|
|
35
36
|
|
|
36
37
|
const API_URL = process.env.API_URL || 'http://localhost:3000/api/v1';
|
|
37
38
|
const API_KEY = process.env.API_KEY || '';
|
|
@@ -84,7 +85,7 @@ export function createFormRouteHandlers(deps?: { NextResponse?: { json: JsonResp
|
|
|
84
85
|
|
|
85
86
|
return json(payload);
|
|
86
87
|
} catch (error) {
|
|
87
|
-
|
|
88
|
+
serverError('FORM_SUBMISSION_ROUTE_ERROR', error);
|
|
88
89
|
return json(
|
|
89
90
|
{
|
|
90
91
|
success: false,
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { useEffect } from 'react';
|
|
3
4
|
import Script from 'next/script';
|
|
5
|
+
import { firePixelEvent } from './firePixelEvent';
|
|
6
|
+
import { log } from './logging';
|
|
4
7
|
|
|
5
8
|
const FBEVENTS_URL = 'https://connect.facebook.net/en_US/fbevents.js';
|
|
6
9
|
|
|
@@ -13,7 +16,6 @@ const PIXEL_SCRIPT = (pixelId: string) => `
|
|
|
13
16
|
t.src=v;s=b.getElementsByTagName(e)[0];
|
|
14
17
|
s.parentNode.insertBefore(t,s)}(window, document,'script','${FBEVENTS_URL}');
|
|
15
18
|
fbq('init', '${pixelId.replace(/'/g, "\\'")}');
|
|
16
|
-
fbq('track', 'PageView');
|
|
17
19
|
window.__ks_pixel_ids = window.__ks_pixel_ids || [];
|
|
18
20
|
if (window.__ks_pixel_ids.indexOf('${pixelId.replace(/'/g, "\\'")}') === -1) {
|
|
19
21
|
window.__ks_pixel_ids.push('${pixelId.replace(/'/g, "\\'")}');
|
|
@@ -33,6 +35,13 @@ export type MetaPixelProps = {
|
|
|
33
35
|
export function MetaPixel({ pixelId }: MetaPixelProps) {
|
|
34
36
|
const raw = typeof pixelId === 'string' ? pixelId.trim() : '';
|
|
35
37
|
const id = raw && raw !== 'null' && /^\d+$/.test(raw) ? raw : '';
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (!id) return;
|
|
41
|
+
log('meta-pixel', 'PIXEL_INIT', { pixelId: id }, { shipToPostHog: true });
|
|
42
|
+
firePixelEvent('PageView');
|
|
43
|
+
}, [id]);
|
|
44
|
+
|
|
36
45
|
if (!id) {
|
|
37
46
|
return null;
|
|
38
47
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
type FbqFn = (method: string, ...args: unknown[]) => void;
|
|
2
|
+
import { log, warn } from './logging';
|
|
2
3
|
|
|
3
4
|
export type PixelEvent = 'PageView' | 'ViewContent' | 'InitiateCheckout' | 'Lead';
|
|
4
5
|
|
|
@@ -91,7 +92,7 @@ export async function setPixelUserData(userData: PixelUserData): Promise<void> {
|
|
|
91
92
|
export function firePixelEvent(event: PixelEvent, params?: PixelEventParams, eventId?: string): void {
|
|
92
93
|
const fbq = getFbq();
|
|
93
94
|
if (!fbq) {
|
|
94
|
-
|
|
95
|
+
warn('meta-pixel', 'PIXEL_EVENT_SKIPPED_FBQ_NOT_LOADED', { event });
|
|
95
96
|
return;
|
|
96
97
|
}
|
|
97
98
|
|
|
@@ -106,6 +107,10 @@ export function firePixelEvent(event: PixelEvent, params?: PixelEventParams, eve
|
|
|
106
107
|
const customData = Object.keys(normalized).length > 0 ? normalized : undefined;
|
|
107
108
|
const eventData = eventId ? { eventID: eventId } : undefined;
|
|
108
109
|
|
|
109
|
-
|
|
110
|
+
log('meta-pixel', 'PIXEL_EVENT_FIRED', {
|
|
111
|
+
event,
|
|
112
|
+
...normalized,
|
|
113
|
+
...(eventId ? { eventId } : {}),
|
|
114
|
+
}, { shipToPostHog: true });
|
|
110
115
|
fbq('track', event, customData, eventData);
|
|
111
116
|
}
|