mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-28 07:32:52 +00:00
The root layout handed every page the plan catalog (lib/rooms.ts configure with lib/data/plans.json), and /pricing, /calculator, /customers and the benchmark pages bundled the 202 KB price list. lib/api is now the one reader: plans() GET /v1/billing/plans, pricing() /v1/pricing, summary(), served() /v1/models, templates(), blueprints() /v1/blueprint/sbom, one promise a path a page load, each drawn as loading, ready or "api.hanzo.ai is unreachable" (components/api) and never an older number. plans.json, pricing.json, models.json, templates.json, blueprints.json and sync-pricing, sync-models, sync-templates, sync-blueprints are gone. What a static export must know before anyone asks, which model pages exist and their titles, is read by the build itself (lib/api/build, Next's fetch cache keyed by the build's stamp: one answer per build) and renders no rate. lib/moved reads the retired model pages against the export, and the 153 pages the old snapshot kept for models the gateway dropped are in retired.json. Kai's price beside Jev's, Enso's tier rates, a model page's rate and the GPU table read the price list in the browser; the site's plan math no longer imports @hanzo/rooms/lib/plans, whose bundled @hanzo/plans catalog rode the home, /pricing and the picker. Home first load 452.7 -> 437.4 KB gzip (budget 440); pages over perf.budget.json 32 -> 9, the rest over for settings code, connectors and the commerce catalog. The gates serve invented answers (e2e/gates/api) and block them; e2e/gates/read.spec.ts holds that no script carries a plan, price or model table.
65 lines
2.2 KiB
TypeScript
65 lines
2.2 KiB
TypeScript
/**
|
|
* The reads in `lib/api/read.ts`, as hooks: what a client component draws while
|
|
* a document is on its way, once it is here, and when the backend did not answer.
|
|
*
|
|
* Every section renders `loading` first, the state the export prerendered, so
|
|
* hydration matches and no number reaches the HTML. A section mounted after the
|
|
* answer arrived shares the settled promise and draws it on the next tick.
|
|
*/
|
|
import { useEffect, useState } from 'react'
|
|
import {
|
|
blueprints,
|
|
plans,
|
|
pricing,
|
|
served,
|
|
summary,
|
|
templates,
|
|
type Blueprints,
|
|
type Plan,
|
|
type Pricing,
|
|
type Served,
|
|
type Summary,
|
|
type Template,
|
|
type Unreachable,
|
|
} from './read'
|
|
|
|
/** Where a read stands: on its way, here, or failed. */
|
|
export type Reading<T> = { state: 'loading' } | { state: 'ready'; data: T } | { state: 'unreachable'; error: Unreachable }
|
|
|
|
/** One document, read once per page load and shared by every component that asks. */
|
|
export function useReading<T>(load: () => Promise<T>): Reading<T> {
|
|
const [now, setNow] = useState<Reading<T>>({ state: 'loading' })
|
|
useEffect(() => {
|
|
let open = true
|
|
load().then(
|
|
(data) => open && setNow({ state: 'ready', data }),
|
|
(error: Unreachable) => open && setNow({ state: 'unreachable', error }),
|
|
)
|
|
return () => {
|
|
open = false
|
|
}
|
|
}, [load])
|
|
return now
|
|
}
|
|
|
|
/** The plans commerce lists: GET /v1/billing/plans. */
|
|
export const usePlans = (): Reading<Plan[]> => useReading(plans)
|
|
|
|
/** The price list: GET /v1/pricing. */
|
|
export const usePricing = (): Reading<Pricing> => useReading(pricing)
|
|
|
|
/** The price list's counts alone: GET /v1/pricing/summary. */
|
|
export const useSummary = (): Reading<Summary> => useReading(summary)
|
|
|
|
/** The models the gateway answers for: GET /v1/models. */
|
|
export const useServed = (): Reading<Served[]> => useReading(served)
|
|
|
|
/** The templates a new app can start from: GET /v1/templates. */
|
|
export const useTemplates = (): Reading<Template[]> => useReading(templates)
|
|
|
|
/** What each open-source stack costs to run: GET /v1/blueprint/sbom. */
|
|
export const useBlueprints = (): Reading<Blueprints> => useReading(blueprints)
|
|
|
|
/** The data, once it is here; null while loading or failed. */
|
|
export const ready = <T,>(r: Reading<T>): T | null => (r.state === 'ready' ? r.data : null)
|