feature: add Memberships Page with dummy data
This commit is contained in:
parent
875923d5a6
commit
375bfc46b1
@ -25,7 +25,7 @@ export default function PageLayout({
|
|||||||
const isMobile = isMobileDevice();
|
const isMobile = isMobileDevice();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-screen w-full flex flex-col relative bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 transition-colors m-0 p-0 overflow-hidden">
|
<div className="min-h-screen w-full flex flex-col relative bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 transition-colors m-0 p-0">
|
||||||
|
|
||||||
{showHeader && (
|
{showHeader && (
|
||||||
<div className="relative z-50 w-full flex-shrink-0">
|
<div className="relative z-50 w-full flex-shrink-0">
|
||||||
@ -33,8 +33,8 @@ export default function PageLayout({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Main content */}
|
{/* Main content now participates in normal document flow */}
|
||||||
<div className="flex-1 relative z-10 w-full flex flex-col overflow-auto">
|
<div className="flex-1 relative z-10 w-full flex flex-col">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
418
src/app/memberships/page.tsx
Normal file
418
src/app/memberships/page.tsx
Normal file
@ -0,0 +1,418 @@
|
|||||||
|
import { CheckIcon, XMarkIcon } from '@heroicons/react/20/solid'
|
||||||
|
import PageLayout from '../components/PageLayout'
|
||||||
|
|
||||||
|
const frequencies = [
|
||||||
|
{ value: 'monthly', label: 'Monthly' },
|
||||||
|
{ value: 'annually', label: 'Annually' },
|
||||||
|
]
|
||||||
|
const tiers = [
|
||||||
|
{
|
||||||
|
name: 'Starter',
|
||||||
|
id: 'tier-starter',
|
||||||
|
href: '#',
|
||||||
|
featured: false,
|
||||||
|
description: 'Everything you need to get started.',
|
||||||
|
price: { monthly: '$19', annually: '$199' },
|
||||||
|
highlights: ['Custom domains', 'Edge content delivery', 'Advanced analytics'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Scale',
|
||||||
|
id: 'tier-scale',
|
||||||
|
href: '#',
|
||||||
|
featured: true,
|
||||||
|
description: 'Added flexibility at scale.',
|
||||||
|
price: { monthly: '$99', annually: '$999' },
|
||||||
|
highlights: [
|
||||||
|
'Custom domains',
|
||||||
|
'Edge content delivery',
|
||||||
|
'Advanced analytics',
|
||||||
|
'Quarterly workshops',
|
||||||
|
'Single sign-on (SSO)',
|
||||||
|
'Priority phone support',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Growth',
|
||||||
|
id: 'tier-growth',
|
||||||
|
href: '#',
|
||||||
|
featured: false,
|
||||||
|
description: 'All the extras for your growing team.',
|
||||||
|
price: { monthly: '$49', annually: '$499' },
|
||||||
|
highlights: ['Custom domains', 'Edge content delivery', 'Advanced analytics', 'Quarterly workshops'],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const sections = [
|
||||||
|
{
|
||||||
|
name: 'Features',
|
||||||
|
features: [
|
||||||
|
{ name: 'Edge content delivery', tiers: { Starter: true, Growth: true, Scale: true } },
|
||||||
|
{ name: 'Custom domains', tiers: { Starter: '1', Growth: '3', Scale: 'Unlimited' } },
|
||||||
|
{ name: 'Team members', tiers: { Starter: '3', Growth: '20', Scale: 'Unlimited' } },
|
||||||
|
{ name: 'Single sign-on (SSO)', tiers: { Starter: false, Growth: false, Scale: true } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Reporting',
|
||||||
|
features: [
|
||||||
|
{ name: 'Advanced analytics', tiers: { Starter: true, Growth: true, Scale: true } },
|
||||||
|
{ name: 'Basic reports', tiers: { Starter: false, Growth: true, Scale: true } },
|
||||||
|
{ name: 'Professional reports', tiers: { Starter: false, Growth: false, Scale: true } },
|
||||||
|
{ name: 'Custom report builder', tiers: { Starter: false, Growth: false, Scale: true } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Support',
|
||||||
|
features: [
|
||||||
|
{ name: '24/7 online support', tiers: { Starter: true, Growth: true, Scale: true } },
|
||||||
|
{ name: 'Quarterly workshops', tiers: { Starter: false, Growth: true, Scale: true } },
|
||||||
|
{ name: 'Priority phone support', tiers: { Starter: false, Growth: false, Scale: true } },
|
||||||
|
{ name: '1:1 onboarding tour', tiers: { Starter: false, Growth: false, Scale: true } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
function classNames(...classes: (string | undefined | null | false)[]): string {
|
||||||
|
return classes.filter(Boolean).join(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MembershipsPage() {
|
||||||
|
return (
|
||||||
|
<PageLayout>
|
||||||
|
<form className="group/tiers isolate bg-gray-900 pb-24">
|
||||||
|
<div className="flow-root border-b border-b-white/5 bg-gray-800/25 pt-24 pb-16 sm:pt-32 lg:pb-0">
|
||||||
|
<div className="mx-auto max-w-7xl px-6 lg:px-8">
|
||||||
|
<div className="relative z-10">
|
||||||
|
<h2 className="mx-auto max-w-4xl text-center text-5xl font-semibold tracking-tight text-balance text-white sm:text-6xl">
|
||||||
|
Pricing that grows with you
|
||||||
|
</h2>
|
||||||
|
<p className="mx-auto mt-6 max-w-2xl text-center text-lg font-medium text-pretty text-gray-400 sm:text-xl/8">
|
||||||
|
Choose an affordable plan that’s packed with the best features for engaging your audience, creating
|
||||||
|
customer loyalty, and driving sales.
|
||||||
|
</p>
|
||||||
|
<div className="mt-16 flex justify-center">
|
||||||
|
<fieldset aria-label="Payment frequency">
|
||||||
|
<div className="grid grid-cols-2 gap-x-1 rounded-full bg-white/5 p-1 text-center text-xs/5 font-semibold text-white">
|
||||||
|
<label className="group relative rounded-full px-2.5 py-1 has-checked:bg-indigo-500">
|
||||||
|
<input
|
||||||
|
defaultValue="monthly"
|
||||||
|
defaultChecked
|
||||||
|
name="frequency"
|
||||||
|
type="radio"
|
||||||
|
className="absolute inset-0 appearance-none rounded-full"
|
||||||
|
/>
|
||||||
|
<span className="text-white">Monthly</span>
|
||||||
|
</label>
|
||||||
|
<label className="group relative rounded-full px-2.5 py-1 has-checked:bg-indigo-500">
|
||||||
|
<input
|
||||||
|
defaultValue="annually"
|
||||||
|
name="frequency"
|
||||||
|
type="radio"
|
||||||
|
className="absolute inset-0 appearance-none rounded-full"
|
||||||
|
/>
|
||||||
|
<span className="text-white">Annually</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="relative mx-auto mt-10 grid max-w-md grid-cols-1 gap-y-8 lg:mx-0 lg:-mb-14 lg:max-w-none lg:grid-cols-3">
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 1208 1024"
|
||||||
|
aria-hidden="true"
|
||||||
|
className="absolute -bottom-48 left-1/2 h-256 -translate-x-1/2 translate-y-1/2 mask-[radial-gradient(closest-side,white,transparent)] lg:-top-48 lg:bottom-auto lg:translate-y-0"
|
||||||
|
>
|
||||||
|
<ellipse cx={604} cy={512} rx={604} ry={512} fill="url(#d25c25d4-6d43-4bf9-b9ac-1842a30a4867)" />
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="d25c25d4-6d43-4bf9-b9ac-1842a30a4867">
|
||||||
|
<stop stopColor="#7775D6" />
|
||||||
|
<stop offset={1} stopColor="#E935C1" />
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
className="hidden lg:absolute lg:inset-x-px lg:top-4 lg:bottom-0 lg:block lg:rounded-t-2xl lg:bg-gray-800/80 lg:ring-1 lg:ring-white/10"
|
||||||
|
/>
|
||||||
|
{tiers.map((tier) => (
|
||||||
|
<div
|
||||||
|
key={tier.id}
|
||||||
|
data-featured={tier.featured ? 'true' : undefined}
|
||||||
|
className={classNames(
|
||||||
|
tier.featured
|
||||||
|
? 'z-10 bg-gray-800 outline-1 -outline-offset-1 outline-white/10'
|
||||||
|
: 'bg-gray-800/80 outline-1 -outline-offset-1 outline-white/10 lg:bg-transparent lg:pb-14 lg:outline-0',
|
||||||
|
'group/tier relative rounded-2xl',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="p-8 lg:pt-12 xl:p-10 xl:pt-14">
|
||||||
|
<h3 id={`tier-${tier.id}`} className="text-sm/6 font-semibold text-white">
|
||||||
|
{tier.name}
|
||||||
|
</h3>
|
||||||
|
<div className="flex flex-col gap-6 sm:flex-row sm:items-end sm:justify-between lg:flex-col lg:items-stretch">
|
||||||
|
<div className="mt-2 flex items-center gap-x-4">
|
||||||
|
<p className="text-4xl font-semibold tracking-tight text-white group-not-has-[[name=frequency][value=monthly]:checked]/tiers:hidden">
|
||||||
|
{tier.price.monthly}
|
||||||
|
</p>
|
||||||
|
<p className="text-4xl font-semibold tracking-tight text-white group-not-has-[[name=frequency][value=annually]:checked]/tiers:hidden">
|
||||||
|
{tier.price.annually}
|
||||||
|
</p>
|
||||||
|
<div className="text-sm">
|
||||||
|
<p className="text-white">USD</p>
|
||||||
|
<p className="text-gray-400 group-not-has-[[name=frequency][value=monthly]:checked]/tiers:hidden">
|
||||||
|
Billed monthly
|
||||||
|
</p>
|
||||||
|
<p className="text-gray-400 group-not-has-[[name=frequency][value=annually]:checked]/tiers:hidden">
|
||||||
|
Billed annually
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
value={tier.id}
|
||||||
|
name="tier"
|
||||||
|
type="submit"
|
||||||
|
aria-describedby={`tier-${tier.id}`}
|
||||||
|
className="w-full rounded-md bg-white/10 px-3 py-2 text-center text-sm/6 font-semibold text-white not-group-data-featured:inset-ring not-group-data-featured:inset-ring-white/5 group-data-featured/tier:bg-indigo-500 hover:bg-white/20 group-data-featured/tier:hover:bg-indigo-400 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/75 group-data-featured/tier:focus-visible:outline-indigo-500"
|
||||||
|
>
|
||||||
|
Buy this plan
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="mt-8 flow-root sm:mt-10">
|
||||||
|
<ul
|
||||||
|
role="list"
|
||||||
|
className="-my-2 divide-y divide-white/5 border-t border-white/5 text-sm/6 text-white group-data-featured/tier:divide-white/10 group-data-featured/tier:border-white/10 lg:border-t-0"
|
||||||
|
>
|
||||||
|
{tier.highlights.map((mainFeature) => (
|
||||||
|
<li key={mainFeature} className="flex gap-x-3 py-2">
|
||||||
|
<CheckIcon
|
||||||
|
aria-hidden="true"
|
||||||
|
className="h-6 w-5 flex-none text-gray-500 group-data-featured/tier:text-indigo-400"
|
||||||
|
/>
|
||||||
|
{mainFeature}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="relative bg-gray-900 lg:pt-14">
|
||||||
|
<div className="mx-auto max-w-7xl px-6 py-24 sm:py-32 lg:px-8">
|
||||||
|
{/* Feature comparison (up to lg) */}
|
||||||
|
<section aria-labelledby="mobile-comparison-heading" className="lg:hidden">
|
||||||
|
<h2 id="mobile-comparison-heading" className="sr-only">
|
||||||
|
Feature comparison
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="mx-auto max-w-2xl space-y-16">
|
||||||
|
{tiers.map((tier) => (
|
||||||
|
<div key={tier.id} className="border-t border-white/10">
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
tier.featured ? 'border-indigo-500' : 'border-transparent',
|
||||||
|
'-mt-px w-72 border-t-2 pt-10 md:w-80',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
className={classNames(
|
||||||
|
tier.featured ? 'text-indigo-400' : 'text-white',
|
||||||
|
'text-sm/6 font-semibold',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{tier.name}
|
||||||
|
</h3>
|
||||||
|
<p className="mt-1 text-sm/6 text-gray-400">{tier.description}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-10 space-y-10">
|
||||||
|
{sections.map((section) => (
|
||||||
|
<div key={section.name}>
|
||||||
|
<h4 className="text-sm/6 font-semibold text-white">{section.name}</h4>
|
||||||
|
<div className="relative mt-6">
|
||||||
|
{/* Fake card background */}
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
className="absolute inset-y-0 right-0 hidden w-1/2 rounded-lg bg-gray-800/50 sm:block"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
tier.featured ? 'ring-2 ring-indigo-500' : 'ring-1 ring-white/10',
|
||||||
|
'relative rounded-lg bg-gray-800/50 sm:rounded-none sm:bg-transparent sm:ring-0',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<dl className="divide-y divide-white/10 text-sm/6">
|
||||||
|
{section.features.map((feature) => (
|
||||||
|
<div
|
||||||
|
key={feature.name}
|
||||||
|
className="flex items-center justify-between px-4 py-3 sm:grid sm:grid-cols-2 sm:px-0"
|
||||||
|
>
|
||||||
|
<dt className="pr-4 text-gray-400">{feature.name}</dt>
|
||||||
|
<dd className="flex items-center justify-end sm:justify-center sm:px-4">
|
||||||
|
{typeof feature.tiers[tier.name as 'Starter' | 'Growth' | 'Scale'] === 'string' ? (
|
||||||
|
<span className={tier.featured ? 'font-semibold text-indigo-400' : 'text-white'}>
|
||||||
|
{feature.tiers[tier.name as 'Starter' | 'Growth' | 'Scale']}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{feature.tiers[tier.name as 'Starter' | 'Growth' | 'Scale'] === true ? (
|
||||||
|
<CheckIcon aria-hidden="true" className="mx-auto size-5 text-indigo-400" />
|
||||||
|
) : (
|
||||||
|
<XMarkIcon aria-hidden="true" className="mx-auto size-5 text-gray-600" />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<span className="sr-only">
|
||||||
|
{feature.tiers[tier.name as 'Starter' | 'Growth' | 'Scale'] === true ? 'Yes' : 'No'}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Fake card border */}
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
className={classNames(
|
||||||
|
tier.featured ? 'ring-2 ring-indigo-500' : 'ring-1 ring-white/10',
|
||||||
|
'pointer-events-none absolute inset-y-0 right-0 hidden w-1/2 rounded-lg sm:block',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Feature comparison (lg+) */}
|
||||||
|
<section aria-labelledby="comparison-heading" className="hidden lg:block">
|
||||||
|
<h2 id="comparison-heading" className="sr-only">
|
||||||
|
Feature comparison
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-4 gap-x-8 border-t border-white/10 before:block">
|
||||||
|
{tiers.map((tier) => (
|
||||||
|
<div key={tier.id} aria-hidden="true" className="-mt-px">
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
tier.featured ? 'border-indigo-500' : 'border-transparent',
|
||||||
|
'border-t-2 pt-10',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
className={classNames(
|
||||||
|
tier.featured ? 'text-indigo-400' : 'text-white',
|
||||||
|
'text-sm/6 font-semibold',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{tier.name}
|
||||||
|
</p>
|
||||||
|
<p className="mt-1 text-sm/6 text-gray-400">{tier.description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="-mt-6 space-y-16">
|
||||||
|
{sections.map((section) => (
|
||||||
|
<div key={section.name}>
|
||||||
|
<h3 className="text-sm/6 font-semibold text-white">{section.name}</h3>
|
||||||
|
<div className="relative -mx-8 mt-10">
|
||||||
|
{/* Fake card backgrounds */}
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
className="absolute inset-x-8 inset-y-0 grid grid-cols-4 gap-x-8 before:block"
|
||||||
|
>
|
||||||
|
<div className="size-full rounded-lg bg-gray-800/50" />
|
||||||
|
<div className="size-full rounded-lg bg-gray-800/50" />
|
||||||
|
<div className="size-full rounded-lg bg-gray-800/50" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table className="relative w-full border-separate border-spacing-x-8">
|
||||||
|
<thead>
|
||||||
|
<tr className="text-left">
|
||||||
|
<th scope="col">
|
||||||
|
<span className="sr-only">Feature</span>
|
||||||
|
</th>
|
||||||
|
{tiers.map((tier) => (
|
||||||
|
<th key={tier.id} scope="col">
|
||||||
|
<span className="sr-only">{tier.name} tier</span>
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{section.features.map((feature, featureIdx) => (
|
||||||
|
<tr key={feature.name}>
|
||||||
|
<th scope="row" className="w-1/4 py-3 pr-4 text-left text-sm/6 font-normal text-white">
|
||||||
|
{feature.name}
|
||||||
|
{featureIdx !== section.features.length - 1 ? (
|
||||||
|
<div className="absolute inset-x-8 mt-3 h-px bg-white/10" />
|
||||||
|
) : null}
|
||||||
|
</th>
|
||||||
|
{tiers.map((tier) => (
|
||||||
|
<td key={tier.id} className="relative w-1/4 px-4 py-0 text-center">
|
||||||
|
<span className="relative size-full py-3">
|
||||||
|
{typeof feature.tiers[tier.name as 'Starter' | 'Growth' | 'Scale'] === 'string' ? (
|
||||||
|
<span
|
||||||
|
className={classNames(
|
||||||
|
tier.featured ? 'font-semibold text-indigo-400' : 'text-white',
|
||||||
|
'text-sm/6',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{feature.tiers[tier.name as 'Starter' | 'Growth' | 'Scale']}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{feature.tiers[tier.name as 'Starter' | 'Growth' | 'Scale'] === true ? (
|
||||||
|
<CheckIcon aria-hidden="true" className="mx-auto size-5 text-indigo-400" />
|
||||||
|
) : (
|
||||||
|
<XMarkIcon aria-hidden="true" className="mx-auto size-5 text-gray-600" />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<span className="sr-only">
|
||||||
|
{feature.tiers[tier.name as 'Starter' | 'Growth' | 'Scale'] === true ? 'Yes' : 'No'}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{/* Fake card borders */}
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
className="pointer-events-none absolute inset-x-8 inset-y-0 grid grid-cols-4 gap-x-8 before:block"
|
||||||
|
>
|
||||||
|
{tiers.map((tier) => (
|
||||||
|
<div
|
||||||
|
key={tier.id}
|
||||||
|
className={classNames(
|
||||||
|
tier.featured ? 'ring-2 ring-indigo-500' : 'ring-1 ring-white/10',
|
||||||
|
'rounded-lg',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</PageLayout>
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user