The portal no longer starts @hanzo/event's tag manager. Once a stored or new choice allows Analytics, it asks cloud for the brand's consent policy with the brand's key and no credential, hands only the policy to @hanzo/event, and leaves the tags cloud lists beside it unloaded. The event client starts after the policy arrives and only if the choice still counts under it, so an Accept stored under an older policy, a refusal cloud saw, or no answer at all starts nothing and stores nothing, and the banner asks again. The page reloads only when the visitor withdraws Analytics after the client started, on the choice they store, never on a policy's arrival. The completed sign-up goes to the stream with the visitor's consent stamped on it, for cloud to forward server-side. The spec lists Google's and Meta's tags for every key and holds every identity host to loading none, and covers a newer policy, a refusal cloud saw and GPC over a stored Accept: one load, the form, nothing sent or kept. Co-authored-by: Hanzo Dev <[email protected]>
Retired — this is a stale copy of
hanzoai/id.Its 4 unique commits are carried into
hanzoai/idfirst — merged onto the default branch or pushed there ascarry/*refs — so nothing here is lost. This copy has no push mirror, so those commits had reached nothing.It also declared
ghcr.io/hanzoai/id, the taghanzoai/idowns, so a push here could have published over it. That declaration is removed.
@hanzo/id
White-label login + identity verification portal. One Vite SPA, four hosts
(hanzo.id, lux.id, zoo.id, pars.id), per-tenant brand resolved from
the request hostname at runtime.
Layout
apps/
web/ Vite + React 19 + @hanzo/gui — the actual SPA
k8s/ Deployment + Service + Ingress (4 hosts, 4 TLS secrets)
pkgs/
shared/ @hanzo/id-shared — TenantConfig + brand resolver
auth/ @hanzo/id-auth — composable login/signup/OTP flows
on top of @hanzo/iam SDK
idv/ @hanzo/id-idv — pluggable identity verification
(Persona, Onfido, Veriff, stub)
legacy-nextjs/ Frozen — predecessor Next.js implementation. Kept
for reference until v0.1.0 ships to production.
Local dev
pnpm install
pnpm dev # http://localhost:5173 (defaults to hanzo brand)
To preview a different brand locally, edit /etc/hosts:
127.0.0.1 lux.id zoo.id pars.id
then visit http://lux.id:5173.
Build
pnpm build # builds apps/web -> dist/
docker build -t ghcr.io/hanzoai/id:0.1.0 .
Adding a brand
- Publish or workspace-link the new per-org brand pkg (must ship
brand.jsonat the package root and match theBrandContractshape inpkgs/shared/src/types.ts). - Add a
DEFAULT_TENANTSentry inpkgs/shared/src/tenant.tsOR put the override in the runtime catalog (IAM_TENANT_CONFIG_JSONenv) so no rebuild is needed. - Add the hostname to
apps/web/vite.config.ts::BRAND_PACKAGES(lets dev + build serve/brand/<pkg>/brand.json). - Add the hostname + TLS secret to
apps/web/k8s/ingress.yaml. - DNS: CNAME or A record → cluster ingress IP.
That's it — no per-brand Worker, no per-brand image, no per-brand deployment. One binary, four brands.
Plugging an IDV provider
import { registerProvider, createPersonaProvider } from '@hanzo/id-idv'
registerProvider(createPersonaProvider({
templateId: import.meta.env.VITE_PERSONA_TEMPLATE_ID,
apiKey: import.meta.env.VITE_PERSONA_API_KEY,
environment: 'production',
}))
The portal stays unchanged — switching providers is a single registration call at boot.