mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-24 20:09:02 +00:00
feat(analytics): docs send through @hanzo/event and the tag manager; the GA4, Pixel, Insights and analytics scripts leave every layout; the one consent banner
This commit is contained in:
@@ -32,9 +32,6 @@ export const viewport: Viewport = {
|
||||
export default function RootLayout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<html lang="en" className={`${Zen.variable} ${ZenMono.variable}`} suppressHydrationWarning>
|
||||
<head>
|
||||
<script defer src="https://analytics.hanzo.ai/script.js" data-website-id="89cb4513-3384-491f-8eba-c393d51a16ef" data-do-not-track="true" data-exclude-search="true" />
|
||||
</head>
|
||||
<Body>
|
||||
<NextProvider>
|
||||
<TreeContextProvider tree={source.getPageTree()}>
|
||||
|
||||
@@ -41,7 +41,6 @@ export default function RootLayout({ children }: { children: ReactNode }) {
|
||||
<body className="antialiased">{children}
|
||||
<Footer />
|
||||
<Analytics product="flow" /></body>
|
||||
<script defer src="https://analytics.hanzo.ai/script.js" data-website-id="811b5039-8438-453a-9c8a-e1f4cff05353" data-do-not-track="true" data-exclude-search="true" />
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@ export default function RootLayout({ children }: { children: ReactNode }) {
|
||||
<body className="antialiased">{children}
|
||||
<Footer />
|
||||
<Analytics product="team" /></body>
|
||||
<script defer src="https://analytics.hanzo.ai/script.js" data-website-id="9e8eaacf-fb42-4b3b-a95f-11c58d50aec0" data-do-not-track="true" data-exclude-search="true" />
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ export default function RootLayout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<html lang="en" className={`${Zen.variable} ${ZenMono.variable}`} suppressHydrationWarning>
|
||||
<head>
|
||||
<script defer src="https://analytics.hanzo.ai/script.js" data-website-id="PLACEHOLDER" />
|
||||
</head>
|
||||
<Body>
|
||||
<NextProvider>
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ spec:
|
||||
# enough for Scalar to execute.
|
||||
env:
|
||||
- name: HANZO_STATIC_CSP
|
||||
value: "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://api.hanzo.ai https://analytics.hanzo.ai; worker-src 'self' blob:; child-src 'self' blob:; manifest-src 'self'"
|
||||
value: "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: https://www.googletagmanager.com https://connect.facebook.net https://snap.licdn.com https://static.ads-twitter.com https://analytics.tiktok.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://api.hanzo.ai https://www.googletagmanager.com https://*.google-analytics.com https://*.analytics.google.com https://www.facebook.com https://connect.facebook.net https://px.ads.linkedin.com https://analytics.twitter.com https://analytics.tiktok.com; worker-src 'self' blob:; child-src 'self' blob:; manifest-src 'self'"
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 3000
|
||||
|
||||
@@ -23,7 +23,9 @@
|
||||
"types:check": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hanzo/event": "catalog:"
|
||||
"@hanzo/event": "catalog:",
|
||||
"@hanzo/gui": "catalog:",
|
||||
"@hanzo/ui": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "catalog:",
|
||||
|
||||
@@ -1,67 +1,72 @@
|
||||
'use client';
|
||||
|
||||
import { keyForPage } from '@hanzo/event';
|
||||
import { AnalyticsProvider, usePageview } from '@hanzo/event/react';
|
||||
|
||||
/** The client, for a page that sends an event of its own. */
|
||||
export { useAnalytics } from '@hanzo/event/react';
|
||||
import { useEffect, type ReactNode } from 'react';
|
||||
import { startTags } from '@hanzo/event';
|
||||
import { AnalyticsProvider, useConsent, usePageview } from '@hanzo/event/react';
|
||||
import { GuiProvider } from '@hanzo/gui';
|
||||
import guiConfig from '@hanzo/ui/gui-config';
|
||||
import { Consent } from '@hanzo/ui/consent';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import type { ReactNode } from '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. */
|
||||
export { useAnalytics } from '@hanzo/event/react';
|
||||
export { EVENTS } from '@hanzo/event';
|
||||
|
||||
/** The ONE Hanzo Cloud telemetry front door — POST api.hanzo.ai/v1/event. */
|
||||
const HOST = 'https://api.hanzo.ai';
|
||||
|
||||
/** Publishable ingest key: the org's, from the keyring @hanzo/event carries,
|
||||
* resolved from the host the page is served on — docs.hanzo.ai is the hanzo
|
||||
* org's. A docs site is read-only and logged out, so no bearer can ride the
|
||||
* request — this write-only, bundle-safe key IS how anonymous pageviews and
|
||||
* errors resolve to an org. A host no brand claims gets none and reports
|
||||
* nothing, rather than filing its visitors under another org. */
|
||||
const INGEST_KEY = keyForPage();
|
||||
|
||||
/** Honor an explicit browser opt-out (Global Privacy Control, then legacy DNT).
|
||||
* SSR (no navigator) defaults to consented; the browser reads the real signal on
|
||||
* hydration. Opting out suppresses pageviews AND errors — the `enabled` gate is
|
||||
* the whole consent story. */
|
||||
function consented(): boolean {
|
||||
if (typeof navigator === 'undefined') return true;
|
||||
const nav = navigator as Navigator & {
|
||||
globalPrivacyControl?: boolean;
|
||||
doNotTrack?: string | null;
|
||||
};
|
||||
if (nav.globalPrivacyControl === true) return false;
|
||||
const dnt = nav.doNotTrack;
|
||||
return dnt !== '1' && dnt !== 'yes';
|
||||
}
|
||||
/** The sites one visit can cross; GA4 keeps it one session across them. */
|
||||
const DOMAINS = [
|
||||
'hanzo.ai',
|
||||
'docs.hanzo.ai',
|
||||
'hanzo.id',
|
||||
'hanzo.build',
|
||||
'platform.hanzo.ai',
|
||||
'hanzo.app',
|
||||
'hanzo.chat',
|
||||
'pay.hanzo.ai',
|
||||
];
|
||||
|
||||
function Pageview() {
|
||||
usePageview(usePathname());
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The ad tags. `startTags` fetches this site's tag set from cloud (GA4 and the
|
||||
* Pixel are configuration there, never ids in this repo) and the consent rule
|
||||
* for this visitor's region, and loads only what consent allows.
|
||||
*/
|
||||
function Tags() {
|
||||
useEffect(() => startTags({ domains: [window.location.hostname, ...DOMAINS] }), []);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Telemetry for a Hanzo docs site — mount once in the root layout, around the
|
||||
* app when a page sends events of its own (`useAnalytics` from @hanzo/event/react):
|
||||
*
|
||||
* <Analytics product="zen-docs">{app}</Analytics>
|
||||
* <Analytics product="docs">{app}</Analytics>
|
||||
*
|
||||
* The provider owns the ONE @hanzo/event client: it fires the first pageview,
|
||||
* registers auto error capture (window.onerror + unhandledrejection), and flushes
|
||||
* the batch on unload; <Pageview> adds one pageview per client-side route change.
|
||||
* Errors are just events on the same stream, so there is no separate DSN. Nothing
|
||||
* here is docs-specific except the default host and consent gate — `product` is
|
||||
* the only knob, because Cloud derives everything else server-side.
|
||||
* Errors are events on the same stream (`type: 'error'`). The stream runs while
|
||||
* the visitor allows analytics: cloud serves the rule for their region, and
|
||||
* <Consent> is the one banner that asks where the region requires it and offers
|
||||
* "Cookie settings" everywhere. Its key is the site's own project key, found by host.
|
||||
* `product` is the only knob.
|
||||
*/
|
||||
export function Analytics({ product, children }: { product: string; children?: ReactNode }) {
|
||||
export function Analytics({ product = 'docs', children }: { product?: string; children?: ReactNode }) {
|
||||
const consent = useConsent();
|
||||
return (
|
||||
<AnalyticsProvider
|
||||
config={{ product, host: HOST, ingestKey: INGEST_KEY, enabled: consented() }}
|
||||
>
|
||||
<AnalyticsProvider config={{ product, host: HOST, enabled: consent.analytics }}>
|
||||
<Tags />
|
||||
<Pageview />
|
||||
{children}
|
||||
<GuiProvider config={guiConfig as never} defaultTheme="dark">
|
||||
<Consent />
|
||||
</GuiProvider>
|
||||
</AnalyticsProvider>
|
||||
);
|
||||
}
|
||||
export default Analytics;
|
||||
|
||||
Generated
+8
-2
@@ -4370,6 +4370,12 @@ importers:
|
||||
'@hanzo/event':
|
||||
specifier: 'catalog:'
|
||||
version: 0.3.50([email protected])
|
||||
'@hanzo/gui':
|
||||
specifier: 'catalog:'
|
||||
version: 8.3.5([email protected]([email protected]))([email protected](@babel/[email protected]([email protected]))(@react-native/[email protected](@babel/[email protected]([email protected]))([email protected]))(@types/[email protected])([email protected])([email protected]))([email protected])
|
||||
'@hanzo/ui':
|
||||
specifier: 'catalog:'
|
||||
version: 8.27.35(ffc64b3d71a990f72cef419463133daf)
|
||||
react:
|
||||
specifier: '>=19.2.8'
|
||||
version: 19.2.8
|
||||
@@ -24297,7 +24303,7 @@ snapshots:
|
||||
|
||||
'@hanzo/[email protected]([email protected])':
|
||||
dependencies:
|
||||
'@hanzo/event': 0.3.41([email protected])
|
||||
'@hanzo/event': 0.3.50([email protected])
|
||||
optionalDependencies:
|
||||
react: 19.2.8
|
||||
|
||||
@@ -26527,7 +26533,7 @@ snapshots:
|
||||
|
||||
'@hanzogui/[email protected]([email protected])':
|
||||
dependencies:
|
||||
'@hanzo/event': 0.3.41([email protected])
|
||||
'@hanzo/event': 0.3.50([email protected])
|
||||
'@hanzo/observe': 0.1.11([email protected])
|
||||
react: 19.2.8
|
||||
|
||||
|
||||
Reference in New Issue
Block a user