Files

74 lines
2.4 KiB
TypeScript

'use client'
import { FAMILIES } from '@/components/pricing/Plans'
import { charge, type Plan } from '@/lib/api/read'
import { ready, usePlans } from '@/lib/api/use'
/**
* The plans /pricing sells, said once more as a machine can hold them: an
* OfferCatalog of one Offer per plan, each priced by the month and, where the
* catalog sells one, by the year.
*
* Read from the plan authority (GET /v1/billing/plans) when the page opens, the
* same read the cards draw, so the structured data and the cards cannot state
* two prices. Nothing is in the export: until the plans are read, and when the
* backend does not answer, there is no block at all rather than an old price.
* A plan sold by conversation (Enterprise) is offered with no price, since the
* row's 0 is not what it costs.
*/
const SITE = 'https://hanzo.ai'
/** `</script>` inside a JSON string ends the block and the rest of the page becomes markup. */
const embed = (value: unknown) => JSON.stringify(value).replace(/</g, '\\u003c')
/** One term's price: a month, or a year where the catalog sells one. */
function term(p: Plan, interval: 'month' | 'year', price: number) {
return {
'@type': 'UnitPriceSpecification',
price,
priceCurrency: 'USD',
billingDuration: interval === 'month' ? 'P1M' : 'P1Y',
unitText: p.pricePerUser ? `per member per ${interval}` : `per ${interval}`,
}
}
function offer(p: Plan) {
const base = {
'@type': 'Offer',
name: p.name,
...(p.description ? { description: p.description } : {}),
itemOffered: { '@id': `${SITE}/#platform` },
}
if (p.contactSales) return { ...base, url: `${SITE}/contact-sales` }
const month = p.priceMonthly ?? 0
const year = charge(p, 'year')
return {
...base,
url: `${SITE}/pricing`,
price: month,
priceCurrency: 'USD',
priceSpecification: [term(p, 'month', month), ...(year ? [term(p, 'year', year)] : [])],
}
}
export function PlansJsonLd() {
const rows = ready(usePlans())?.filter((p) => FAMILIES.has(p.category))
if (!rows?.length) return null
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: embed({
'@context': 'https://schema.org',
'@type': 'OfferCatalog',
'@id': `${SITE}/pricing#plans`,
name: 'Hanzo plans',
url: `${SITE}/pricing`,
itemListElement: rows.map(offer),
}),
}}
/>
)
}