mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
85 lines
3.7 KiB
TypeScript
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>
|
|
)
|
|
}
|