mirror of
https://github.com/hanzo-apps/link-onepage.git
synced 2026-10-30 19:12:05 +00:00
Real Hanzo-stack app forked from hanzo-starter: Vite + React 19 + @hanzo/gui + @hanzo/iam (PKCE) + @hanzo/base. Three views over two org-scoped Base collections (profile, links): the public link page (centered, mobile-first, monochrome big buttons), an editor (profile + link CRUD/reorder), and analytics-lite (real per-link tap counts). Provider stack, vite.config, auth, and deploy contract are identical to the starter.
3.7 KiB
3.7 KiB
link-onepage — agent notes
A link-in-bio page you own, built on the canonical Hanzo app stack: Vite +
React 19 + @hanzo/gui (UI) + @hanzo/iam (auth) + @hanzo/base (data). Forked
from hanzo-apps/hanzo-starter — provider stack, vite.config.ts, auth, and the
deploy contract are IDENTICAL to the starter; only the app on top differs. Keep
it REAL — every surface builds and runs, no fabricated UI, no fabricated metrics.
The app
- One page, three views (
src/views/):page.tsx— the public link page from your Base rows (profile + links as big buttons). Tapping a link opens it and bumpslinks.clicks.editor.tsx— profile form (name · bio · avatar) + link CRUD and reorder.analytics.tsx— analytics-lite: links ranked by the realclickscounter.
signed-out.tsxis the landing: it renders the samecomponents/page-view.tsxwith demo content (DEMO_PROFILE/DEMO_LINKSinsrc/lib/page.ts) so the hero is a live preview, plus the PKCE sign-in button.shell.tsxis the signed-in shell: brand bar + a three-tab switch (no router — tab state in memory, same "one static SPA" stance as the auth gate).components/page-view.tsxis the ONE presentational surface shared by the signed-out preview and the signed-in Page tab. DRY: the page is defined once.
Data contract
schema.sqlprovisions two org-scoped Base collections;src/lib/page.tsmirrors them asProfile(name · bio · avatar) andLink(label · url · sort · clicks) and is the single source for collection names, demo content, and theordered/host/monogram/hrefhelpers. Keep them in lockstep.sort(notorder) is the display-order column —orderis a reserved SQL word. Links are ordered client-side byordered()for a deterministic result.clicksis a real per-link counter the Page tab increments on tap; Analytics reads it. No estimates — a page with no taps shows zeros.
Gotchas (do not regress)
@hanzo/guiunder Vite needs three things invite.config.ts(unchanged from the starter): (1) aliasreact-native→react-native-web, (2)defineprocess.env.TAMAGUI_TARGET/NODE_ENV/__DEV__, (3)dedupereact/react-dom/react-native-web. No@hanzogui/vite-plugin.@hanzo/guiprops are Tamagui LONGHAND with this v5 config:alignItems/justifyContent/backgroundColor/padding/paddingHorizontal/borderRadius/textAlign/alignSelf— NOT theitems/justify/bg/p/rounded/text/selfshorthands. Shorthands pass at runtime but FAILtsc.Button/pressables useonPress;Input/TextAreausevalue/onChangeText.- PKCE storage is
localStorage(not sessionStorage) so the verifier/state survive the round-trip to hanzo.id. - Auth reads
import.meta.env.VITE_HANZO_CLIENT_ID(fallbackhanzo-app) insrc/env.ts— the one place env is read.
Deploy contract (Hanzo Cloud)
- Static SPA:
npm run build→dist/, served at<slug>.hanzo.app. No server. - On publish,
schema.sql→provisionBaseFromDDLcreates the collections (org-scoped, IAM-native). Runtime read/write is browser →VITE_HANZO_BASE_URLwith the IAM token. - IAM redirect registration is the one external requirement: the IAM client
must allow this origin's
/auth/callback(ahttps://*.hanzo.app/auth/callbackwildcard on the shared client, or a per-apphanzo-link-onepageclient).
Proven
tsc --noEmit clean · vite build → dist/ · CI (.github/workflows/ci.yml)
runs npm ci && npm run typecheck && npm run build — build-verification only,
NEVER a container image (Hanzo Cloud owns deploys; do not build images locally).