mirror of
https://github.com/hanzoai/billing.git
synced 2026-10-30 18:46:54 +00:00
plan_clicked and checkout_started were sent by a bare beacon with no credential, and the door refuses a write it cannot file under a project — 401 ingest_key_required — so every one of them was discarded. The money funnel had no steps after the click because none of them ever landed. They go through @hanzo/event now, which carries the publishable key for an anonymous visitor and the buyer's own IAM token when there is one. That also supplies the anonymous id, a first-party cookie on hanzo.ai, so a visitor who read pricing on the marketing site is the same person here and the steps join. The plans grid emits pricing_viewed: it is a tab, not a page, so a pageview never saw it, and a buyer who arrives straight at billing looked like a plan click from nowhere.
42 lines
1.8 KiB
TypeScript
42 lines
1.8 KiB
TypeScript
'use client'
|
|
|
|
import * as React from 'react'
|
|
import { AnalyticsProvider } from '@hanzo/event/react'
|
|
|
|
import { analytics } from './analytics'
|
|
import { projectId } from './wallet'
|
|
|
|
// wagmi + RainbowKit are half a megabyte and only the crypto payment UI needs
|
|
// them, so they load lazily and only where a WalletConnect id is configured.
|
|
const WalletProviders = React.lazy(() => import('./wallet-providers'))
|
|
|
|
export function Providers({ children }: { children: React.ReactNode }) {
|
|
// Decided ONCE, on first render, and never revisited — because `children`
|
|
// must keep the same position in the tree for their whole life.
|
|
//
|
|
// This used to render them in three places in turn: bare while a `mounted`
|
|
// flag was false, then as a Suspense fallback while the wallet chunk loaded,
|
|
// then inside the provider. React remounts a subtree whose parent changes, so
|
|
// every page mounted three times. Two symptoms, one cause: every billing
|
|
// fetch ran twice, and the in-flight Square card attach was torn down and
|
|
// never retried, which left the top-up form permanently empty and the Pay
|
|
// button permanently disabled — self-service money-in could not complete.
|
|
const [wallet] = React.useState(() => projectId() !== '')
|
|
|
|
// Telemetry wraps BOTH paths and is never conditional, for the same reason the
|
|
// wallet decision is frozen: a provider that appears or disappears remounts
|
|
// everything under it. It also has to be outside the wallet branch to count a
|
|
// visitor who never loads the wallet chunk, which is most of them.
|
|
return (
|
|
<AnalyticsProvider client={analytics}>
|
|
{wallet ? (
|
|
<React.Suspense fallback={null}>
|
|
<WalletProviders>{children}</WalletProviders>
|
|
</React.Suspense>
|
|
) : (
|
|
children
|
|
)}
|
|
</AnalyticsProvider>
|
|
)
|
|
}
|