mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
/pricing opens on the plans; what a metered bill is made of and every model's rate are drawn only while API is the tab on screen. Plans tells the page its tab (onTab). The gates read the rate card, Kai's card and the unreachable state on /pricing#api, and hold the sections off every other tab. Claude-Session: https://claude.ai/code/session_01BLLMyBFSvAfCt7ZGqYuiEz
83 lines
3.6 KiB
TypeScript
83 lines
3.6 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* What Hanzo costs.
|
|
*
|
|
* THE PLANS FIRST, THE METER ON THE API TAB. A reader buys a plan — Free, a
|
|
* personal rung, Team by the seat, or Enterprise by conversation — so the page
|
|
* opens on the plans as cards (Plans) and the questions. What a metered bill is
|
|
* made of (Tariff) and every model's rate belong to the API tab, and are drawn
|
|
* only while it is the tab on screen.
|
|
*
|
|
* NOTHING HERE IS TYPED, AND NOTHING IS BAKED. The plans come from commerce's
|
|
* plan authority (GET /v1/billing/plans), and the rate card, the model rates and
|
|
* the tool rates from the price list (GET /v1/pricing), each read when the page
|
|
* opens (lib/api) — so a price a SuperAdmin edits at admin.hanzo.ai is the price
|
|
* the next reader sees, and a backend that does not answer is said to be
|
|
* unreachable rather than stood in for. scripts/audit-price-literals.mjs fails
|
|
* the build on a typed copy: a number a reader sees is a number somebody could
|
|
* be charged.
|
|
*/
|
|
|
|
import dynamic from 'next/dynamic'
|
|
import { Suspense, useState } from 'react'
|
|
import { Box } from '@hanzo/ui'
|
|
import { Line } from '@/components/type'
|
|
import Plans, { type Audience } from '@/components/pricing/Plans'
|
|
import Tariff from '@/components/pricing/Tariff'
|
|
import { Wide, Sub } from '@/components/pricing/kit'
|
|
|
|
// Below the plans and the card, so not on the path to a usable page: the model
|
|
// table and the questions. Both are still prerendered (the table as its loading
|
|
// line, the questions whole), so a crawler meets them in the HTML; their scripts
|
|
// load at low priority, and each sits in its own Suspense boundary so the rest
|
|
// of the page hydrates without waiting for them.
|
|
const APIPricing = dynamic(() => import('@/components/pricing/APIPricing'))
|
|
const PricingFAQ = dynamic(() => import('@/components/pricing/PricingFAQ'))
|
|
|
|
// The view is stated by Plans, once per tab a reader looks at, with the plans it
|
|
// shows (lib/analytics/tags.ts track).
|
|
export default function PricingPage() {
|
|
// The tab Plans has on screen: the meter and the model rates are the API tab's.
|
|
const [tab, setTab] = useState<Audience | null>(null)
|
|
return (
|
|
/* A BLOCK, not a stack. gui's YStack is a flex column, and a flex column
|
|
shrinks its children to the height it was given — so a section taller than
|
|
the viewport gets squashed rather than scrolled, which cut the plan cards
|
|
off mid-list and put the CTA over the bullets. Nothing here needs flex. */
|
|
<Box minH="100vh" pt={36} pb={48} px="$4" $sm={{ pt: 48 }}>
|
|
<Plans onTab={setTab} />
|
|
|
|
{tab === 'api' ? (
|
|
<>
|
|
<Tariff />
|
|
<Wide marginTop={96}>
|
|
<Sub
|
|
title="Every model, at the price on your bill"
|
|
lede="Model inference is metered per token in five disjoint tiers, at the tariff for the completion window the request asked for."
|
|
/>
|
|
<Suspense>
|
|
<APIPricing />
|
|
</Suspense>
|
|
</Wide>
|
|
</>
|
|
) : null}
|
|
|
|
<Suspense>
|
|
<PricingFAQ />
|
|
</Suspense>
|
|
|
|
<Wide maxWidth={768} marginTop={48} marginBottom={32}>
|
|
<Line render="p" size="base" tone="muted" align="center">
|
|
Need help sizing seats or configuring the right plan?{' '}
|
|
<a href="/buying-specialist">Talk to our AI Buying Specialist →</a>
|
|
</Line>
|
|
<Line render="p" size="sm" tone="muted" align="center" marginTop={8}>
|
|
Need dedicated VPC clusters, custom fine-tuning, or high-volume enterprise SLAs?{' '}
|
|
<a href="/enterprise">Contact our engineering team →</a>
|
|
</Line>
|
|
</Wide>
|
|
</Box>
|
|
)
|
|
}
|