Files
www_engage_os/src/components/Pricing.tsx
2025-09-05 14:35:51 +02:00

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