Files

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>
</>
)
}