mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +00:00
130 lines
4.1 KiB
TypeScript
130 lines
4.1 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* What a bill is made of, what each part costs, and the window that decides the
|
|
* tariff — the whole of /pricing below the plans.
|
|
*
|
|
* Every figure here is READ, never written: the card is the price list's
|
|
* (`usePricing`, GET /v1/pricing), read when the page opens, and
|
|
* components/pricing/rates.ts is the one place a rate becomes money. The page
|
|
* states no rate it could not be charged for, and none at all when the backend
|
|
* does not answer.
|
|
*
|
|
* THE ORDER IS THE ARGUMENT. What a bill is made of comes before what the parts
|
|
* cost, because a reader who does not know a bill has four parts cannot read a
|
|
* table of eight rows. The window comes last because it is a lever rather than a
|
|
* price: it multiplies the first component and nothing else on the page.
|
|
*/
|
|
|
|
import { YStack, XStack } from '@hanzo/ui'
|
|
import { Grid } from '@hanzo/ui/grid'
|
|
import { Line } from '@/components/type'
|
|
import { Panel, Rates, Sub, Wide, Mark } from './kit'
|
|
import { priced, bySecond, type Card, type Part as Component } from './rates'
|
|
import { usePricing } from '@/lib/api/use'
|
|
import { Ready } from '@/components/api/state'
|
|
|
|
/** A component's card: what it meters, when it is priced, and what that means. */
|
|
function Part({ part }: { part: Component }) {
|
|
return (
|
|
<Panel pad="$5">
|
|
<Line render="h3" size="xl" weight="600" marginBottom="$1">
|
|
{part.title}
|
|
</Line>
|
|
<Line size="sm" tone="muted" marginBottom="$4">
|
|
{part.basis}
|
|
</Line>
|
|
<Line size="sm" tone="muted" flex={1}>
|
|
{part.note}
|
|
</Line>
|
|
{part.tiers ? (
|
|
<XStack flexWrap="wrap" gap="$2" mt="$4">
|
|
{part.tiers.map((t) => (
|
|
<Line key={t} size="xs" tone="muted" fontFamily="$mono">
|
|
{t}
|
|
</Line>
|
|
))}
|
|
</XStack>
|
|
) : null}
|
|
</Panel>
|
|
)
|
|
}
|
|
|
|
export default function Tariff() {
|
|
const reading = usePricing()
|
|
return (
|
|
<Wide render="section" marginTop={96}>
|
|
<Sub
|
|
title="How we bill"
|
|
lede="Transparent pricing based on what your models, agents, and tools use. Predictable, metered, and with no hidden fees."
|
|
/>
|
|
<Ready reading={reading} what="the rate card">
|
|
{(doc) => <Bill card={doc.tariff} />}
|
|
</Ready>
|
|
</Wide>
|
|
)
|
|
}
|
|
|
|
/** The card itself: its parts, its rates and its windows. */
|
|
function Bill({ card: tariff }: { card: Card }) {
|
|
return (
|
|
<>
|
|
<Grid columns={{ min: 260, max: 4 }} gap={24}>
|
|
{tariff.components.map((part) => (
|
|
<Part key={part.name} part={part} />
|
|
))}
|
|
</Grid>
|
|
|
|
<YStack mt={64}>
|
|
<Sub
|
|
title="The rates"
|
|
lede="Every rate here is the price you pay: the one on your bill, and the one your usage page shows."
|
|
/>
|
|
<Rates
|
|
columns={[
|
|
{ head: '' },
|
|
{ head: 'Rate', align: 'right', mono: true },
|
|
{ head: '', wide: true, tone: 'muted' },
|
|
]}
|
|
rows={tariff.rates.map((r) => {
|
|
const second = bySecond(r)
|
|
return [
|
|
r.title,
|
|
second ? (
|
|
<YStack key={r.name} items="flex-end">
|
|
<Line weight="500">{priced(r)}</Line>
|
|
<Line size="sm" tone="muted">
|
|
{second}
|
|
</Line>
|
|
</YStack>
|
|
) : (
|
|
priced(r)
|
|
),
|
|
r.note,
|
|
]
|
|
})}
|
|
/>
|
|
</YStack>
|
|
|
|
<YStack mt={64}>
|
|
<Sub
|
|
title="The completion window is the biggest lever"
|
|
lede="The same model at a different window is a different price, chosen per request. Pay the interactive tariff only when a person is waiting."
|
|
/>
|
|
<Grid columns={{ min: 240, max: 3 }} gap={24}>
|
|
{tariff.windows.map((w) => (
|
|
<Panel key={w.name} pad="$5">
|
|
<Line size="lg" weight="600" marginBottom="$2" fontFamily="$mono">
|
|
{w.name}
|
|
</Line>
|
|
<Line size="sm" tone="muted">
|
|
{w.note}
|
|
</Line>
|
|
</Panel>
|
|
))}
|
|
</Grid>
|
|
</YStack>
|
|
</>
|
|
)
|
|
}
|