mirror of
https://github.com/hanzoai/billing.git
synced 2026-10-30 18:46:54 +00:00
Self-serve top-up could not complete for anyone. Three faults, one path. The card field never appeared. `useSquareCard` kept liveness in a ref shared across effect runs, so a cleanup could flip it false and the attach still in flight from the CURRENT run would then read "unmounted" and destroy the card it had just successfully attached. Measured on the live page: the field attaches at 700ms and is gone by 1200ms, leaving no spinner, no error and no card — and Pay latched disabled forever, because ready never went true. Liveness now belongs to the run that owns it. What made the effect run three times was `providers`, which rendered `children` 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 and every billing fetch ran twice. The wallet stack is half a megabyte and only the crypto UI needs it, so the decision is made once now and children never move. And the page did not know who was paying. It read a bearer token from `?token=` or a `hanzo-auth-token` cookie — neither of which anything sets. The session is in localStorage, where `getAccessToken` reads it, so every top-up opened from our own Add-credit links carried no token and died on submit with "Authentication required". The subject falls back the same way, to the org slug commerce keys billing on, so the money lands in the account whose balance the rest of the app shows. A card on file is no longer offered here: POST /v1/billing/topup answers 404 to an authenticated caller on both the gateway and commerce, while the nonce path beside it answers 400 "sourceId is required". The option could only fail, and it was selected BY DEFAULT — so restoring the token would have made failing the default. Two colours were wrong in the light theme, both because they were spelled literally. Pay was `#ffffff` on `var(--color-bg)`: white on white, the primary action on the money page, invisible. It takes the brand pair, which inverts. The card ink was `#fafafa`, and Square paints its fields on a white surface it will not let us repaint — `.input-container` backgroundColor is rejected outright and fails the whole attach — so that was ghost text in the DEFAULT dark theme too, not only in light. Square's own colours are legible under both. `square-card-form` carried a second copy of the load/attach/retry/destroy dance, with the same liveness bug and the same container id as the top-up page, aiming Square at whichever box mounted first. It uses the hook and its own id. Co-authored-by: Hanzo Dev <[email protected]>
24 lines
996 B
TypeScript
24 lines
996 B
TypeScript
'use client'
|
|
|
|
/**
|
|
* The WalletConnect project id, and nothing else.
|
|
*
|
|
* Its own module because two callers need this one fact and only one of them
|
|
* can afford the dependencies. `wallet-config` builds the RainbowKit config, so
|
|
* it pulls in wagmi and RainbowKit — half a megabyte. `providers` only needs to
|
|
* know whether a wallet stack should mount at all, and asking the config would
|
|
* drag that bundle into the first paint of every page.
|
|
*
|
|
* Resolved at CALL time so it picks up the value the serving layer injects from
|
|
* KMS as `window.__HANZO_CONFIG__.walletConnectProjectId`, whenever that lands
|
|
* relative to this module's load. Empty means crypto is off.
|
|
*/
|
|
export function projectId(): string {
|
|
const runtime =
|
|
typeof window !== 'undefined'
|
|
? (window as { __HANZO_CONFIG__?: { walletConnectProjectId?: string } })
|
|
.__HANZO_CONFIG__?.walletConnectProjectId
|
|
: undefined
|
|
return runtime || process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID || ''
|
|
}
|