Files
zeekay 6c65e3e941 @hanzo/app 0.2.0: the app SDK, renamed from @hanzo/rooms
Every import, the catalog pin and the docs name @hanzo/app and
github.com/hanzoai/app. lib/rooms.ts is lib/app.ts and
components/rooms.tsx is components/app.tsx.
2026-10-22 19:07:20 -07:00

122 lines
4.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
/**
* /invite — an affiliate's invitation: who invited the reader, the plan they were
* invited to, and Continue, which signs in or creates the account and then goes
* through /onboarding (lib/auth/destination `picker`, the terms a new account
* owes) to that plan's checkout on pay (lib/checkout).
*
* ?aff=<code> the affiliate's code; the app keeps it and names it to the
* reader's organization before checkout (lib/hanzo/affiliate)
* ?by=<name> the name the invitation is from, as the link states it
* ?plan=<slug> the plan offered; without one, Continue signs in, and a new
* account is asked how it will use Hanzo (components/onboarding/flow)
*/
import { Suspense } from 'react'
import { useSearchParams } from 'next/navigation'
import { XStack, YStack } from '@hanzo/ui'
import { Check } from 'lucide-react'
import { Waiting } from '@hanzo/app/waiting'
import { Line } from '@/components/type'
import { LOGIN } from '@hanzo/app/lib/destination'
import { money } from '@hanzo/app/lib/pay'
import { usePlans } from '@/lib/api/use'
import { Loading, Unreachable } from '@/components/api/state'
import { Action } from '@hanzo/ui/marketing'
import { checkout } from '@/lib/checkout'
/** Where a paid buyer comes back to, the same as a pricing card's checkout. */
const BACK = 'https://hanzo.ai/'
function Invitation() {
const q = useSearchParams()
const by = (q.get('by') ?? '').trim().slice(0, 60)
const slug = (q.get('plan') ?? '').trim()
const reading = usePlans()
const plan = reading.state === 'ready' ? reading.data.find((p) => p.id === slug) : undefined
const go = () => {
const pay = plan ? checkout({ plan: plan.checkoutId || plan.id, returnUrl: BACK }) : ''
window.location.assign(pay ? `${LOGIN}?next=${encodeURIComponent(pay)}` : LOGIN)
}
return (
<YStack items="center" gap="$5" maxW={560} mx="auto" width="100%" px="$4" py="$8" $sm={{ gap: '$6', py: '$10' }}>
<YStack items="center" gap="$2">
<Line render="h1" size="x3" weight="600" align="center">
You’re invited to Hanzo
</Line>
<Line size="base" tone="muted" align="center">
{by ? `${by} invited you to Hanzo` : 'You’ve been invited to Hanzo'}
{plan ? ` ${plan.name}.` : '.'}
</Line>
</YStack>
{plan ? (
<YStack
width="100%"
gap="$3"
p="$5"
$sm={{ p: '$6' }}
rounded="var(--radius-xl)"
borderWidth={1}
borderColor="$edge"
bg="$panel"
>
<Line size="x2" weight="600">
{plan.name}
</Line>
{plan.priceMonthly ? (
<Line size="x3" weight="600">
{money(plan.priceMonthly)}
<Line render="span" size="sm" tone="muted">
{' '}
USD/month
</Line>
</Line>
) : null}
{plan.description ? (
<Line size="sm" tone="muted">
{plan.description}
</Line>
) : null}
<YStack gap="$2" pt="$2">
{plan.features.slice(0, 5).map((f) => (
<XStack key={f} gap="$2" items="flex-start">
<YStack mt={3} shrink={0}>
<Check size={16} aria-hidden />
</YStack>
<Line size="sm">{f}</Line>
</XStack>
))}
</YStack>
</YStack>
) : slug && reading.state === 'loading' ? (
<Loading what="the plan" />
) : slug && reading.state === 'unreachable' ? (
<Unreachable what="the plan and its price" />
) : null}
<YStack items="center" gap="$2" width="100%">
<YStack width="100%" maxW={360}>
<Action render="button" type="button" fill onClick={go}>
Continue
</Action>
</YStack>
<Line render="p" size="xs" tone="muted" align="center">
Sign in, or create your account, then {plan ? 'pay for the plan on the next page.' : 'choose a plan.'}
</Line>
</YStack>
</YStack>
)
}
export default function InvitePage() {
return (
<Suspense fallback={<Waiting title="One moment" />}>
<Invitation />
</Suspense>
)
}