299 lines
8.7 KiB
TypeScript
299 lines
8.7 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import { Radio, RadioGroup } from '@headlessui/react'
|
|
import clsx from 'clsx'
|
|
|
|
import { Button } from '@/components/Button'
|
|
import { Container } from '@/components/Container'
|
|
import { Logomark } from '@/components/Logo'
|
|
|
|
const plans = [
|
|
{
|
|
name: 'Starter',
|
|
featured: false,
|
|
price: { Monthly: '$0', Annually: '$0' },
|
|
description:
|
|
'Perfect for small teams and early-stage initiatives getting started with community engagement.',
|
|
button: {
|
|
label: 'Launch for Free',
|
|
href: '/register',
|
|
},
|
|
features: [
|
|
'Up to 1,000 members',
|
|
'Built-in community tools (forums, updates, events)',
|
|
'Basic learning paths',
|
|
'Donations & campaigns',
|
|
'Your logo & colors',
|
|
'ThreeFold subdomain (yourname.ThreeFold.org)',
|
|
'Email support',
|
|
],
|
|
logomarkClassName: 'fill-gray-300',
|
|
},
|
|
{
|
|
name: 'Impact',
|
|
featured: true,
|
|
price: { Monthly: '$89', Annually: '$890' },
|
|
description:
|
|
'For growing organizations ready to scale impact, train supporters, and fundraise with confidence.',
|
|
button: {
|
|
label: 'Get Started',
|
|
href: '/register',
|
|
},
|
|
features: [
|
|
'Up to 25,000 members',
|
|
'Advanced training with AI-powered content',
|
|
'Multilingual support',
|
|
'Peer-to-peer fundraising tools',
|
|
'Impact dashboards & metrics',
|
|
'Full branding (custom domain, logo, colors)',
|
|
'API access + integrations (Mailchimp, CRM)',
|
|
'Priority support',
|
|
],
|
|
logomarkClassName: 'fill-cyan-500',
|
|
},
|
|
{
|
|
name: 'Sovereign',
|
|
featured: false,
|
|
price: { Monthly: 'Custom', Annually: 'Custom' },
|
|
description:
|
|
'Best for large-scale networks and institutions with custom needs, privacy requirements, or regional infrastructure.',
|
|
button: {
|
|
label: 'Contact Sales',
|
|
href: '/contact',
|
|
},
|
|
features: [
|
|
'Unlimited members & projects',
|
|
'White-label platform (yourname.org)',
|
|
'Self-host or deploy in your region',
|
|
'Custom integrations & AI assistants',
|
|
'Field-level data collection',
|
|
'Dedicated success manager',
|
|
'Onboarding & migration support',
|
|
'24/7 enterprise-grade support',
|
|
],
|
|
logomarkClassName: 'fill-gray-500',
|
|
},
|
|
]
|
|
|
|
|
|
function CheckIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
|
|
return (
|
|
<svg viewBox="0 0 24 24" aria-hidden="true" {...props}>
|
|
<path
|
|
d="M9.307 12.248a.75.75 0 1 0-1.114 1.004l1.114-1.004ZM11 15.25l-.557.502a.75.75 0 0 0 1.15-.043L11 15.25Zm4.844-5.041a.75.75 0 0 0-1.188-.918l1.188.918Zm-7.651 3.043 2.25 2.5 1.114-1.004-2.25-2.5-1.114 1.004Zm3.4 2.457 4.25-5.5-1.187-.918-4.25 5.5 1.188.918Z"
|
|
fill="currentColor"
|
|
/>
|
|
<circle
|
|
cx="12"
|
|
cy="12"
|
|
r="8.25"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="1.5"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
/>
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function Plan({
|
|
name,
|
|
price,
|
|
description,
|
|
button,
|
|
features,
|
|
activePeriod,
|
|
logomarkClassName,
|
|
featured = false,
|
|
}: {
|
|
name: string
|
|
price: {
|
|
Monthly: string
|
|
Annually: string
|
|
}
|
|
description: string
|
|
button: {
|
|
label: string
|
|
href: string
|
|
}
|
|
features: Array<string>
|
|
activePeriod: 'Monthly' | 'Annually'
|
|
logomarkClassName?: string
|
|
featured?: boolean
|
|
}) {
|
|
return (
|
|
<section
|
|
className={clsx(
|
|
'flex flex-col overflow-hidden rounded-3xl p-6 shadow-lg shadow-black/20',
|
|
featured ? 'order-first bg-cyan-600 lg:order-none' : 'bg-gray-900',
|
|
)}
|
|
>
|
|
<h3
|
|
className={clsx(
|
|
'flex items-center text-sm font-semibold',
|
|
featured ? 'text-white' : 'text-white',
|
|
)}
|
|
>
|
|
<Logomark className={clsx('h-6 w-6 flex-none', logomarkClassName)} />
|
|
<span className="ml-4">{name}</span>
|
|
</h3>
|
|
<p
|
|
className={clsx(
|
|
'relative mt-5 flex text-3xl tracking-tight',
|
|
featured ? 'text-white' : 'text-white',
|
|
)}
|
|
>
|
|
{price.Monthly === price.Annually ? (
|
|
price.Monthly
|
|
) : (
|
|
<>
|
|
<span
|
|
aria-hidden={activePeriod === 'Annually'}
|
|
className={clsx(
|
|
'transition duration-300',
|
|
activePeriod === 'Annually' &&
|
|
'pointer-events-none translate-x-6 opacity-0 select-none',
|
|
)}
|
|
>
|
|
{price.Monthly}
|
|
</span>
|
|
<span
|
|
aria-hidden={activePeriod === 'Monthly'}
|
|
className={clsx(
|
|
'absolute top-0 left-0 transition duration-300',
|
|
activePeriod === 'Monthly' &&
|
|
'pointer-events-none -translate-x-6 opacity-0 select-none',
|
|
)}
|
|
>
|
|
{price.Annually}
|
|
</span>
|
|
</>
|
|
)}
|
|
</p>
|
|
<p
|
|
className={clsx(
|
|
'mt-3 text-sm',
|
|
featured ? 'text-gray-100' : 'text-gray-300',
|
|
)}
|
|
>
|
|
{description}
|
|
</p>
|
|
<div className="order-last mt-6">
|
|
<ul
|
|
role="list"
|
|
className={clsx(
|
|
'-my-2 divide-y text-sm',
|
|
featured
|
|
? 'divide-gray-700 text-gray-100'
|
|
: 'divide-gray-700 text-gray-300',
|
|
)}
|
|
>
|
|
{features.map((feature) => (
|
|
<li key={feature} className="flex py-2">
|
|
<CheckIcon
|
|
className={clsx(
|
|
'h-6 w-6 flex-none',
|
|
featured ? 'text-white' : 'text-cyan-400',
|
|
)}
|
|
/>
|
|
<span className="ml-4">{feature}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
<Button
|
|
href={button.href}
|
|
color={featured ? 'white' : 'cyan'}
|
|
className="mt-6"
|
|
aria-label={`Get started with the ${name} plan for ${price}`}
|
|
>
|
|
{button.label}
|
|
</Button>
|
|
</section>
|
|
)
|
|
}
|
|
|
|
export function Pricing() {
|
|
let [activePeriod, setActivePeriod] = useState<'Monthly' | 'Annually'>(
|
|
'Monthly',
|
|
)
|
|
|
|
return (
|
|
<section
|
|
id="pricing"
|
|
aria-labelledby="pricing-title"
|
|
className="border-t border-gray-800 py-24"
|
|
style={{ backgroundColor: '#121212' }}
|
|
>
|
|
<Container>
|
|
<div className="mx-auto max-w-2xl text-center">
|
|
<h2
|
|
id="pricing-title"
|
|
className="text-3xl font-medium tracking-tight text-white"
|
|
>
|
|
Flat pricing, no management fees.
|
|
</h2>
|
|
<p className="mt-2 lg:text-lg text-base text-gray-300">
|
|
Whether you're one person trying to get ahead or a big firm trying
|
|
to take over the world, we've got a plan for you.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="mt-8 flex justify-center">
|
|
<div className="relative">
|
|
<RadioGroup
|
|
value={activePeriod}
|
|
onChange={setActivePeriod}
|
|
className="grid grid-cols-2"
|
|
>
|
|
{['Monthly', 'Annually'].map((period) => (
|
|
<Radio
|
|
key={period}
|
|
value={period}
|
|
className={clsx(
|
|
'cursor-pointer border border-gray-600 px-[calc(--spacing(3)-1px)] py-[calc(--spacing(2)-1px)] text-sm text-gray-300 transition-colors hover:border-gray-500 data-focus:outline-2 data-focus:outline-offset-2 bg-gray-800',
|
|
period === 'Monthly'
|
|
? 'rounded-l-lg'
|
|
: '-ml-px rounded-r-lg',
|
|
)}
|
|
>
|
|
{period}
|
|
</Radio>
|
|
))}
|
|
</RadioGroup>
|
|
<div
|
|
aria-hidden="true"
|
|
className={clsx(
|
|
'pointer-events-none absolute inset-0 z-10 grid grid-cols-2 overflow-hidden rounded-lg bg-cyan-500 transition-all duration-300',
|
|
activePeriod === 'Monthly'
|
|
? '[clip-path:inset(0_50%_0_0)]'
|
|
: '[clip-path:inset(0_0_0_calc(50%-1px))]',
|
|
)}
|
|
>
|
|
{['Monthly', 'Annually'].map((period) => (
|
|
<div
|
|
key={period}
|
|
className={clsx(
|
|
'py-2 text-center text-sm font-semibold text-white',
|
|
period === 'Annually' && '-ml-px',
|
|
)}
|
|
>
|
|
{period}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mx-auto mt-16 grid max-w-2xl grid-cols-1 items-start gap-x-8 gap-y-10 sm:mt-20 lg:max-w-none lg:grid-cols-3">
|
|
{plans.map((plan) => (
|
|
<Plan key={plan.name} {...plan} activePeriod={activePeriod} />
|
|
))}
|
|
</div>
|
|
</Container>
|
|
</section>
|
|
)
|
|
}
|