mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +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.
86 lines
3.1 KiB
TypeScript
86 lines
3.1 KiB
TypeScript
import { CardGrid, Cta, Page, PageHero, Prose, Section } from '@/components/marketing/page-kit'
|
|
import { TRY } from '@/components/home/nav-data'
|
|
import { Gpus } from './Gpus'
|
|
import { INDEX, KINDS, PRODUCTS, href, ofKind, type Solution as Entry } from '@/lib/data/solutions'
|
|
import { Action } from '@hanzo/ui/marketing'
|
|
import { XStack } from '@/components/ui'
|
|
|
|
/** A row that wraps: the two doors, or a set of links. */
|
|
const ROW = { flexWrap: 'wrap', gap: 12 } as const
|
|
|
|
const SALES = '/contact-sales'
|
|
|
|
/** Try Hanzo, filled; Contact sales beside it. The two doors every solution ends on. */
|
|
export function Doors() {
|
|
return (
|
|
<XStack {...ROW}>
|
|
<Action fill href={TRY.href}>
|
|
{TRY.label}
|
|
</Action>
|
|
<Action href={SALES}>
|
|
Contact sales
|
|
</Action>
|
|
</XStack>
|
|
)
|
|
}
|
|
|
|
const label = (kind: Entry['kind']) => KINDS.find((k) => k.id === kind)!.label
|
|
|
|
/**
|
|
* One solution, and the one template every /solutions/<slug> page is: who it is
|
|
* for and what it promises, what it does for you, the products under it (each a
|
|
* link), what is true about security where the buyer asks, and its siblings.
|
|
* The entry is data in lib/data/solutions.ts; this file owns only the shape.
|
|
*/
|
|
export function Solution({ entry }: { entry: Entry }) {
|
|
const siblings = ofKind(entry.kind).filter((s) => s.slug !== entry.slug)
|
|
return (
|
|
<Page>
|
|
<PageHero eyebrow={`Solutions ยท ${label(entry.kind)}`} title={entry.title} lede={entry.lede}>
|
|
<Doors />
|
|
</PageHero>
|
|
<Section title="What it does for you">
|
|
<CardGrid items={entry.work.map((w) => ({ title: w.title, description: w.body }))} />
|
|
</Section>
|
|
{entry.rates ? (
|
|
<Section title="Capacity" lede="On-demand GPUs today, per hour. Reserved capacity is priced in the agreement.">
|
|
<Prose>
|
|
<Gpus />
|
|
</Prose>
|
|
</Section>
|
|
) : null}
|
|
<Section title="What it runs on" lede="The products underneath, on one account, one key and one bill.">
|
|
<CardGrid
|
|
columns={3}
|
|
items={entry.products.map((id) => {
|
|
const p = PRODUCTS[id]
|
|
return { title: p.label, description: p.line, href: p.href }
|
|
})}
|
|
/>
|
|
</Section>
|
|
{entry.trust ? (
|
|
<Section title="Security and compliance">
|
|
<CardGrid items={entry.trust.map((t) => ({ title: t.title, description: t.body }))} />
|
|
<XStack {...ROW} mt={24}>
|
|
<Cta href="/trust">How the controls work</Cta>
|
|
<Cta href="/legal">Terms and data processing</Cta>
|
|
</XStack>
|
|
</Section>
|
|
) : null}
|
|
<Section title={siblings.length ? `More ${label(entry.kind).toLowerCase()}` : 'More solutions'}>
|
|
<XStack {...ROW}>
|
|
{siblings.map((s) => (
|
|
<Cta key={s.slug} href={href(s)}>
|
|
{s.name}
|
|
</Cta>
|
|
))}
|
|
<Cta href={INDEX}>All solutions</Cta>
|
|
</XStack>
|
|
</Section>
|
|
<Section title="Put it to work" lede="Start in the builder today, or talk to us about your organization.">
|
|
<Doors />
|
|
</Section>
|
|
</Page>
|
|
)
|
|
}
|