Files
zeekay 9c4fa265ae Team Roster — a searchable team directory on Hanzo
Vite + React 19 + @hanzo/gui + @hanzo/iam + @hanzo/base, forked from
hanzo-starter (identical provider stack, vite config, auth + deploy contract).

- one org-scoped people(name,role,team,email,avatar,bio) Base collection
- three views: directory grid (avatar cards + quick search) / person profile / edit
- friendly light surface built entirely from @hanzo/gui primitives
- PKCE sign-in via hanzo.id; useQuery/useMutation carry the IAM bearer token
2026-07-21 13:27:02 -07:00

5.2 KiB

Roster

A searchable team directory — a real, buildable Hanzo app you fork on hanzo.app and deploy live on Hanzo Cloud.

Browse teammates as avatar cards, filter by team, search across every field, and open anyone for their role, contact, and bio. Add and edit people inline — each one is an org-scoped row in Hanzo Base.

  • UI — @hanzo/gui (the Hanzo design system) under Vite + React 19. A clean, friendly light surface built entirely from gui primitives — no second kit, no Tailwind.
  • Auth — @hanzo/iam, OAuth2 PKCE against hanzo.id. No local passwords — IAM owns every credential interaction.
  • Data — @hanzo/base, the IAM-native, org-scoped data plane. people is a real Base collection.

Three views: the directory grid, a person profile, and the edit form (add · update · delete).

Stack (pinned)

Package Version
react / react-dom ^19.2.4
@hanzo/gui + @hanzogui/config 7.3.0
@hanzo/iam ^0.13.1
@hanzo/base ^0.2.1
vite ^6 (@vitejs/plugin-react)
typescript 5.9.3

Run it

npm install
npm run dev        # http://localhost:5173
npm run build      # tsc --noEmit && vite build  ->  dist/
npm run preview    # serve the production build (SPA fallback on)

Out of the box it runs against live Hanzo (hanzo.id + api.hanzo.ai) — no config needed to see the sign-in flow. Copy .env.example to .env to point at a different environment.

Environment contract

Only VITE_-prefixed vars reach the browser (this is a static SPA — there is no server). Defaults in parentheses.

Var Purpose
VITE_HANZO_IAM_URL (https://hanzo.id) OIDC issuer.
VITE_HANZO_CLIENT_ID (hanzo-app) IAM application (<org>-<app>). Its redirect-URI list must allow this deploy's /auth/callback — see Ambient IAM.
VITE_HANZO_REDIRECT_URI (${origin}/auth/callback) PKCE redirect.
VITE_HANZO_BASE_URL (https://api.hanzo.ai) Browser-reachable Hanzo Base data plane. Deploy injects the provisioned URL.
VITE_HANZO_API_URL / VITE_HANZO_API_KEY Optional — only if the app calls the Hanzo LLM gateway. Unused here; never commit a real key.

How auth works — ambient IAM

login() starts an OAuth2 PKCE S256 redirect to hanzo.id; hanzo.id returns to /auth/callback, where handleCallback() exchanges the code for tokens (stored in localStorage, refresh-aware via offline_access). Every deployed app is a static site at <slug>.hanzo.app; there is no server token — the SPA authenticates the user in the browser and carries the resulting IAM JWT to Base. "Ambient" means the app just reads the signed-in user via that token.

The one deploy requirement: the IAM client (VITE_HANZO_CLIENT_ID) must list this origin's /auth/callback as an allowed redirect URI. Register a https://*.hanzo.app/auth/callback wildcard on the shared client so every forked app works, or register a dedicated hanzo-team-roster client to scope it.

How data works — Base from schema.sql

schema.sql is the app's databaseSchema (SQL DDL). On publish, Hanzo Cloud translates the people CREATE TABLE into a Hanzo Base collection (provisionBaseFromDDL, additive + idempotent). Base manages id/created/updated/owner/org, stamps owner+org from the verified IAM principal, and scopes every row to the caller's org — a teammate in your org sees the row; other orgs cannot. At runtime src/views/directory.tsx, profile.tsx, and edit.tsx read/write that collection through @hanzo/base/react (useQuery/useMutation) carrying the IAM token.

Deploy — Hanzo Cloud

hanzo.yml declares a static build (npm run build → dist/, served at <slug>.hanzo.app) plus the Base schema to provision and the env to inject. Do not build a container image locally — Hanzo Cloud owns builds and deploys. CI here only proves the template compiles green.

Layout

src/
  main.tsx          entry
  providers.tsx     GuiProvider -> IamProvider -> BaseProvider(client=IAM-token)
  app.tsx           route (/auth/callback) + auth gate (light Theme)
  gui.config.ts     createGui(defaultConfig from @hanzogui/config/v5)
  iam.config.ts     IAM PKCE config
  env.ts            the VITE_ env contract, one place
  lib/base.ts       BaseClient carrying the IAM bearer token
  lib/roster.ts     Person type + directory helpers + the light UI palette
  auth/callback.tsx PKCE return leg
  views/
    signed-out.tsx  landing with an illustrative directory preview
    home.tsx        signed-in shell — one Route (list · profile · edit)
    directory.tsx   the searchable avatar-card grid
    profile.tsx     a person's profile
    edit.tsx        add / edit / delete a person
    kit.tsx         the light-surface atoms (avatar · card · inputs · buttons)
    brand.tsx       the Roster mark
schema.sql          databaseSchema -> the `people` Base collection on publish
hanzo.yml           Hanzo Cloud build/deploy manifest