Pricing
blockpricingPricing collection with eleven interchangeable variants: seat matrix, interactive calculator, enterprise SLA card, feature add-on builder, feature tier slider, grouped matrix, per-plan cards, sticky-header matrix, single plan, tier comparison matrix, and usage calculator slider.
Installation
$pnpm dlx shadcn@latest add https://uipkge.dev/r/react/pricing.json$npx shadcn@latest add https://uipkge.dev/r/react/pricing.json$yarn dlx shadcn@latest add https://uipkge.dev/r/react/pricing.json$bunx shadcn@latest add https://uipkge.dev/r/react/pricing.jsonNamed registry:
npx shadcn@latest add @uipkge-react/pricingInstalls to:components/blocks/pricing/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
RequestTierinterface RequestTier {
label: string
requests: number
cost: number
}StorageTierinterface StorageTier {
label: string
gb: number
cost: number
}SlaGuaranteeinterface SlaGuarantee {
title: string
metric: string
description: string
icon: string
}ComplianceCertinterface ComplianceCert {
id: string
name: string
status: string
badgeVariant?: 'default' | 'outline' | 'secondary'
}PricingPlaninterface PricingPlan {
id: string
name: string
monthlyPrice: number
description: string
includedSeats: number
}PricingAddoninterface PricingAddon {
id: string
name: string
monthlyPrice: number
description: string
category: string
}Planinterface Plan {
id: string
name: string
description: string
monthlyPrice: number
annualPrice: number
highlight: boolean
badge?: string
ctaText: string
ctaVariant: 'default' | 'outline' | 'secondary'
}FeatureComparisonRowinterface FeatureComparisonRow {
category: string
features: {
name: string
tooltip: string
community: boolean | string
pro: boolean | string
enterprise: boolean | string
}[]
}npm dependencies
Files installed (11)
components/blocks/pricing/Pricing01.tsx23.9 kB
'use client' import * as React from 'react' import { ArrowRight, Check, ChevronDown, ChevronUp, ShieldCheck, Users, Zap } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card' import { Separator } from '@/components/ui/separator' import { Slider } from '@/components/ui/slider' import { Switch } from '@/components/ui/switch' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group' import { cn } from '@/lib/utils' type BillingCycle = 'monthly' | 'yearly' type Currency = 'USD' | 'EUR' | 'GBP' const currencySymbols: Record<Currency, string> = { USD: '$', EUR: '€', GBP: '£', } const currencyRates: Record<Currency, number> = { USD: 1.0, EUR: 0.92, GBP: 0.79, } const comparisonMatrix = [ { category: 'Platform & Compute Core', features: [ { name: 'Active Team Members', starter: 'Up to 10', team: 'Unlimited', enterprise: 'Unlimited + Org Units' }, { name: 'Monthly API Requests', starter: '100,000 / mo', team: '2,500,000 / mo', enterprise: 'Custom Unlimited' }, { name: 'Workflow Automations', starter: '10 active', team: '100 active', enterprise: 'Unlimited real-time' }, { name: 'Data Retention History', starter: '30 days', team: '365 days', enterprise: '7 years immutable' }, ], }, { category: 'Security & Enterprise Governance', features: [ { name: 'SOC 2 Type II & ISO 27001', starter: 'Standard', team: 'Included', enterprise: 'Included + Auditor Portal', }, { name: 'SAML SSO & SCIM Provisioning', starter: '—', team: 'Google / Okta', enterprise: 'Custom IdP + SCIM v2' }, { name: 'Role-Based Access (RBAC)', starter: '3 predefined roles', team: 'Granular permissions', enterprise: 'Custom policy engine', }, { name: 'Immutable Audit Trail Logs', starter: '—', team: '90 days exportable', enterprise: 'Real-time SIEM streaming', }, ], }, { category: 'Support & Success SLAs', features: [ { name: 'Support Channel', starter: 'Community & Email', team: 'Priority Email + Chat', enterprise: 'Dedicated Private Slack', }, { name: 'First-Response SLA', starter: '24 business hours', team: '4 business hours', enterprise: '< 15 mins (24/7/365)', }, { name: 'Uptime SLA Guarantee', starter: '99.9%', team: '99.95%', enterprise: '99.99% financially backed' }, { name: 'Dedicated Solutions Architect', starter: '—', team: '—', enterprise: 'Assigned Principal Engineer' }, ], }, ] export function Pricing01({ className }: { className?: string }) { const [billingCycle, setBillingCycle] = React.useState<BillingCycle>('yearly') const [currency, setCurrency] = React.useState<Currency>('USD') const [teamSeats, setTeamSeats] = React.useState<number[]>([12]) const [addDedicatedSla, setAddDedicatedSla] = React.useState(false) const [addAuditVault, setAddAuditVault] = React.useState(false) const [showComparisonTable, setShowComparisonTable] = React.useState(false) const rates = currencyRates[currency] const sym = currencySymbols[currency] const starterBase = billingCycle === 'yearly' ? 12 : 15 const teamBase = billingCycle === 'yearly' ? 28 : 35 const enterpriseBase = billingCycle === 'yearly' ? 68 : 85 const slaCost = addDedicatedSla ? 99 : 0 const vaultCost = addAuditVault ? 49 : 0 const currentSeats = teamSeats[0] || 1 const starterMonthlyTotal = Math.round((starterBase * currentSeats + (addAuditVault ? 29 : 0)) * rates) const teamMonthlyTotal = Math.round((teamBase * currentSeats + slaCost + vaultCost) * rates) const enterpriseMonthlyTotal = Math.round((enterpriseBase * currentSeats + slaCost + vaultCost) * rates) const annualSavingsTeam = Math.round((35 * currentSeats * 12 - 28 * currentSeats * 12) * rates) return ( <section data-slot="pricing-01" className={cn('bg-background w-full py-16 sm:py-24', className)}> <div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"> {/* Section Header */} <div className="mx-auto max-w-3xl space-y-4 text-center"> <div className="border-primary/20 bg-primary/5 text-primary inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs font-medium shadow-2xs"> <ShieldCheck className="size-3.5" /> <span>Predictable Enterprise Pricing</span> </div> <h2 className="text-foreground text-3xl font-bold tracking-tight sm:text-4xl lg:text-5xl"> Scale effortlessly without seat tax surprises </h2> <p className="text-muted-foreground text-base sm:text-lg"> Zero setup fees, transparent volume discounts, and instant self-serve provisioning. Switch or cancel plans anytime. </p> {/* Billing Cycle & Currency Switcher Toolbar */} <div className="mt-8 flex flex-wrap items-center justify-center gap-4 pt-2"> {/* Monthly vs Yearly */} <div className="border-border bg-card inline-flex rounded-lg border p-1 shadow-2xs"> <ToggleGroup type="single" value={billingCycle} onValueChange={(v) => v && setBillingCycle(v as BillingCycle)} > <ToggleGroupItem value="monthly" className="px-3.5 py-1.5 text-xs font-medium"> Monthly </ToggleGroupItem> <ToggleGroupItem value="yearly" className="flex items-center gap-1.5 px-3.5 py-1.5 text-xs font-medium"> <span>Annual</span> <Badge variant="secondary" className="bg-primary/10 text-primary border-primary/20 px-1.5 py-0 text-xs" > Save 20% </Badge> </ToggleGroupItem> </ToggleGroup> </div> {/* Currency Selector */} <div className="border-border bg-card inline-flex rounded-lg border p-1 shadow-2xs"> <ToggleGroup type="single" value={currency} onValueChange={(v) => v && setCurrency(v as Currency)}> <ToggleGroupItem value="USD" className="px-2.5 py-1 font-mono text-xs font-medium"> USD ($) </ToggleGroupItem> <ToggleGroupItem value="EUR" className="px-2.5 py-1 font-mono text-xs font-medium"> EUR (€) </ToggleGroupItem> <ToggleGroupItem value="GBP" className="px-2.5 py-1 font-mono text-xs font-medium"> GBP (£) </ToggleGroupItem> </ToggleGroup> </div> </div> {/* Interactive Team Seat Simulator Bar */} <div className="border-border bg-card/60 mx-auto mt-6 max-w-xl rounded-xl border p-4 shadow-2xs backdrop-blur-xs"> <div className="flex flex-wrap items-center justify-between gap-2 text-xs"> <div className="text-foreground flex items-center gap-2 font-medium"> <Users className="text-primary size-4" /> <span>Simulate Team Size:</span> </div> <div className="flex items-center gap-2"> <span className="bg-muted text-foreground rounded px-2 py-0.5 font-mono text-xs font-semibold"> {currentSeats} {currentSeats === 1 ? 'seat' : 'seats'} </span> {billingCycle === 'yearly' && annualSavingsTeam > 0 && ( <span className="text-success text-xs font-medium"> (Saves ~{sym} {annualSavingsTeam.toLocaleString()}/yr on Team plan) </span> )} </div> </div> <div className="mt-3"> <Slider value={teamSeats} onValueChange={setTeamSeats} min={1} max={50} step={1} className="cursor-pointer" /> </div> <div className="text-muted-foreground mt-2 flex justify-between font-mono text-xs"> <span>1 seat</span> <span>25 seats</span> <span>50+ seats</span> </div> </div> </div> {/* Plan Cards Grid */} <div className="mt-12 grid grid-cols-1 gap-8 lg:grid-cols-3 lg:items-stretch"> {/* Tier 1: Starter */} <Card className="border-border bg-card hover:border-primary/40 relative flex flex-col justify-between shadow-xs transition-colors duration-200 hover:shadow-md"> <div> <CardHeader className="pb-4"> <div className="flex items-center justify-between"> <Badge variant="outline" className="font-mono text-xs"> Bootstrap & Indie </Badge> </div> <CardTitle className="text-foreground text-xl font-bold tracking-tight">Starter Studio</CardTitle> <CardDescription className="text-muted-foreground text-xs"> For agile squads and early-stage product teams validating core workflows. </CardDescription> <div className="mt-6 space-y-1"> <div className="flex items-baseline gap-1.5"> <span className="text-foreground text-4xl font-bold tracking-tight"> {sym} {Math.round(starterBase * rates)} </span> <span className="text-muted-foreground text-xs font-medium">/ seat / month</span> </div> <div className="text-muted-foreground font-mono text-xs"> Est. {sym} {starterMonthlyTotal.toLocaleString()}/mo for {currentSeats} {currentSeats === 1 ? 'seat' : 'seats'} </div> </div> </CardHeader> <CardContent className="space-y-4 pt-2"> <Separator /> <div className="space-y-2.5"> <p className="text-muted-foreground text-xs font-semibold tracking-wider uppercase"> Included features </p> <ul className="text-foreground space-y-2.5 text-xs"> <li className="flex items-start gap-2"> <Check className="text-success mt-0.5 size-4 shrink-0" /> <span>Up to 10 team seats & unlimited guests</span> </li> <li className="flex items-start gap-2"> <Check className="text-success mt-0.5 size-4 shrink-0" /> <span>100,000 monthly API event calls</span> </li> <li className="flex items-start gap-2"> <Check className="text-success mt-0.5 size-4 shrink-0" /> <span>30-day continuous audit & revision history</span> </li> <li className="flex items-start gap-2"> <Check className="text-success mt-0.5 size-4 shrink-0" /> <span>Standard Community & Email support (24h)</span> </li> <li className="text-muted-foreground flex items-start gap-2"> <Check className="text-muted-foreground/50 mt-0.5 size-4 shrink-0" /> <span>Community Discord access & quickstarts</span> </li> </ul> </div> </CardContent> </div> <CardFooter className="border-border mt-auto border-t pt-4"> <Button variant="outline" className="h-10 w-full gap-1.5 text-xs font-semibold"> <span>Start Free 14-Day Trial</span> <ArrowRight className="size-3.5" /> </Button> </CardFooter> </Card> {/* Tier 2: Team Pro (Highlighted Flagship) */} <Card className="border-primary bg-card ring-primary/20 relative flex flex-col justify-between shadow-lg ring-2 transition-colors duration-200 hover:shadow-xl lg:-translate-y-2"> <div className="absolute -top-3.5 left-1/2 -translate-x-1/2"> <Badge className="bg-primary text-primary-foreground gap-1.5 px-3 py-0.5 text-xs font-semibold shadow-sm"> <Zap className="size-3 fill-current" /> <span>Most Popular Choice</span> </Badge> </div> <div> <CardHeader className="pt-7 pb-4"> <div className="flex items-center justify-between"> <Badge variant="secondary" className="bg-primary/10 text-primary border-primary/20 font-mono text-xs"> Scale & High-Growth </Badge> </div> <CardTitle className="text-foreground text-2xl font-bold tracking-tight">Team Scale</CardTitle> <CardDescription className="text-muted-foreground text-xs"> For fast-scaling engineering and product organizations requiring deep automation. </CardDescription> <div className="mt-6 space-y-1"> <div className="flex items-baseline gap-1.5"> <span className="text-foreground text-4xl font-bold tracking-tight"> {sym} {Math.round(teamBase * rates)} </span> <span className="text-muted-foreground text-xs font-medium">/ seat / month</span> </div> <div className="text-primary font-mono text-xs font-medium"> Est. {sym} {teamMonthlyTotal.toLocaleString()}/mo for {currentSeats} {currentSeats === 1 ? 'seat' : 'seats'} </div> </div> </CardHeader> <CardContent className="space-y-4 pt-2"> <Separator /> <div className="space-y-2.5"> <p className="text-muted-foreground text-xs font-semibold tracking-wider uppercase"> Everything in Starter, plus </p> <ul className="text-foreground space-y-2.5 text-xs"> <li className="flex items-start gap-2"> <Check className="text-success mt-0.5 size-4 shrink-0" /> <span className="font-medium">Unlimited team seats & custom workspace roles</span> </li> <li className="flex items-start gap-2"> <Check className="text-success mt-0.5 size-4 shrink-0" /> <span>2.5M monthly API calls & priority queue</span> </li> <li className="flex items-start gap-2"> <Check className="text-success mt-0.5 size-4 shrink-0" /> <span>Google & Okta SAML SSO integration</span> </li> <li className="flex items-start gap-2"> <Check className="text-success mt-0.5 size-4 shrink-0" /> <span>365-day immutable compliance logs</span> </li> <li className="flex items-start gap-2"> <Check className="text-success mt-0.5 size-4 shrink-0" /> <span>Priority ticketing with 4h guaranteed SLA</span> </li> </ul> </div> {/* Addons Selector for Team */} <div className="border-border bg-muted/30 space-y-2.5 rounded-lg border p-3"> <p className="text-foreground text-xs font-semibold">Optional Power Add-ons</p> <div className="flex items-center justify-between text-xs"> <label htmlFor="react-sla-addon" className="text-muted-foreground cursor-pointer"> 24/7 Dedicated SLA (+{sym} {Math.round(99 * rates)}/mo) </label> <Switch id="react-sla-addon" checked={addDedicatedSla} onCheckedChange={setAddDedicatedSla} /> </div> <div className="flex items-center justify-between text-xs"> <label htmlFor="react-vault-addon" className="text-muted-foreground cursor-pointer"> SOC 2 Vault Streaming (+{sym} {Math.round(49 * rates)}/mo) </label> <Switch id="react-vault-addon" checked={addAuditVault} onCheckedChange={setAddAuditVault} /> </div> </div> </CardContent> </div> <CardFooter className="border-border mt-auto border-t pt-4"> <Button className="h-10 w-full gap-1.5 text-xs font-semibold shadow-sm"> <span>Deploy Team Workspace</span> <ArrowRight className="size-3.5" /> </Button> </CardFooter> </Card> {/* Tier 3: Enterprise Platform */} <Card className="border-border bg-card hover:border-primary/40 relative flex flex-col justify-between shadow-xs transition-colors duration-200 hover:shadow-md"> <div> <CardHeader className="pb-4"> <div className="flex items-center justify-between"> <Badge variant="outline" className="font-mono text-xs"> Enterprise & Security </Badge> </div> <CardTitle className="text-foreground text-xl font-bold tracking-tight">Enterprise Suite</CardTitle> <CardDescription className="text-muted-foreground text-xs"> Dedicated infrastructure, custom security controls, and bespoke compliance SLAs. </CardDescription> <div className="mt-6 space-y-1"> <div className="flex items-baseline gap-1.5"> <span className="text-foreground text-4xl font-bold tracking-tight"> {sym} {Math.round(enterpriseBase * rates)} </span> <span className="text-muted-foreground text-xs font-medium">/ seat / month</span> </div> <div className="text-muted-foreground font-mono text-xs"> Custom volume licensing available for 100+ seats </div> </div> </CardHeader> <CardContent className="space-y-4 pt-2"> <Separator /> <div className="space-y-2.5"> <p className="text-muted-foreground text-xs font-semibold tracking-wider uppercase"> Everything in Team, plus </p> <ul className="text-foreground space-y-2.5 text-xs"> <li className="flex items-start gap-2"> <Check className="text-success mt-0.5 size-4 shrink-0" /> <span>Custom SCIM v2 user provisioning & directory sync</span> </li> <li className="flex items-start gap-2"> <Check className="text-success mt-0.5 size-4 shrink-0" /> <span>Dedicated VPC peering & custom data residency</span> </li> <li className="flex items-start gap-2"> <Check className="text-success mt-0.5 size-4 shrink-0" /> <span>99.99% uptime SLA with financial penalty backing</span> </li> <li className="flex items-start gap-2"> <Check className="text-success mt-0.5 size-4 shrink-0" /> <span>Assigned Principal Solutions Engineer & private Slack</span> </li> <li className="flex items-start gap-2"> <Check className="text-success mt-0.5 size-4 shrink-0" /> <span>Custom Master Service Agreement (MSA) & DPA</span> </li> </ul> </div> </CardContent> </div> <CardFooter className="border-border mt-auto border-t pt-4"> <Button variant="outline" className="h-10 w-full gap-1.5 text-xs font-semibold"> <ShieldCheck className="text-primary size-3.5" /> <span>Contact Solutions Team</span> </Button> </CardFooter> </Card> </div> {/* Collapsible Feature Matrix Trigger */} <div className="mt-12 text-center"> <Button variant="ghost" size="sm" className="text-foreground hover:bg-muted gap-2 text-xs font-semibold" onClick={() => setShowComparisonTable(!showComparisonTable)} > <span> {showComparisonTable ? 'Hide Detailed Feature Comparison' : 'Compare All Features & Enterprise Limits'} </span> {showComparisonTable ? <ChevronUp className="size-4" /> : <ChevronDown className="size-4" />} </Button> </div> {/* Feature Comparison Matrix Table */} {showComparisonTable && ( <div className="border-border bg-card mt-8 overflow-hidden rounded-xl border shadow-xs"> <div className="border-border bg-muted/20 flex flex-wrap items-center justify-between gap-4 border-b p-4 sm:p-6"> <div> <h3 className="text-foreground text-base font-bold">Detailed Specification & Limits</h3> <p className="text-muted-foreground text-xs">Comprehensive side-by-side breakdown across every tier.</p> </div> <Badge variant="outline" className="font-mono text-xs"> All plans include SSL & automated backups </Badge> </div> <div className="overflow-x-auto"> <Table> <TableHeader> <TableRow className="bg-muted/40"> <TableHead className="text-foreground w-[34%] text-xs font-semibold">Capabilities</TableHead> <TableHead className="text-foreground w-[22%] text-xs font-semibold">Starter Studio</TableHead> <TableHead className="text-primary w-[22%] text-xs font-bold font-semibold">Team Scale</TableHead> <TableHead className="text-foreground w-[22%] text-xs font-semibold">Enterprise Suite</TableHead> </TableRow> </TableHeader> <TableBody> {comparisonMatrix.map((group, idx) => ( <React.Fragment key={idx}> <TableRow className="bg-muted/60 text-muted-foreground text-xs font-semibold"> <TableCell colSpan={4} className="py-2.5 font-mono text-xs tracking-wider uppercase"> {group.category} </TableCell> </TableRow> {group.features.map((feat, fIdx) => ( <TableRow key={fIdx} className="text-xs"> <TableCell className="text-foreground py-3 font-medium">{feat.name}</TableCell> <TableCell className="text-muted-foreground py-3">{feat.starter}</TableCell> <TableCell className="text-foreground bg-primary/5 py-3 font-medium">{feat.team}</TableCell> <TableCell className="text-foreground py-3">{feat.enterprise}</TableCell> </TableRow> ))} </React.Fragment> ))} </TableBody> </Table> </div> </div> )} </div> </section> ) }components/blocks/pricing/PricingCalculator.tsx20.6 kB
'use client' import * as React from 'react' import { ArrowRight, Check, HardDrive, Headphones, KeyRound, ShieldCheck, Users, Zap } from 'lucide-react' import { cn } from '@/lib/utils' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card' import { Separator } from '@/components/ui/separator' import { Slider } from '@/components/ui/slider' import { Switch } from '@/components/ui/switch' interface RequestTier { label: string requests: number cost: number } interface StorageTier { label: string gb: number cost: number } const REQUEST_TIERS: RequestTier[] = [ { label: '10K requests', requests: 10_000, cost: 0 }, { label: '50K requests', requests: 50_000, cost: 25 }, { label: '100K requests', requests: 100_000, cost: 60 }, { label: '250K requests', requests: 250_000, cost: 120 }, { label: '500K requests', requests: 500_000, cost: 220 }, { label: '1M requests', requests: 1_000_000, cost: 400 }, { label: '2.5M requests', requests: 2_500_000, cost: 750 }, { label: '5M requests', requests: 5_000_000, cost: 1200 }, ] const STORAGE_TIERS: StorageTier[] = [ { label: '100 GB', gb: 100, cost: 10 }, { label: '250 GB', gb: 250, cost: 25 }, { label: '500 GB', gb: 500, cost: 45 }, { label: '1 TB', gb: 1_000, cost: 80 }, { label: '2 TB', gb: 2_000, cost: 150 }, { label: '5 TB', gb: 5_000, cost: 320 }, { label: '10 TB', gb: 10_000, cost: 580 }, ] const SEAT_PRICE = 15 const SUPPORT_MANAGER_PRICE = 200 const CUSTOM_SLA_PRICE = 500 const SSO_PRICE = 100 const SEAT_MARKS = { 1: '1', 25: '25', 50: '50', 75: '75', 100: '100', } const REQUEST_MARKS = { 0: '10K', 2: '100K', 4: '500K', 7: '5M', } const STORAGE_MARKS = { 0: '100 GB', 2: '500 GB', 4: '2 TB', 6: '10 TB', } export function PricingCalculator({ className }: { className?: string }) { const [isAnnual, setIsAnnual] = React.useState(true) const [seats, setSeats] = React.useState(12) const [requestIndex, setRequestIndex] = React.useState(2) const [storageIndex, setStorageIndex] = React.useState(2) const [addonSupport, setAddonSupport] = React.useState(false) const [addonSla, setAddonSla] = React.useState(false) const [addonSso, setAddonSso] = React.useState(true) const currentRequestTier = REQUEST_TIERS[requestIndex] ?? REQUEST_TIERS[0] const currentStorageTier = STORAGE_TIERS[storageIndex] ?? STORAGE_TIERS[0] const seatTier = React.useMemo(() => { if (seats <= 5) return { label: 'Starter Team', variant: 'secondary' as const } if (seats <= 25) return { label: 'Growth Team', variant: 'outline' as const } return { label: 'Scale Team', variant: 'default' as const } }, [seats]) const recommendedPlan = React.useMemo(() => { if (addonSla || requestIndex >= 6 || storageIndex >= 5 || seats >= 30) { return { name: 'Enterprise', badge: 'Enterprise Plan', baseFee: 199, variant: 'default' as const } } if (seats >= 8 || requestIndex >= 3 || storageIndex >= 3 || addonSupport) { return { name: 'Growth', badge: 'Growth Plan', baseFee: 79, variant: 'default' as const } } return { name: 'Starter', badge: 'Starter Plan', baseFee: 29, variant: 'secondary' as const } }, [addonSla, requestIndex, storageIndex, seats, addonSupport]) const baseCost = recommendedPlan.baseFee const seatsCost = seats * SEAT_PRICE const requestsCost = currentRequestTier.cost const storageCost = currentStorageTier.cost const addonsCost = (addonSupport ? SUPPORT_MANAGER_PRICE : 0) + (addonSla ? CUSTOM_SLA_PRICE : 0) + (addonSso ? SSO_PRICE : 0) const activeAddonsCount = (addonSupport ? 1 : 0) + (addonSla ? 1 : 0) + (addonSso ? 1 : 0) const subtotalMonthly = baseCost + seatsCost + requestsCost + storageCost + addonsCost const discountMultiplier = isAnnual ? 0.8 : 1.0 const totalMonthly = Math.round(subtotalMonthly * discountMultiplier) const totalAnnual = totalMonthly * 12 const annualSavings = Math.round(subtotalMonthly * 0.2 * 12) return ( <div data-slot="pricing-calculator" className={cn('mx-auto w-full max-w-6xl space-y-10 p-4 sm:p-6 lg:p-8', className)} > <div className="flex flex-col items-center gap-4 text-center"> <Badge variant="outline" className="gap-1.5 px-3 py-1 text-xs font-medium tracking-wider uppercase"> <ShieldCheck className="text-primary size-3.5" /> Pricing Calculator </Badge> <div className="space-y-2"> <h2 className="text-foreground text-2xl font-bold tracking-tight sm:text-3xl lg:text-4xl"> Interactive Pricing Calculator </h2> <p className="text-muted-foreground mx-auto max-w-2xl text-sm sm:text-base"> Calculate your exact monthly or annual investment based on usage. </p> </div> <div className="border-border bg-card mt-2 inline-flex items-center gap-3 rounded-full border px-4 py-2 shadow-xs"> <span className={cn( 'text-xs font-medium transition-colors', !isAnnual ? 'text-foreground font-semibold' : 'text-muted-foreground', )} > Monthly </span> <Switch checked={isAnnual} onCheckedChange={setIsAnnual} /> <span className={cn( 'text-xs font-medium transition-colors', isAnnual ? 'text-foreground font-semibold' : 'text-muted-foreground', )} > Pay Annually </span> <Badge variant="secondary" className="bg-primary/10 text-primary border-primary/20 text-xs font-semibold"> Save 20% </Badge> </div> </div> <div className="grid grid-cols-1 gap-8 lg:grid-cols-12 lg:items-start"> <div className="space-y-6 lg:col-span-7"> <Card> <CardHeader> <CardTitle>Capacity & Scale</CardTitle> <CardDescription>Configure team seats, API request throughput, and dedicated storage.</CardDescription> </CardHeader> <CardContent className="space-y-8"> <div className="space-y-4"> <div className="flex items-center justify-between"> <div className="flex items-center gap-2.5"> <div className="bg-primary/10 text-primary border-primary/20 flex size-8 shrink-0 items-center justify-center rounded-md border"> <Users className="size-4" /> </div> <div> <span className="text-foreground text-sm font-medium">Team Seats</span> <Badge variant={seatTier.variant} className="ml-2 text-xs font-normal"> {seatTier.label} </Badge> </div> </div> <div className="text-right"> <span className="text-foreground text-base font-semibold tabular-nums">{seats}</span> <span className="text-muted-foreground text-xs"> seats (${seats * SEAT_PRICE}/mo)</span> </div> </div> <div className="pb-6"> <Slider value={[seats]} min={1} max={100} step={1} marks={SEAT_MARKS} tooltip={(val) => `${val} seats`} onValueChange={(val) => setSeats(val[0])} /> </div> <p className="text-muted-foreground text-xs"> Full workspace access, fine-grained permission controls, and audit log tracking for each member. </p> </div> <Separator /> <div className="space-y-4"> <div className="flex items-center justify-between"> <div className="flex items-center gap-2.5"> <div className="bg-primary/10 text-primary border-primary/20 flex size-8 shrink-0 items-center justify-center rounded-md border"> <Zap className="size-4" /> </div> <div> <span className="text-foreground text-sm font-medium">Monthly API Requests</span> </div> </div> <div className="text-right"> <span className="text-foreground text-base font-semibold tabular-nums"> {currentRequestTier.label} </span> <span className="text-muted-foreground text-xs"> {' '} ({currentRequestTier.cost === 0 ? 'Included' : `+$${currentRequestTier.cost}/mo`}) </span> </div> </div> <div className="pb-6"> <Slider value={[requestIndex]} min={0} max={REQUEST_TIERS.length - 1} step={1} marks={REQUEST_MARKS} tooltip={(idx) => REQUEST_TIERS[idx]?.label ?? ''} onValueChange={(val) => setRequestIndex(val[0])} /> </div> <p className="text-muted-foreground text-xs"> Globally distributed edge endpoints, automatic rate limiting, and sub-50ms p99 latency SLA. </p> </div> <Separator /> <div className="space-y-4"> <div className="flex items-center justify-between"> <div className="flex items-center gap-2.5"> <div className="bg-primary/10 text-primary border-primary/20 flex size-8 shrink-0 items-center justify-center rounded-md border"> <HardDrive className="size-4" /> </div> <div> <span className="text-foreground text-sm font-medium">Dedicated Cloud Storage</span> </div> </div> <div className="text-right"> <span className="text-foreground text-base font-semibold tabular-nums"> {currentStorageTier.label} </span> <span className="text-muted-foreground text-xs"> (+${currentStorageTier.cost}/mo)</span> </div> </div> <div className="pb-6"> <Slider value={[storageIndex]} min={0} max={STORAGE_TIERS.length - 1} step={1} marks={STORAGE_MARKS} tooltip={(idx) => STORAGE_TIERS[idx]?.label ?? ''} onValueChange={(val) => setStorageIndex(val[0])} /> </div> <p className="text-muted-foreground text-xs"> Encrypted at rest (AES-256) with multi-region automated replication and daily disaster backups. </p> </div> </CardContent> </Card> <Card> <CardHeader> <CardTitle>Enterprise Add-ons</CardTitle> <CardDescription> Enhance your infrastructure with enterprise compliance, reliability, and support. </CardDescription> </CardHeader> <CardContent className="space-y-3"> <div className="border-border bg-card hover:border-primary/30 flex flex-col justify-between gap-4 rounded-lg border p-4 transition-colors sm:flex-row sm:items-center"> <div className="flex items-start gap-3"> <div className="bg-primary/10 text-primary border-primary/20 mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-md border"> <Headphones className="size-4" /> </div> <div className="space-y-0.5"> <div className="flex items-center gap-2"> <span className="text-foreground text-sm font-medium">Dedicated Support Manager</span> <Badge variant="outline" className="text-xs font-normal"> +$200/mo </Badge> </div> <p className="text-muted-foreground text-xs"> Direct Slack channel, named technical account manager & 1-hour response SLA. </p> </div> </div> <div className="flex sm:justify-end"> <Switch checked={addonSupport} onCheckedChange={setAddonSupport} /> </div> </div> <div className="border-border bg-card hover:border-primary/30 flex flex-col justify-between gap-4 rounded-lg border p-4 transition-colors sm:flex-row sm:items-center"> <div className="flex items-start gap-3"> <div className="bg-primary/10 text-primary border-primary/20 mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-md border"> <ShieldCheck className="size-4" /> </div> <div className="space-y-0.5"> <div className="flex items-center gap-2"> <span className="text-foreground text-sm font-medium">Custom SLA Guarantee</span> <Badge variant="outline" className="text-xs font-normal"> +$500/mo </Badge> </div> <p className="text-muted-foreground text-xs"> 99.99% uptime guarantee with financial commitments and priority disaster recovery. </p> </div> </div> <div className="flex sm:justify-end"> <Switch checked={addonSla} onCheckedChange={setAddonSla} /> </div> </div> <div className="border-border bg-card hover:border-primary/30 flex flex-col justify-between gap-4 rounded-lg border p-4 transition-colors sm:flex-row sm:items-center"> <div className="flex items-start gap-3"> <div className="bg-primary/10 text-primary border-primary/20 mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-md border"> <KeyRound className="size-4" /> </div> <div className="space-y-0.5"> <div className="flex items-center gap-2"> <span className="text-foreground text-sm font-medium">Single Sign-On (SSO / SAML)</span> <Badge variant="outline" className="text-xs font-normal"> +$100/mo </Badge> </div> <p className="text-muted-foreground text-xs"> Okta, Azure AD, Google Workspace, and SAML 2.0 enterprise identity integration. </p> </div> </div> <div className="flex sm:justify-end"> <Switch checked={addonSso} onCheckedChange={setAddonSso} /> </div> </div> </CardContent> </Card> </div> <div className="lg:sticky lg:top-8 lg:col-span-5"> <Card className="border-border bg-card shadow-xs"> <CardHeader className="pb-4"> <div className="flex items-center justify-between"> <span className="text-muted-foreground text-xs font-semibold tracking-wider uppercase"> Estimated Investment </span> <Badge variant={recommendedPlan.variant} className="gap-1 shadow-xs"> {recommendedPlan.name === 'Enterprise' && <ShieldCheck className="size-3" />} {recommendedPlan.badge} </Badge> </div> <div className="mt-4"> <div className="flex items-baseline gap-1.5"> <span className="text-foreground text-4xl font-bold tracking-tight tabular-nums sm:text-5xl"> ${totalMonthly} </span> <span className="text-muted-foreground text-sm font-normal"> / month</span> </div> {isAnnual ? ( <p className="text-muted-foreground mt-2 text-xs"> Billed annually (${totalAnnual.toLocaleString('en-US')}/yr) · <span className="text-success font-semibold"> {' '} Save ${annualSavings.toLocaleString('en-US')}/yr </span> </p> ) : ( <p className="text-muted-foreground mt-2 text-xs"> Billed monthly · Switch to annual to save 20% (${annualSavings.toLocaleString('en-US')}/yr) </p> )} </div> </CardHeader> <CardContent className="space-y-4"> <Separator /> <div className="space-y-2.5"> <div className="flex items-center justify-between text-sm"> <span className="text-muted-foreground">Base platform ({recommendedPlan.name})</span> <span className="font-medium tabular-nums"> ${isAnnual ? Math.round(baseCost * 0.8) : baseCost}/mo </span> </div> <div className="flex items-center justify-between text-sm"> <span className="text-muted-foreground"> Team seats ({seats} × ${SEAT_PRICE}) </span> <span className="font-medium tabular-nums"> ${isAnnual ? Math.round(seatsCost * 0.8) : seatsCost}/mo </span> </div> <div className="flex items-center justify-between text-sm"> <span className="text-muted-foreground">API throughput ({currentRequestTier.label})</span> <span className="font-medium tabular-nums"> {requestsCost === 0 ? 'Included' : `$${isAnnual ? Math.round(requestsCost * 0.8) : requestsCost}/mo`} </span> </div> <div className="flex items-center justify-between text-sm"> <span className="text-muted-foreground">Cloud storage ({currentStorageTier.label})</span> <span className="font-medium tabular-nums"> ${isAnnual ? Math.round(storageCost * 0.8) : storageCost}/mo </span> </div> <div className="flex items-center justify-between text-sm"> <span className="text-muted-foreground">Add-ons ({activeAddonsCount} active)</span> <span className="font-medium tabular-nums"> {addonsCost === 0 ? '$0/mo' : `$${isAnnual ? Math.round(addonsCost * 0.8) : addonsCost}/mo`} </span> </div> {isAnnual && ( <div className="bg-success/10 text-success flex items-center justify-between rounded-md px-2.5 py-1.5 text-xs font-medium"> <span>Annual discount applied</span> <span className="font-semibold tabular-nums">20% off</span> </div> )} </div> <Separator /> <ul className="text-muted-foreground space-y-2 text-xs"> <li className="flex items-center gap-2"> <Check className="text-primary size-3.5 shrink-0" /> <span>14-day fully featured free trial</span> </li> <li className="flex items-center gap-2"> <Check className="text-primary size-3.5 shrink-0" /> <span>No credit card required upfront</span> </li> <li className="flex items-center gap-2"> <Check className="text-primary size-3.5 shrink-0" /> <span>Zero-downtime migration assistance</span> </li> </ul> </CardContent> <CardFooter className="flex flex-col gap-2.5 pt-2"> <Button className="w-full gap-2 font-semibold shadow-xs" size="lg"> Start 14-Day Free Trial <ArrowRight className="size-4" /> </Button> <Button variant="outline" className="w-full" size="default"> Request Custom Quote </Button> </CardFooter> </Card> </div> </div> </div> ) }components/blocks/pricing/PricingEnterpriseSlaCard.tsx8.8 kB
'use client' import * as React from 'react' import { ArrowRight, Building2, Calendar, CheckCircle2, Lock, Server } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { cn } from '@/lib/utils' export interface SlaGuarantee { title: string metric: string description: string icon: string } export interface ComplianceCert { id: string name: string status: string badgeVariant?: 'default' | 'outline' | 'secondary' } export interface PricingEnterpriseSlaCardProps { title?: string description?: string guarantees?: SlaGuarantee[] certifications?: ComplianceCert[] className?: string } const DEFAULT_GUARANTEES: SlaGuarantee[] = [ { title: 'High-Availability SLA', metric: '99.999%', description: 'Financial penalty-backed monthly uptime commitment across multi-region clusters.', icon: 'ShieldCheck', }, { title: 'P1 Incident Response', metric: '< 15 mins', description: 'Direct paging to designated Staff Infrastructure Commanders 24/7/365.', icon: 'Headphones', }, { title: 'Global Edge TTFB', metric: '< 12ms', description: 'Sub-15ms Time-To-First-Byte guaranteed via Anycast network mesh.', icon: 'Zap', }, { title: 'Custom Legal & DPA', metric: 'Bespoke', description: 'Dedicated legal counsel review, redline allowances, and customized BAAs.', icon: 'Scale', }, ] const DEFAULT_CERTS: ComplianceCert[] = [ { id: 'soc2', name: 'SOC 2 Type II Certified', status: 'Continuous Audit' }, { id: 'hipaa', name: 'HIPAA Compliant BAA', status: 'Available' }, { id: 'iso27001', name: 'ISO/IEC 27001:2022', status: 'Certified' }, { id: 'gdpr', name: 'GDPR & CCPA Verified', status: 'Compliant' }, { id: 'fedramp', name: 'FedRAMP In-Process', status: 'High Baseline' }, ] export function PricingEnterpriseSlaCard({ title = 'Mission-critical infrastructure with contractual legal guarantees.', description = 'Tailored enterprise licensing, custom security reviews, isolated VPC deployments, and white-glove migration engineering.', guarantees = DEFAULT_GUARANTEES, certifications = DEFAULT_CERTS, className, }: PricingEnterpriseSlaCardProps) { const [isMeetingRequested, setIsMeetingRequested] = React.useState(false) function requestMeeting() { setIsMeetingRequested(true) setTimeout(() => { setIsMeetingRequested(false) }, 3000) } return ( <section data-slot="pricing-enterprise-sla-card" className={cn('bg-background relative overflow-hidden py-16 sm:py-24', className)} > <div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"> {/* Section Header */} <div className="mx-auto max-w-3xl space-y-4 text-center"> <a href="#enterprise-contract" className="group border-border/80 bg-secondary/60 hover:bg-secondary text-foreground inline-flex items-center gap-2 rounded-full border px-3.5 py-1 text-xs font-medium shadow-2xs transition-colors" > <Building2 className="text-primary size-3.5" /> <span>Enterprise Custom Contracting</span> <ArrowRight className="text-muted-foreground size-3 transition-transform group-hover:translate-x-0.5" /> </a> <h2 className="text-foreground text-3xl font-bold tracking-tight sm:text-4xl">{title}</h2> <p className="text-muted-foreground text-base sm:text-lg">{description}</p> </div> {/* Main Enterprise Showcase Container */} <div className="border-border bg-card mt-12 overflow-hidden rounded-2xl border shadow-sm"> <div className="divide-border grid grid-cols-1 divide-y lg:grid-cols-12 lg:divide-x lg:divide-y-0"> {/* Left: SLA Guarantees & Contractual Commitments (7 Cols) */} <div className="space-y-8 p-8 lg:col-span-7"> <div className="space-y-1"> <div className="text-primary text-xs font-bold tracking-wider uppercase">Service Level Agreement</div> <h3 className="text-foreground text-xl font-bold">Penalty-Backed Contractual Metrics</h3> <p className="text-muted-foreground text-xs"> Every commitment is codified into your master service agreement with direct financial remedies. </p> </div> {/* Guarantees 2x2 Grid */} <div className="grid grid-cols-1 gap-4 sm:grid-cols-2"> {guarantees.map((item, idx) => ( <div key={idx} className="border-border bg-muted/20 space-y-2 rounded-xl border p-4"> <div className="flex items-center justify-between"> <span className="text-foreground text-xs font-bold">{item.title}</span> <span className="text-primary font-mono text-xs font-bold">{item.metric}</span> </div> <p className="text-muted-foreground text-xs leading-relaxed">{item.description}</p> </div> ))} </div> {/* Compliance & Governance Strip */} <div className="space-y-3 pt-2"> <div className="text-muted-foreground flex items-center gap-2 text-xs font-bold tracking-wider uppercase"> <Lock className="text-primary size-3.5" /> <span>Security & Regulatory Attestations</span> </div> <div className="flex flex-wrap gap-2"> {certifications.map((cert) => ( <Badge key={cert.id} variant="outline" className="border-border bg-background text-foreground gap-1.5 px-3 py-1 text-xs font-medium" > <CheckCircle2 className="text-success size-3" /> <span>{cert.name}</span> <span className="text-muted-foreground font-mono text-xs">({cert.status})</span> </Badge> ))} </div> </div> </div> {/* Right: Direct Enterprise Solution Consultation Card (5 Cols) */} <div className="bg-muted/30 flex flex-col justify-between space-y-6 p-8 lg:col-span-5"> <div className="space-y-4"> <div className="border-border flex items-center justify-between border-b pb-3"> <div className="flex items-center gap-2"> <Server className="text-primary size-4" /> <span className="text-foreground text-sm font-semibold">Custom Private Deployment</span> </div> <Badge variant="outline" className="border-success/30 bg-success/10 text-success font-mono text-xs"> Tailored </Badge> </div> <div className="space-y-2.5"> <div className="text-foreground text-xs font-bold">Included with Custom Tier:</div> <ul className="text-muted-foreground space-y-2 text-xs"> <li className="flex items-center gap-2"> <CheckCircle2 className="text-success size-3.5 shrink-0" /> <span>Dedicated AWS / GCP VPC peering or self-hosted air-gap</span> </li> <li className="flex items-center gap-2"> <CheckCircle2 className="text-success size-3.5 shrink-0" /> <span>Custom SAML 2.0 / Okta / Azure AD SCIM provisioning</span> </li> <li className="flex items-center gap-2"> <CheckCircle2 className="text-success size-3.5 shrink-0" /> <span>Dedicated Solution Architect & design system migration team</span> </li> <li className="flex items-center gap-2"> <CheckCircle2 className="text-success size-3.5 shrink-0" /> <span>Invoiced payment via ACH, Wire Transfer, or AWS Marketplace</span> </li> </ul> </div> </div> {/* Action Block */} <div className="border-border space-y-3 border-t pt-4"> <Button className="w-full gap-2 shadow-xs" size="lg" onClick={requestMeeting}> <Calendar className="size-4" /> <span> {isMeetingRequested ? 'Direct Routing to Architect...' : 'Book Enterprise Technical Review'} </span> </Button> <div className="text-muted-foreground text-center text-xs"> Average executive response time: <strong>under 20 minutes</strong> </div> </div> </div> </div> </div> </div> </section> ) }components/blocks/pricing/PricingFeatureAddonBuilder.tsx15.6 kB
'use client' import * as React from 'react' import { ArrowRight, Calculator, Check, CreditCard } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card, CardContent } from '@/components/ui/card' import { cn } from '@/lib/utils' export interface PricingPlan { id: string name: string monthlyPrice: number description: string includedSeats: number } export interface PricingAddon { id: string name: string monthlyPrice: number description: string category: string } export interface PricingFeatureAddonBuilderProps { title?: string description?: string plans?: PricingPlan[] addons?: PricingAddon[] className?: string } const DEFAULT_PLANS: PricingPlan[] = [ { id: 'starter', name: 'Starter Tier', monthlyPrice: 29, description: 'For indie developers and early-stage prototypes needing unbundled speed.', includedSeats: 2, }, { id: 'growth', name: 'Growth Core', monthlyPrice: 99, description: 'For scaling product engineering teams building high-conversion platforms.', includedSeats: 5, }, { id: 'scale', name: 'Scale Enterprise', monthlyPrice: 299, description: 'For mission-critical production clusters requiring sub-10ms global edge delivery.', includedSeats: 15, }, ] const DEFAULT_ADDONS: PricingAddon[] = [ { id: 'dedicated-ip', name: 'Dedicated Static Edge IP', monthlyPrice: 49, description: 'Static IPv4/IPv6 address allocations with zero-reputation penalty.', category: 'Network', }, { id: 'audit-logs', name: 'Immutable SOC2 Audit Logs', monthlyPrice: 79, description: 'Cryptographically signed telemetry logs with 365-day cold storage retention.', category: 'Security', }, { id: 'multi-region', name: 'Multi-Region Active-Active Mesh', monthlyPrice: 129, description: 'Synchronized cross-continental database replicas and automatic DNS failover.', category: 'Reliability', }, { id: 'priority-sla', name: '1-Hour Enterprise Response SLA', monthlyPrice: 199, description: 'Direct Slack / Discord hotline with senior design engineering staff.', category: 'Support', }, ] export function PricingFeatureAddonBuilder({ title = 'Build your custom plan with transparent, zero-surprise pricing.', description = 'Select your base tier, adjust seat allocations, and toggle modular enterprise add-ons with real-time invoice calculations.', plans = DEFAULT_PLANS, addons = DEFAULT_ADDONS, className, }: PricingFeatureAddonBuilderProps) { const [selectedPlanId, setSelectedPlanId] = React.useState('growth') const [selectedAddonIds, setSelectedAddonIds] = React.useState<string[]>(['dedicated-ip', 'audit-logs']) const [seatCount, setSeatCount] = React.useState(8) const [isAnnual, setIsAnnual] = React.useState(true) const selectedPlan = React.useMemo(() => { return plans.find((p) => p.id === selectedPlanId) || plans[0] }, [plans, selectedPlanId]) const extraSeats = React.useMemo(() => { return Math.max(0, seatCount - selectedPlan.includedSeats) }, [seatCount, selectedPlan]) const extraSeatCost = React.useMemo(() => extraSeats * 15, [extraSeats]) const totalAddonsCost = React.useMemo(() => { return addons.filter((a) => selectedAddonIds.includes(a.id)).reduce((sum, a) => sum + a.monthlyPrice, 0) }, [addons, selectedAddonIds]) const monthlySubtotal = React.useMemo(() => { return selectedPlan.monthlyPrice + extraSeatCost + totalAddonsCost }, [selectedPlan, extraSeatCost, totalAddonsCost]) const finalMonthlyRate = React.useMemo(() => { if (isAnnual) { return Math.round(monthlySubtotal * 0.8) } return monthlySubtotal }, [isAnnual, monthlySubtotal]) const annualSavings = React.useMemo(() => { return (monthlySubtotal - Math.round(monthlySubtotal * 0.8)) * 12 }, [monthlySubtotal]) function toggleAddon(addonId: string) { if (selectedAddonIds.includes(addonId)) { setSelectedAddonIds(selectedAddonIds.filter((id) => id !== addonId)) } else { setSelectedAddonIds([...selectedAddonIds, addonId]) } } return ( <section data-slot="pricing-feature-addon-builder" className={cn('bg-background relative overflow-hidden py-16 sm:py-24', className)} > <div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"> {/* Section Header */} <div className="mx-auto max-w-3xl space-y-4 text-center"> <a href="#pricing-calculator" className="group border-border/80 bg-secondary/60 hover:bg-secondary text-foreground inline-flex items-center gap-2 rounded-full border px-3.5 py-1 text-xs font-medium shadow-2xs transition-colors" > <Calculator className="text-primary size-3.5" /> <span>Real-time Add-on Cost Synthesizer</span> <ArrowRight className="text-muted-foreground size-3 transition-transform group-hover:translate-x-0.5" /> </a> <h2 className="text-foreground text-3xl font-bold tracking-tight sm:text-4xl">{title}</h2> <p className="text-muted-foreground text-base sm:text-lg">{description}</p> {/* Billing Cadence Toggle */} <div className="flex items-center justify-center gap-3 pt-2"> <span className={cn( 'text-xs font-medium', !isAnnual ? 'text-foreground font-semibold' : 'text-muted-foreground', )} > Monthly Billing </span> <button type="button" className={cn( 'focus-visible:ring-ring relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus-visible:ring-2 focus-visible:outline-none', isAnnual ? 'bg-primary' : 'bg-muted', )} onClick={() => setIsAnnual(!isAnnual)} > <span className={cn( 'bg-background pointer-events-none inline-block size-5 transform rounded-full shadow-lg ring-0 transition duration-200 ease-in-out', isAnnual ? 'translate-x-5' : 'translate-x-0', )} /> </button> <span className={cn( 'flex items-center gap-1.5 text-xs font-medium', isAnnual ? 'text-foreground font-semibold' : 'text-muted-foreground', )} > <span>Annual Billing</span> <Badge variant="outline" className="border-success/30 bg-success/10 text-success text-xs"> Save 20% </Badge> </span> </div> </div> {/* Add-on Builder Workbench */} <div className="mt-12 grid grid-cols-1 gap-6 lg:grid-cols-12"> {/* Left: Plan Selection & Addon Toggles (7 Cols) */} <div className="space-y-6 lg:col-span-7"> {/* Step 1: Base Tier Cards */} <div className="space-y-3"> <div className="text-muted-foreground text-xs font-bold tracking-wider uppercase"> Step 1: Choose Base Core Tier </div> <div className="grid grid-cols-1 gap-3 sm:grid-cols-3"> {plans.map((plan) => ( <button key={plan.id} type="button" className={cn( 'flex flex-col justify-between rounded-xl border p-4 text-left transition-colors', selectedPlanId === plan.id ? 'border-primary bg-primary/5 ring-primary/20 shadow-xs ring-1' : 'border-border bg-card hover:bg-muted/40 text-muted-foreground hover:text-foreground', )} onClick={() => setSelectedPlanId(plan.id)} > <div> <div className="text-foreground text-xs font-bold">{plan.name}</div> <div className="text-foreground mt-1 font-mono text-lg font-bold"> ${plan.monthlyPrice} <span className="text-muted-foreground text-xs font-normal">/mo</span> </div> </div> <div className="text-muted-foreground mt-2 text-xs"> Includes {plan.includedSeats} engineer seats </div> </button> ))} </div> </div> {/* Step 2: Seat Allocation Slider */} <Card className="border-border bg-card/60 shadow-2xs"> <CardContent className="space-y-2.5 p-4"> <div className="flex items-center justify-between text-xs"> <span className="text-foreground font-bold">Engineer Team Seats</span> <span className="text-foreground font-mono text-sm font-bold"> {seatCount} seats (${extraSeatCost}/mo extra) </span> </div> <input type="range" min="2" max="50" step="1" value={seatCount} onChange={(e) => setSeatCount(parseInt(e.target.value))} className="accent-primary w-full cursor-pointer" /> <div className="text-muted-foreground flex justify-between font-mono text-xs"> <span>2 seats</span> <span>50 seats</span> </div> </CardContent> </Card> {/* Step 3: Enterprise Add-ons Checklist */} <div className="space-y-3"> <div className="text-muted-foreground text-xs font-bold tracking-wider uppercase"> Step 3: Select Modular Capabilities </div> <div className="grid grid-cols-1 gap-2.5"> {addons.map((addon) => ( <button key={addon.id} type="button" className={cn( 'flex items-center justify-between rounded-xl border p-3.5 text-left transition-colors', selectedAddonIds.includes(addon.id) ? 'border-primary/60 bg-primary/5 shadow-2xs' : 'border-border bg-card hover:bg-muted/30 text-muted-foreground hover:text-foreground', )} onClick={() => toggleAddon(addon.id)} > <div className="flex min-w-0 items-center gap-3"> <div className={cn( 'flex size-5 shrink-0 items-center justify-center rounded border transition-colors', selectedAddonIds.includes(addon.id) ? 'border-primary bg-primary text-primary-foreground' : 'border-border bg-background', )} > {selectedAddonIds.includes(addon.id) ? <Check className="size-3.5" /> : null} </div> <div className="min-w-0 space-y-0.5"> <div className="flex items-center gap-2"> <span className="text-foreground text-xs font-bold">{addon.name}</span> <Badge variant="outline" className="border-border text-muted-foreground text-xs"> {addon.category} </Badge> </div> <div className="text-muted-foreground truncate text-xs">{addon.description}</div> </div> </div> <div className="text-foreground shrink-0 pl-2 font-mono text-xs font-bold"> +${addon.monthlyPrice} <span className="text-muted-foreground text-xs font-normal">/mo</span> </div> </button> ))} </div> </div> </div> {/* Right: Real-time Invoice Estimate Card (5 Cols) */} <div className="lg:col-span-5"> <Card className="border-border bg-card/90 sticky top-8 shadow-md backdrop-blur-xs"> <CardContent className="space-y-6 p-6"> <div className="border-border flex items-center justify-between border-b pb-3"> <div className="flex items-center gap-2"> <CreditCard className="text-primary size-4" /> <span className="text-foreground text-sm font-semibold">Estimated Monthly Invoice</span> </div> <Badge variant="outline" className="border-border text-primary font-mono text-xs"> {isAnnual ? 'Annualized' : 'Monthly'} </Badge> </div> {/* Price Breakdown List */} <div className="space-y-3 text-xs"> <div className="text-muted-foreground flex justify-between"> <span>{selectedPlan.name}</span> <span className="text-foreground font-mono">${selectedPlan.monthlyPrice}.00</span> </div> {extraSeats > 0 ? ( <div className="text-muted-foreground flex justify-between"> <span>Extra Seats ({extraSeats} × $15)</span> <span className="text-foreground font-mono">${extraSeatCost}.00</span> </div> ) : null} {addons .filter((a) => selectedAddonIds.includes(a.id)) .map((addon) => ( <div key={addon.id} className="text-muted-foreground flex justify-between"> <span className="truncate pr-2">{addon.name}</span> <span className="text-foreground font-mono">${addon.monthlyPrice}.00</span> </div> ))} {isAnnual ? ( <div className="border-border/60 text-success flex justify-between border-t pt-2 font-medium"> <span>Annual Billing Discount (20%)</span> <span className="font-mono">−${monthlySubtotal - finalMonthlyRate}.00</span> </div> ) : null} </div> {/* Total Sum Band */} <div className="border-border bg-muted/40 space-y-1 rounded-lg border p-4"> <div className="text-muted-foreground text-xs font-medium">Net Monthly Investment</div> <div className="flex items-baseline gap-1.5"> <span className="text-foreground font-mono text-3xl font-bold tracking-tight"> ${finalMonthlyRate} </span> <span className="text-muted-foreground font-mono text-xs">/ month</span> </div> {isAnnual ? ( <div className="text-success text-xs font-medium"> Billed annually (${finalMonthlyRate * 12}/yr • Save ${annualSavings}/yr) </div> ) : null} </div> <Button className="w-full gap-2 shadow-xs" size="lg"> <span>Start 14-Day Free Evaluation</span> <ArrowRight className="size-4" /> </Button> </CardContent> </Card> </div> </div> </div> </section> ) }components/blocks/pricing/PricingFeatureTierSlider.tsx8.8 kB
'use client' import * as React from 'react' import { ArrowRight, CheckCircle2, Sliders } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card, CardContent } from '@/components/ui/card' import { cn } from '@/lib/utils' export interface PricingFeatureTierSliderProps { title?: string description?: string className?: string } const TIERS = [ { label: '10K MAU', mauCount: '10,000', basePriceMonthly: 19, tierName: 'Starter Hobbyist', features: [ '10,000 Monthly Users', '2 Dedicated Edge Locations', 'Community Discord SLA', 'Standard 1-Day Log Retention', ], }, { label: '50K MAU', mauCount: '50,000', basePriceMonthly: 49, tierName: 'Pro Creator', features: [ '50,000 Monthly Users', '12 Global Anycast Edges', 'Next-Business-Day Support SLA', '7-Day Immutable Log Retention', ], }, { label: '250K MAU', mauCount: '250,000', basePriceMonthly: 149, tierName: 'Growth Scale', features: [ '250,000 Monthly Users', 'All 36 Global Edge Regions', '4-Hour Priority Engineering SLA', '30-Day SOC2 Audit Logs', ], }, { label: '1M MAU', mauCount: '1,000,000', basePriceMonthly: 399, tierName: 'Enterprise Core', features: [ '1,000,000 Monthly Users', 'Dedicated VPC & Multi-Region Mesh', '15-Minute Critical Incident SLA', '365-Day Cold Storage Logs', ], }, { label: '5M+ MAU', mauCount: '5,000,000+', basePriceMonthly: 899, tierName: 'Hyperscale Cluster', features: [ '5,000,000+ Monthly Users', 'Custom Bare-Metal Cloud Partitions', 'Dedicated Solutions Architect', 'Custom Security Review & BAA', ], }, ] export function PricingFeatureTierSlider({ title = 'Predictable usage pricing with dynamic linear scale.', description = 'Slide to your estimated monthly active users or edge invocations to calculate your exact monthly investment.', className, }: PricingFeatureTierSliderProps) { const [sliderIndex, setSliderIndex] = React.useState(2) const [isAnnual, setIsAnnual] = React.useState(true) const currentTier = TIERS[sliderIndex] const effectivePrice = isAnnual ? Math.round(currentTier.basePriceMonthly * 0.8) : currentTier.basePriceMonthly const annualSavings = (currentTier.basePriceMonthly - effectivePrice) * 12 return ( <section data-slot="pricing-feature-tier-slider" className={cn('bg-background relative overflow-hidden py-16 sm:py-24', className)} > <div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"> {/* Section Header */} <div className="mx-auto max-w-3xl space-y-4 text-center"> <a href="#scale-pricing" className="group border-border/80 bg-secondary/60 hover:bg-secondary text-foreground inline-flex items-center gap-2 rounded-full border px-3.5 py-1 text-xs font-medium shadow-2xs transition-colors" > <Sliders className="text-primary size-3.5" /> <span>Continuous Scale Synthesizer</span> <ArrowRight className="text-muted-foreground size-3 transition-transform group-hover:translate-x-0.5" /> </a> <h2 className="text-foreground text-3xl font-bold tracking-tight sm:text-4xl">{title}</h2> <p className="text-muted-foreground text-base sm:text-lg">{description}</p> {/* Cadence Switcher */} <div className="flex items-center justify-center gap-3 pt-2"> <span className={cn( 'text-xs font-medium', !isAnnual ? 'text-foreground font-semibold' : 'text-muted-foreground', )} > Monthly </span> <button type="button" className={cn( 'relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out', isAnnual ? 'bg-primary' : 'bg-muted', )} onClick={() => setIsAnnual(!isAnnual)} > <span className={cn( 'bg-background pointer-events-none inline-block size-5 transform rounded-full shadow-lg ring-0 transition duration-200 ease-in-out', isAnnual ? 'translate-x-5' : 'translate-x-0', )} /> </button> <span className={cn( 'flex items-center gap-1.5 text-xs font-medium', isAnnual ? 'text-foreground font-semibold' : 'text-muted-foreground', )} > <span>Annual Billing</span> <Badge variant="outline" className="border-success/30 bg-success/10 text-success text-xs"> Save 20% </Badge> </span> </div> </div> {/* Pricing Slider Main Card */} <div className="mx-auto mt-12 max-w-4xl"> <Card className="border-border bg-card overflow-hidden shadow-sm"> <CardContent className="space-y-8 p-8"> {/* Scale Indicator Bar */} <div className="border-border flex flex-wrap items-center justify-between gap-4 border-b pb-6"> <div className="space-y-1"> <span className="text-muted-foreground text-xs font-bold tracking-wider uppercase"> Calculated Tier Plan </span> <h3 className="text-foreground text-2xl font-bold">{currentTier.tierName}</h3> <div className="text-muted-foreground text-xs"> Engineered for {currentTier.mauCount} active user sessions </div> </div> {/* Price Box */} <div className="text-right"> <div className="flex items-baseline justify-end gap-1"> <span className="text-foreground font-mono text-4xl font-bold">${effectivePrice}</span> <span className="text-muted-foreground font-mono text-xs">/ month</span> </div> {isAnnual ? ( <div className="text-success font-mono text-xs font-medium">Save ${annualSavings}/yr on annual</div> ) : null} </div> </div> {/* Interactive Stepped Range Slider */} <div className="space-y-3"> <div className="text-foreground flex items-center justify-between text-xs font-bold"> <span>Monthly Traffic Volume</span> <span className="text-primary font-mono font-bold">{currentTier.mauCount} Users</span> </div> <input type="range" min="0" max="4" step="1" value={sliderIndex} onChange={(e) => setSliderIndex(parseInt(e.target.value))} className="accent-primary h-2 w-full cursor-pointer" /> {/* Slider Step Labels */} <div className="text-muted-foreground flex justify-between font-mono text-xs"> {TIERS.map((t, idx) => ( <span key={idx} className={cn( 'hover:text-foreground cursor-pointer transition-colors', sliderIndex === idx ? 'text-primary font-bold' : '', )} onClick={() => setSliderIndex(idx)} > {t.label} </span> ))} </div> </div> {/* Features Checklist Grid for Current Tier */} <div className="border-border space-y-3 border-t pt-4"> <span className="text-muted-foreground text-xs font-bold tracking-wider uppercase"> Guaranteed Tier Deliverables </span> <div className="grid grid-cols-1 gap-2.5 sm:grid-cols-2"> {currentTier.features.map((feat, fIdx) => ( <div key={fIdx} className="text-foreground flex items-center gap-2 text-xs"> <CheckCircle2 className="text-success size-4 shrink-0" /> <span>{feat}</span> </div> ))} </div> </div> {/* Action Button */} <div className="pt-2"> <Button size="lg" className="w-full gap-2 shadow-xs"> <span>Deploy with {currentTier.tierName}</span> <ArrowRight className="size-4" /> </Button> </div> </CardContent> </Card> </div> </div> </section> ) }components/blocks/pricing/PricingMatrixGrouped.tsx5.8 kB
'use client' import { useState } from 'react' import { Check, ChevronDown, Minus } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card, CardContent } from '@/components/ui/card' import { Separator } from '@/components/ui/separator' import { Table, TableBody, TableCell, TableRow } from '@/components/ui/table' const plans = ['Team', 'Business', 'Enterprise'] const groups: { id: string label: string summary: string rows: { feature: string; values: (boolean | string)[] }[] }[] = [ { id: 'modelling', label: 'Modelling', summary: 'Definitions, versioning, and review', rows: [ { feature: 'Certified definitions', values: ['50', '500', 'Unlimited'] }, { feature: 'Version history', values: ['30 days', 'Unlimited', 'Unlimited'] }, { feature: 'Required review', values: [false, true, true] }, { feature: 'Period locking', values: [false, true, true] }, ], }, { id: 'access', label: 'Access & identity', summary: 'Who sees which rows', rows: [ { feature: 'SSO (SAML)', values: [true, true, true] }, { feature: 'SCIM provisioning', values: [false, true, true] }, { feature: 'Row-level scope', values: [false, true, true] }, { feature: 'Customer-managed keys', values: [false, false, true] }, ], }, { id: 'ops', label: 'Operations', summary: 'Cost, alerting, and support', rows: [ { feature: 'Query budgets', values: [false, true, true] }, { feature: 'Drift alerting', values: [false, true, true] }, { feature: 'Audit log export', values: [false, '90 days', 'Unlimited'] }, { feature: 'Named support engineer', values: [false, false, true] }, ], }, ] export function PricingMatrixGrouped() { // Opens on the first group only: a fully expanded matrix is the thing readers // bounce off, and every group is one click from open. const [open, setOpen] = useState<string[]>([groups[0].id]) const toggle = (id: string) => setOpen((current) => (current.includes(id) ? current.filter((entry) => entry !== id) : [...current, id])) return ( <section data-slot="pricing-matrix-grouped" className="bg-background"> <div className="mx-auto max-w-4xl px-6 py-20 lg:py-28"> <Badge variant="secondary">Compare plans</Badge> <h2 className="mt-4 text-3xl font-semibold tracking-tight sm:text-4xl">Grouped, so it stays readable</h2> <Card className="mt-8"> <CardContent className="p-0"> <div className="text-muted-foreground grid grid-cols-[1fr_repeat(3,5rem)] gap-2 px-4 py-3 text-xs font-medium sm:grid-cols-[1fr_repeat(3,7rem)]"> <span>Capability</span> {plans.map((plan) => ( <span key={plan} className="text-center"> {plan} </span> ))} </div> <Separator /> {groups.map((group) => ( <div key={group.id}> <button type="button" className="hover:bg-muted focus-visible:ring-ring flex w-full items-center gap-3 px-4 py-3 text-left transition-colors focus-visible:ring-2 focus-visible:outline-none" aria-expanded={open.includes(group.id)} aria-controls={`group-${group.id}`} onClick={() => toggle(group.id)} > <ChevronDown className={`text-muted-foreground size-4 shrink-0 transition-transform ${ open.includes(group.id) ? '' : '-rotate-90' }`} aria-hidden="true" /> <span className="min-w-0"> <span className="block text-sm font-medium">{group.label}</span> <span className="text-muted-foreground block text-xs">{group.summary}</span> </span> <span className="text-muted-foreground ml-auto shrink-0 font-mono text-xs">{group.rows.length}</span> </button> {open.includes(group.id) && ( <div id={`group-${group.id}`}> <Table> <TableBody> {group.rows.map((row) => ( <TableRow key={row.feature}> <TableCell className="pl-11 text-sm">{row.feature}</TableCell> {row.values.map((value, index) => ( <TableCell key={index} className="w-20 text-center sm:w-28"> {value === true && ( <> <Check className="text-success mx-auto size-4" aria-hidden="true" /> <span className="sr-only">Included</span> </> )} {value === false && ( <> <Minus className="text-muted-foreground/50 mx-auto size-4" aria-hidden="true" /> <span className="sr-only">Not included</span> </> )} {typeof value === 'string' && <span className="font-mono text-xs">{value}</span>} </TableCell> ))} </TableRow> ))} </TableBody> </Table> </div> )} <Separator /> </div> ))} </CardContent> </Card> <Button className="mt-6">Start on Business</Button> </div> </section> ) }components/blocks/pricing/PricingMatrixPlanCards.tsx3.5 kB
'use client' import { Check, Minus } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card, CardContent } from '@/components/ui/card' import { Separator } from '@/components/ui/separator' const capabilities = [ 'Certified metric definitions', 'Connected warehouses', 'Query budget per team', 'Row-level access control', 'SCIM provisioning', 'Audit log export', 'Embedded dashboards', 'Data residency choice', 'Customer-managed keys', 'Named support engineer', ] const plans = [ { name: 'Team', price: '$0', cadence: 'while in beta', included: [0, 1], note: 'For one team proving it out.' }, { name: 'Business', price: '$1,400', cadence: 'per month', included: [0, 1, 2, 3, 4, 5, 6], note: 'For finance and analytics running together.', featured: true, }, { name: 'Enterprise', price: 'Custom', cadence: 'annual', included: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], note: 'For regulated estates and multi-region.', }, ] export function PricingMatrixPlanCards() { return ( <section data-slot="pricing-matrix-plan-cards" className="bg-background"> <div className="mx-auto max-w-6xl px-6 py-20 lg:py-28"> <div className="max-w-2xl"> <Badge variant="secondary">Plans</Badge> <h2 className="mt-4 text-3xl font-semibold tracking-tight sm:text-4xl">What each plan actually includes</h2> <p className="text-muted-foreground mt-3 text-lg"> The same list on every card, with what is missing shown rather than omitted. </p> </div> <div className="mt-10 grid gap-4 lg:grid-cols-3"> {plans.map((plan) => ( <Card key={plan.name} className={plan.featured ? 'border-primary' : undefined}> <CardContent className="flex h-full flex-col p-6"> <div className="flex items-center justify-between gap-3"> <p className="text-sm font-semibold">{plan.name}</p> {plan.featured && <Badge variant="secondary">Most chosen</Badge>} </div> <p className="font-display mt-4 text-3xl font-bold tracking-tight">{plan.price}</p> <p className="text-muted-foreground mt-1 text-xs">{plan.cadence}</p> <p className="text-muted-foreground mt-3 text-sm leading-relaxed">{plan.note}</p> <Separator className="my-5" /> <ul className="space-y-2.5"> {capabilities.map((capability, index) => ( <li key={capability} className={`flex items-start gap-2.5 text-sm ${ plan.included.includes(index) ? '' : 'text-muted-foreground/60' }`} > {plan.included.includes(index) ? ( <Check className="text-success mt-0.5 size-4 shrink-0" aria-hidden="true" /> ) : ( <Minus className="text-muted-foreground/40 mt-0.5 size-4 shrink-0" aria-hidden="true" /> )} <span>{capability}</span> </li> ))} </ul> <Button variant={plan.featured ? 'default' : 'outline'} className="mt-auto pt-0 [&]:mt-8"> {plan.name === 'Enterprise' ? 'Talk to us' : `Start on ${plan.name}`} </Button> </CardContent> </Card> ))} </div> </div> </section> ) }components/blocks/pricing/PricingMatrixStickyHeader.tsx4.1 kB
'use client' import { Check, Minus } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card, CardContent } from '@/components/ui/card' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' const plans = ['Team', 'Business', 'Enterprise'] // Cells are true / false / a string, so a plan can state a limit rather than // only claiming a feature exists. const rows: { feature: string; values: (boolean | string)[] }[] = [ { feature: 'Certified metric definitions', values: ['50', '500', 'Unlimited'] }, { feature: 'Connected warehouses', values: ['1', '3', 'Unlimited'] }, { feature: 'Query budget per team', values: [false, true, true] }, { feature: 'Row-level access control', values: [false, true, true] }, { feature: 'SCIM provisioning', values: [false, true, true] }, { feature: 'Audit log export', values: [false, '90 days', 'Unlimited'] }, { feature: 'Period locking', values: [false, true, true] }, { feature: 'Embedded dashboards', values: [false, true, true] }, { feature: 'Data residency choice', values: [false, false, true] }, { feature: 'Customer-managed keys', values: [false, false, true] }, { feature: 'Private link', values: [false, false, true] }, { feature: 'Named support engineer', values: [false, false, true] }, ] export function PricingMatrixStickyHeader() { return ( <section data-slot="pricing-matrix-sticky-header" className="bg-background"> <div className="mx-auto max-w-4xl px-6 py-20 lg:py-28"> <Badge variant="secondary">Compare plans</Badge> <h2 className="mt-4 text-3xl font-semibold tracking-tight sm:text-4xl">Every line, no asterisks</h2> <p className="text-muted-foreground mt-3 text-lg"> Limits differ between plans. Where a plan caps something, the cap is written in the cell. </p> <Card className="mt-8"> <CardContent className="p-0"> {/* The header pins with CSS alone. Thirty rows down, a checkmark is still attributable to a column without scrolling back up. */} <div className="max-h-[28rem] overflow-auto"> <Table> <TableHeader className="bg-card sticky top-0 z-10"> <TableRow> <TableHead className="bg-card">Capability</TableHead> {plans.map((plan) => ( <TableHead key={plan} className="bg-card text-center"> {plan} </TableHead> ))} </TableRow> </TableHeader> <TableBody> {rows.map((row) => ( <TableRow key={row.feature}> <TableCell className="font-medium">{row.feature}</TableCell> {row.values.map((value, index) => ( <TableCell key={index} className="text-center"> {value === true && ( <> <Check className="text-success mx-auto size-4" aria-hidden="true" /> <span className="sr-only">Included</span> </> )} {value === false && ( <> <Minus className="text-muted-foreground/50 mx-auto size-4" aria-hidden="true" /> <span className="sr-only">Not included</span> </> )} {typeof value === 'string' && <span className="font-mono text-xs">{value}</span>} </TableCell> ))} </TableRow> ))} </TableBody> </Table> </div> </CardContent> </Card> <div className="mt-6 flex flex-wrap gap-3"> <Button>Start on Team</Button> <Button variant="outline">Talk about Enterprise</Button> </div> </div> </section> ) }components/blocks/pricing/PricingSinglePlan.tsx3.6 kB
'use client' import { ArrowRight, Check, X } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card, CardContent } from '@/components/ui/card' import { Separator } from '@/components/ui/separator' const included = [ 'Unlimited certified metric definitions', 'Unlimited viewers and editors', 'Row-level access from your identity provider', 'Query budgets enforced per team', 'Audit log export, 7-year retention', 'SOC 2 report under NDA', ] // Naming exclusions is what makes a single plan credible. A list of only // inclusions invites the reader to assume the gap is hidden somewhere. const excluded = [ 'On-premise deployment behind your firewall', 'Custom SLAs below 99.9%', 'Professional services beyond the five-week rollout', ] export function PricingSinglePlan() { return ( <section data-slot="pricing-single-plan" className="bg-background"> <div className="mx-auto max-w-3xl px-6 py-20 lg:py-28"> <div className="text-center"> <Badge variant="secondary">Pricing</Badge> <h2 className="mt-4 text-3xl font-semibold tracking-tight text-balance sm:text-4xl">One plan, one number</h2> <p className="text-muted-foreground mt-3 text-lg"> No seat maths and no feature gates. The only thing that scales is query volume. </p> </div> <Card className="mt-10"> <CardContent className="p-8"> <div className="flex flex-wrap items-baseline gap-3"> <span className="font-display text-4xl font-bold tracking-tight">£1,400</span> <span className="text-muted-foreground">per month, billed annually</span> </div> <p className="text-muted-foreground mt-2 text-sm"> Includes 2 million queries a month. Beyond that it is £0.0004 per query, capped by your own budgets. </p> <Button size="lg" className="mt-6 w-full sm:w-auto"> Start the 30-day trial <ArrowRight className="ml-2 size-4" aria-hidden="true" /> </Button> <Separator className="my-8" /> <p className="text-muted-foreground font-mono text-xs tracking-[0.14em] uppercase">Included</p> <ul className="mt-3 grid gap-2.5 sm:grid-cols-2"> {included.map((item) => ( <li key={item} className="flex items-start gap-2.5 text-sm"> <Check className="text-success mt-0.5 size-4 shrink-0" aria-hidden="true" /> <span>{item}</span> </li> ))} </ul> <Separator className="my-6" /> <p className="text-muted-foreground font-mono text-xs tracking-[0.14em] uppercase">Not included</p> <ul className="mt-3 space-y-2.5"> {excluded.map((item) => ( <li key={item} className="flex items-start gap-2.5 text-sm"> <X className="text-muted-foreground/50 mt-0.5 size-4 shrink-0" aria-hidden="true" /> <span className="text-muted-foreground">{item}</span> </li> ))} </ul> <Separator className="my-6" /> <p className="text-muted-foreground text-sm leading-relaxed"> Past roughly 20 million queries a month, or if any of the exclusions above are hard requirements, a conversation will get you a better answer than this page can. </p> <Button variant="outline" className="mt-4"> Talk to us instead </Button> </CardContent> </Card> </div> </section> ) }components/blocks/pricing/PricingTierComparisonMatrix.tsx12.6 kB
'use client' import * as React from 'react' import { ArrowRight, Check, Minus, ShieldCheck } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card } from '@/components/ui/card' import { cn } from '@/lib/utils' type BillingCycle = 'monthly' | 'annual' interface Plan { id: string name: string description: string monthlyPrice: number annualPrice: number highlight: boolean badge?: string ctaText: string ctaVariant: 'default' | 'outline' | 'secondary' } const plans: Plan[] = [ { id: 'community', name: 'Community OSS', description: 'For indie hackers and developers building open source products.', monthlyPrice: 0, annualPrice: 0, highlight: false, ctaText: 'Start Building Free', ctaVariant: 'outline', }, { id: 'pro', name: 'Pro Team', description: 'For growing startup engineering teams that require high velocity.', monthlyPrice: 29, annualPrice: 24, highlight: true, badge: 'Most Popular', ctaText: 'Claim Pro License', ctaVariant: 'default', }, { id: 'enterprise', name: 'Enterprise Scale', description: 'Dedicated registry syncing, SSO, SOC2 compliance audits, and SLA.', monthlyPrice: 119, annualPrice: 99, highlight: false, ctaText: 'Talk to Sales', ctaVariant: 'outline', }, ] interface FeatureComparisonRow { category: string features: { name: string tooltip: string community: boolean | string pro: boolean | string enterprise: boolean | string }[] } const comparisonData: FeatureComparisonRow[] = [ { category: 'Registry & Core Primitives', features: [ { name: 'Full AST Component Source Access', tooltip: 'Raw SFC and TSX source files copied directly into your repository.', community: true, pro: true, enterprise: true, }, { name: 'Dual-Framework Parity (Vue + React)', tooltip: 'Identical DOM semantics and CVA tokens across both ecosystems.', community: true, pro: true, enterprise: true, }, { name: 'Curated Marketing & SaaS Blocks', tooltip: 'Access to 450+ production-grade unbundled layout blocks.', community: '100+ Blocks', pro: 'All 450+ Blocks', enterprise: 'All Blocks + Custom', }, { name: 'Tailwind CSS v4 OKLCH Token System', tooltip: 'Hardware-calibrated color spaces and spring curves.', community: true, pro: true, enterprise: true, }, ], }, { category: 'Enterprise & Security Compliance', features: [ { name: 'Private Registry Mirroring', tooltip: 'Host your organization’s customized internal registry behind a firewall.', community: false, pro: '1 Private Repo', enterprise: 'Unlimited Private Hubs', }, { name: 'SOC2 & ISO 27001 Audit Packs', tooltip: 'Pre-certified security documentation and architecture proofs.', community: false, pro: false, enterprise: true, }, { name: 'Guaranteed 99.99% Registry CDN SLA', tooltip: 'Global multi-region edge distribution uptime guarantee.', community: false, pro: '99.9% SLA', enterprise: '99.99% High Availability', }, { name: 'Dedicated Design Engineering Support', tooltip: 'Direct Slack / Discord channel with core design system maintainers.', community: false, pro: 'Priority Email', enterprise: 'Dedicated Slack Channel', }, ], }, ] export interface PricingTierComparisonMatrixProps { className?: string } export function PricingTierComparisonMatrix({ className }: PricingTierComparisonMatrixProps) { const [billing, setBilling] = React.useState<BillingCycle>('annual') return ( <section data-slot="pricing-tier-comparison-matrix" className={cn('bg-background relative overflow-hidden px-4 py-16 sm:px-6 sm:py-24 lg:px-8', className)} > <div className="mx-auto max-w-7xl space-y-16"> {/* Header */} <div className="mx-auto max-w-3xl space-y-4 text-center"> <Badge variant="secondary" className="gap-1.5 px-3 py-1 font-mono text-xs shadow-xs"> <ShieldCheck className="text-primary size-3.5" /> Predictable Pricing </Badge> <h2 className="text-foreground text-3xl font-bold tracking-tight sm:text-4xl"> Zero seat taxes. Own your source code forever. </h2> <p className="text-muted-foreground text-base"> Choose the tier that fits your engineering team's delivery scale and compliance needs. </p> {/* Billing Toggle */} <div className="flex items-center justify-center gap-3 pt-4"> <span className={cn( 'font-mono text-xs', billing === 'monthly' ? 'text-foreground font-bold' : 'text-muted-foreground', )} > Monthly </span> <div className="border-border bg-card relative flex items-center rounded-full border p-1"> <button type="button" className={cn( 'relative z-10 rounded-full px-3 py-1 font-mono text-xs transition-colors', billing === 'monthly' ? 'bg-primary text-primary-foreground font-semibold shadow-xs' : 'text-muted-foreground', )} onClick={() => setBilling('monthly')} > Monthly </button> <button type="button" className={cn( 'relative z-10 flex items-center gap-1.5 rounded-full px-3 py-1 font-mono text-xs transition-colors', billing === 'annual' ? 'bg-primary text-primary-foreground font-semibold shadow-xs' : 'text-muted-foreground', )} onClick={() => setBilling('annual')} > <span>Annual</span> <span className="bg-success rounded-full px-1.5 py-0.5 text-xs font-bold text-white">Save 20%</span> </button> </div> </div> </div> {/* Pricing Plan Cards Grid (3 Columns) */} <div className="grid grid-cols-1 items-stretch gap-6 md:grid-cols-3"> {plans.map((plan) => ( <Card key={plan.id} className={cn( 'border-border bg-card relative flex flex-col justify-between space-y-6 rounded-2xl p-6 text-left shadow-xl transition-colors sm:p-8', plan.highlight ? 'border-primary/80 ring-primary/20 scale-[1.02] shadow-sm ring-2' : 'hover:border-border/80', )} > {/* Top Badge */} {plan.badge && ( <div className="absolute -top-3 left-1/2 -translate-x-1/2"> <Badge className="px-3 py-0.5 font-mono text-xs tracking-wider uppercase shadow-md"> {plan.badge} </Badge> </div> )} <div className="space-y-4"> <div> <h3 className="text-foreground font-mono text-xl font-bold">{plan.name}</h3> <p className="text-muted-foreground mt-1 min-h-[36px] text-xs">{plan.description}</p> </div> {/* Price display */} <div className="flex items-baseline gap-1.5 font-mono"> <span className="text-foreground text-4xl font-bold"> ${billing === 'annual' ? plan.annualPrice : plan.monthlyPrice} </span> <span className="text-muted-foreground text-xs">/ month</span> </div> <p className="text-muted-foreground font-mono text-xs"> {billing === 'annual' && plan.annualPrice > 0 ? `Billed annually ($${plan.annualPrice * 12}/yr)` : 'Billed monthly'} </p> </div> <Button variant={plan.ctaVariant} className="h-10 w-full gap-1.5 font-mono text-xs shadow-xs"> <span>{plan.ctaText}</span> <ArrowRight className="size-3.5" /> </Button> </Card> ))} </div> {/* Deep Feature Comparison Matrix Table */} <div className="space-y-6"> <div className="text-center"> <h3 className="text-foreground font-mono text-xl font-bold">Detailed Feature Comparison</h3> <p className="text-muted-foreground mt-1 text-xs">Full granular matrix of capabilities and entitlements.</p> </div> <Card className="border-border bg-card overflow-hidden rounded-2xl text-left shadow-sm"> <div className="overflow-x-auto"> <table className="w-full border-collapse text-left text-xs"> <thead> <tr className="border-border bg-muted/40 text-muted-foreground border-b font-mono"> <th className="w-1/2 p-4 font-semibold">Capability</th> <th className="p-4 text-center font-semibold">Community</th> <th className="text-primary p-4 text-center font-bold font-semibold">Pro Team</th> <th className="p-4 text-center font-semibold">Enterprise</th> </tr> </thead> <tbody> {comparisonData.map((section, sIdx) => ( <React.Fragment key={sIdx}> <tr className="bg-muted/20 border-border/80 border-b"> <td colSpan={4} className="text-muted-foreground p-3 px-4 font-mono text-xs font-bold tracking-wider uppercase" > {section.category} </td> </tr> {section.features.map((row, rIdx) => ( <tr key={rIdx} className="border-border/60 hover:bg-muted/10 border-b transition-colors"> <td className="p-4"> <div className="text-foreground font-medium">{row.name}</div> <div className="text-muted-foreground mt-0.5 text-xs">{row.tooltip}</div> </td> <td className="p-4 text-center font-mono"> {typeof row.community === 'boolean' ? ( row.community ? ( <Check className="text-success mx-auto size-4" /> ) : ( <Minus className="text-muted-foreground/40 mx-auto size-4" /> ) ) : ( <span className="text-muted-foreground">{row.community}</span> )} </td> <td className="p-4 text-center font-mono font-semibold"> {typeof row.pro === 'boolean' ? ( row.pro ? ( <Check className="text-success mx-auto size-4" /> ) : ( <Minus className="text-muted-foreground/40 mx-auto size-4" /> ) ) : ( <span className="text-primary">{row.pro}</span> )} </td> <td className="p-4 text-center font-mono"> {typeof row.enterprise === 'boolean' ? ( row.enterprise ? ( <Check className="text-success mx-auto size-4" /> ) : ( <Minus className="text-muted-foreground/40 mx-auto size-4" /> ) ) : ( <span className="text-foreground font-semibold">{row.enterprise}</span> )} </td> </tr> ))} </React.Fragment> ))} </tbody> </table> </div> </Card> </div> </div> </section> ) } export default PricingTierComparisonMatrixcomponents/blocks/pricing/PricingUsageCalculatorSlider.tsx9.4 kB
'use client' import * as React from 'react' import { ArrowRight, Calculator, Globe, Server, Users } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card } from '@/components/ui/card' import { cn } from '@/lib/utils' export interface PricingUsageCalculatorSliderProps { className?: string } export function PricingUsageCalculatorSlider({ className }: PricingUsageCalculatorSliderProps) { const [mau, setMau] = React.useState(100000) const [qps, setQps] = React.useState(500) const [edgeReplicas, setEdgeReplicas] = React.useState(3) const formatNumber = (num: number): string => { if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M' if (num >= 1000) return (num / 1000).toFixed(0) + 'k' return num.toString() } const applyPreset = (preset: 'seed' | 'growth' | 'scale') => { if (preset === 'seed') { setMau(25000) setQps(150) setEdgeReplicas(2) } else if (preset === 'growth') { setMau(350000) setQps(2500) setEdgeReplicas(5) } else { setMau(2500000) setQps(12000) setEdgeReplicas(10) } } const traditionalCost = React.useMemo(() => { const baseSeatFee = 350 const perUserFee = (mau / 1000) * 1.8 const qpsSurcharge = qps * 0.25 return Math.round(baseSeatFee + perUserFee + qpsSurcharge) }, [mau, qps]) const uipkgeCost = React.useMemo(() => { const rawBandwidth = (mau / 100000) * 4 const flatProLicense = 24 return Math.round(flatProLicense + rawBandwidth) }, [mau]) const monthlySavings = Math.max(0, traditionalCost - uipkgeCost) const annualSavings = monthlySavings * 12 return ( <section data-slot="pricing-usage-calculator-slider" className={cn('bg-background relative overflow-hidden px-4 py-16 sm:px-6 sm:py-24 lg:px-8', className)} > <div className="mx-auto max-w-6xl space-y-12"> {/* Section Header */} <div className="mx-auto max-w-3xl space-y-4 text-center"> <Badge variant="secondary" className="gap-1.5 px-3 py-1 font-mono text-xs shadow-xs"> <Calculator className="text-primary size-3.5" /> Interactive Infrastructure ROI Calculator </Badge> <h2 className="text-foreground text-3xl font-bold tracking-tight sm:text-4xl"> Calculate your annual savings with unbundled architecture. </h2> <p className="text-muted-foreground text-base"> See how eliminating proprietary seat licenses and runtime SaaS wrappers cuts your front-end TCO. </p> {/* Presets Bar */} <div className="flex flex-wrap items-center justify-center gap-2 pt-2"> <Button size="sm" variant="outline" className="font-mono text-xs" onClick={() => applyPreset('seed')}> Seed Startup (25k MAU) </Button> <Button size="sm" variant="outline" className="font-mono text-xs" onClick={() => applyPreset('growth')}> Growth Scale (350k MAU) </Button> <Button size="sm" variant="outline" className="font-mono text-xs" onClick={() => applyPreset('scale')}> Hypergrowth (2.5M MAU) </Button> </div> </div> {/* 2-Column Split: Sliders Workbench Left (7 Cols), Savings Scorecard Right (5 Cols) */} <div className="grid grid-cols-1 items-stretch gap-8 lg:grid-cols-12"> {/* Sliders Controls (7 Cols) */} <Card className="border-border bg-card/95 flex flex-col justify-between space-y-6 rounded-2xl p-6 text-left shadow-xl sm:p-8 lg:col-span-7"> <div className="space-y-6"> <div className="border-border flex items-center justify-between border-b pb-4"> <h3 className="text-foreground font-mono text-sm font-bold">Traffic & Telemetry Inputs</h3> <span className="text-muted-foreground font-mono text-xs">Dynamic Projection</span> </div> {/* Slider 1: MAU */} <div className="space-y-2"> <div className="flex items-center justify-between font-mono text-xs"> <span className="text-muted-foreground flex items-center gap-1.5"> <Users className="text-primary size-3.5" /> Monthly Active Users (MAU) </span> <span className="text-foreground text-sm font-bold">{formatNumber(mau)} users</span> </div> <input type="range" min="10000" max="5000000" step="25000" value={mau} onChange={(e) => setMau(Number(e.target.value))} className="accent-primary bg-border h-2 w-full cursor-pointer rounded-lg" /> </div> {/* Slider 2: QPS */} <div className="space-y-2"> <div className="flex items-center justify-between font-mono text-xs"> <span className="text-muted-foreground flex items-center gap-1.5"> <Server className="text-primary size-3.5" /> Peak Query Throughput </span> <span className="text-foreground text-sm font-bold">{formatNumber(qps)} QPS</span> </div> <input type="range" min="100" max="20000" step="100" value={qps} onChange={(e) => setQps(Number(e.target.value))} className="accent-primary bg-border h-2 w-full cursor-pointer rounded-lg" /> </div> {/* Slider 3: Global Replicas */} <div className="space-y-2"> <div className="flex items-center justify-between font-mono text-xs"> <span className="text-muted-foreground flex items-center gap-1.5"> <Globe className="text-primary size-3.5" /> Global Edge POP Replicas </span> <span className="text-foreground text-sm font-bold">{edgeReplicas} Edge Regions</span> </div> <input type="range" min="1" max="12" step="1" value={edgeReplicas} onChange={(e) => setEdgeReplicas(Number(e.target.value))} className="accent-primary bg-border h-2 w-full cursor-pointer rounded-lg" /> </div> </div> <div className="border-border text-muted-foreground flex items-center justify-between border-t pt-4 font-mono text-xs"> <span>Formula: Direct AST + Raw Static Hosting</span> <span className="text-success font-semibold">✓ Zero Seat Surcharges</span> </div> </Card> {/* ROI Cost & Savings Projection Card (5 Cols) */} <Card className="border-border bg-card/95 flex flex-col justify-between space-y-6 rounded-2xl p-6 text-left shadow-sm sm:p-8 lg:col-span-5"> <div className="space-y-6"> <Badge variant="outline" className="border-success/20 bg-success/10 text-success font-mono text-xs"> Projected Annual Net Savings </Badge> {/* Big Stat Display */} <div className="space-y-1"> <div className="text-success font-mono text-4xl font-bold tracking-tight sm:text-5xl"> ${annualSavings.toLocaleString()} </div> <p className="text-muted-foreground font-mono text-xs"> Saved every year (${monthlySavings.toLocaleString()}/month) </p> </div> {/* Comparative Cost Bars */} <div className="space-y-3 pt-2"> <div className="space-y-1"> <div className="flex items-center justify-between font-mono text-xs"> <span className="text-destructive font-medium">Traditional Monolith Stack:</span> <span className="text-foreground font-bold">${traditionalCost.toLocaleString()}/mo</span> </div> <div className="bg-destructive/20 h-2 overflow-hidden rounded-full"> <div className="bg-destructive h-full w-full" /> </div> </div> <div className="space-y-1"> <div className="flex items-center justify-between font-mono text-xs"> <span className="text-success font-semibold">UIPKGE Unbundled Registry:</span> <span className="text-foreground font-bold">${uipkgeCost.toLocaleString()}/mo</span> </div> <div className="bg-success/20 h-2 overflow-hidden rounded-full"> <div className="bg-success h-full transition-[width] duration-300" style={{ width: `${Math.max(5, (uipkgeCost / traditionalCost) * 100)}%` }} /> </div> </div> </div> </div> <Button className="mt-4 h-10 w-full gap-1.5 font-mono text-xs shadow-md"> <span>Lock In Pro Savings</span> <ArrowRight className="size-3.5" /> </Button> </Card> </div> </div> </section> ) } export default PricingUsageCalculatorSlider
Raw manifest:https://uipkge.dev/r/react/pricing.json