Files

85 lines
3.7 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 { PlansJsonLd } from '@/components/pricing/PlansJsonLd'
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} />
<PlansJsonLd />
{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>
)
}