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