Files
zeekay 1e58ce9811 Take @hanzo/event 0.3.66: an eu visitor who accepts reaches Meta
Under 0.3.63 the held revoke and the grant waited together in fbevents'
queue, so an eu visitor who pressed Accept all stayed revoked and no
PageView reached the Pixel. 0.3.66 fixes that; 0.3.65 adds the audience
events (product_viewed, search_performed, cta_clicked, the leads). Every
export this site imports is unchanged.
2026-10-30 07:39:14 -07:00

89 lines
3.6 KiB
TypeScript

'use client'
import { useEffect, useRef } from 'react'
import { usePathname } from 'next/navigation'
import { startTags, track } from '@hanzo/event'
import { AnalyticsProvider, useAnalytics, usePageview } from '@hanzo/event/react'
/** The ONE Hanzo Cloud telemetry front door — POST api.hanzo.ai/v1/event. Cloud
* fans the one batched stream out to the web (analytics), product (insights) and
* error (sentry) lenses; the client never sends the org — Cloud resolves the
* tenant server-side from the publishable ingest key. */
const HOST = 'https://api.hanzo.ai'
/** The sites one visit can cross; GA4 keeps it one session across them. */
const DOMAINS = ['hanzo.industries', 'hanzo.ai', 'pay.hanzo.ai', 'cal.hanzo.ai']
/** The funnel moment a click to one of these hosts is (@hanzo/events names). */
const INTENT: Record<string, string> = {
'cal.hanzo.ai': 'sales_contacted',
}
/**
* ONE analytics client. `@hanzo/ui` depends on `@hanzogui/telemetry`, whose
* `track` builds an ambient, unkeyed client the first time a shared component
* reports an interaction, with pageviews on: a second `$pageview` per load,
* answered 401. Module scope, because that client is built on first use during
* render.
*/
if (typeof globalThis !== 'undefined') {
const g = globalThis as { __HANZO_TELEMETRY__?: { enabled?: boolean } }
g.__HANZO_TELEMETRY__ = { ...g.__HANZO_TELEMETRY__, enabled: false }
}
function Pageview() {
usePageview(usePathname())
return null
}
/**
* The ad tags and the cross-site journey. `startTags` fetches this site's tag
* set and loads only what consent allows, again on every consent change; no
* tag id lives in this repo. A link to another Hanzo host carries the visitor
* (@hanzo/event `link`), and a click to the sales calendar is a funnel moment.
*/
function Tags() {
const stream = useAnalytics()
const live = useRef(stream)
live.current = stream
useEffect(() => startTags({ host: window.location.hostname, domains: DOMAINS }), [])
useEffect(() => {
const carry = (e: Event) => {
const a = (e.target as Element | null)?.closest?.('a[href]') as HTMLAnchorElement | null
if (a) a.href = live.current.link(a.href)
}
const click = (e: MouseEvent) => {
const a = (e.target as Element | null)?.closest?.('a[href]') as HTMLAnchorElement | null
const name = a && INTENT[new URL(a.href, window.location.href).host]
if (name) track(live.current, name, { link_url: a.href, page_path: window.location.pathname })
}
const on = ['pointerdown', 'click', 'contextmenu', 'keydown'] as const
on.forEach((t) => document.addEventListener(t, carry, { capture: true }))
document.addEventListener('click', click, { capture: true })
return () => {
on.forEach((t) => document.removeEventListener(t, carry, { capture: true }))
document.removeEventListener('click', click, { capture: true })
}
}, [])
return null
}
/**
* Telemetry root. The provider owns the ONE @hanzo/event client: it fires the
* first pageview, registers auto error capture (window.onerror +
* unhandledrejection) and flushes on unload; <Pageview> adds one per client-side
* route change. The stream is first-party analytics and runs for every visitor,
* before and whatever the consent choice (@hanzo/event 0.3.66); consent gates
* the third-party tags alone (startTags, in <Tags>). The key is the one SITE_KEY
* gives hanzo.industries, project hanzo-industries.
*/
export function Analytics({ children }: { children: React.ReactNode }) {
return (
<AnalyticsProvider config={{ product: 'hanzo-industries', host: HOST }}>
<Pageview />
<Tags />
{children}
</AnalyticsProvider>
)
}