pricing.tsx
"use client"
import { useId, useState } from "react"
import { cn } from "cn"
import {
AnimatePresence,
motion,
useReducedMotion,
type Variants,
} from "motion/react"
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
} from "@/components/ui/input-group"
import { Separator } from "@/components/ui/separator"
import { Slider } from "@/components/ui/slider"
import { FancyButton } from "@/components/ui/fancy-button"
const animVariant: Variants = {
hidden: { opacity: 0, y: 10, filter: "blur(4px)", scale: 0.98 },
visible: (i: number) => ({
opacity: 1,
y: 0,
filter: "blur(0px)",
scale: 1,
transition: {
delay: i * 0.03,
type: "spring",
damping: 22,
stiffness: 280,
},
}),
exit: {
opacity: 0,
y: -10,
filter: "blur(4px)",
scale: 0.98,
transition: { duration: 0.14 },
},
}
export function Pricing() {
const [weeklyReports, setWeeklyReports] = useState(10)
const [costPerReport, setCostPerReport] = useState(150)
const reportsLabelId = useId()
const costInputId = useId()
const shouldReduceMotion = useReducedMotion()
const maxWeeklyReports = 20
const reportTicks = Array.from(
{ length: maxWeeklyReports + 1 },
(_, tick) => tick
)
const monthlyReports = weeklyReports * 4
const monthlyValueSaved = monthlyReports * costPerReport
const platformCost = 299
const totalRoi = Math.max(0, monthlyValueSaved - platformCost)
const roiChars = totalRoi.toLocaleString("en-US").split("")
return (
<section
aria-label="Pricing"
className="mx-auto flex w-full max-w-5xl flex-col gap-10"
>
<h2 className="max-w-xl text-4xl font-semibold tracking-tight text-balance text-foreground sm:text-5xl">
See what you save
</h2>
<div className="flex w-full flex-col gap-2 rounded-4xl bg-card p-1 shadow-elevated-lg lg:flex-row">
<div className="flex min-w-0 flex-1 flex-col gap-10 p-6 sm:p-8 lg:p-10">
<div className="flex flex-col gap-10">
<p
id={reportsLabelId}
className="text-lg leading-relaxed text-muted-foreground"
>
How many complex data reports is your team compiling manually
every single week?
</p>
<div className="flex flex-col gap-8">
<div className="flex flex-wrap items-center justify-center gap-2 text-4xl font-bold tracking-tight text-foreground sm:text-5xl">
<span>{weeklyReports}</span>
<span>reports</span>
</div>
<div className="grid w-full gap-4">
<Slider
aria-labelledby={reportsLabelId}
value={[weeklyReports]}
onValueChange={(value) =>
setWeeklyReports(
Array.isArray(value) ? (value[0] ?? 0) : value
)
}
max={maxWeeklyReports}
min={0}
step={1}
className="cursor-grab active:cursor-grabbing **:data-[slot=slider-thumb]:size-6 **:data-[slot=slider-thumb]:border-4 **:data-[slot=slider-thumb]:border-card **:data-[slot=slider-thumb]:bg-primary **:data-[slot=slider-thumb]:ring-0 **:data-[slot=slider-thumb]:focus-within:ring-3 **:data-[slot=slider-track]:h-2.5 **:data-[slot=slider-track]:bg-input/60"
/>
<div
aria-hidden="true"
className="flex w-full items-start justify-between px-3 text-xs font-medium text-muted-foreground"
>
{reportTicks.map((tick) => (
<span
key={tick}
className="flex w-0 flex-col items-center gap-2"
>
<span
className={cn(
"w-px bg-muted-foreground/30",
tick % 5 === 0 ? "h-2" : "h-1"
)}
/>
{tick % 5 === 0 && <span>{tick}</span>}
</span>
))}
</div>
</div>
</div>
</div>
<Separator className="bg-border/50" />
<FieldGroup>
<Field orientation="responsive" className="gap-3">
<FieldLabel htmlFor={costInputId} className="text-base">
Average cost per report
</FieldLabel>
<InputGroup className="h-11 bg-transparent @md/field-group:w-40 @md/field-group:shrink-0">
<InputGroupAddon>
<InputGroupText>$</InputGroupText>
</InputGroupAddon>
<InputGroupInput
id={costInputId}
type="number"
value={costPerReport}
onChange={(event) =>
setCostPerReport(
Math.max(0, event.target.valueAsNumber || 0)
)
}
min={0}
className="text-base font-semibold tabular-nums"
/>
</InputGroup>
</Field>
</FieldGroup>
</div>
<div className="flex w-full flex-col items-center justify-between gap-8 rounded-3xl bg-muted p-6 sm:p-8 lg:w-sm lg:shrink-0 lg:p-10">
<div className="flex w-full flex-col items-center gap-2 text-center">
<h3 className="font-medium tracking-tight text-foreground">
Automated Monthly ROI
</h3>
<div className="mt-2 max-w-full text-5xl leading-none font-bold tracking-tight break-all text-foreground sm:text-7xl">
<span>$</span>
<AnimatePresence initial={false} mode="popLayout">
{roiChars.map((char, idx) => (
<motion.span
key={`${totalRoi}-${idx}`}
variants={animVariant}
initial={shouldReduceMotion ? false : "hidden"}
animate="visible"
exit={shouldReduceMotion ? undefined : "exit"}
custom={idx}
className="inline-block"
>
{char}
</motion.span>
))}
</AnimatePresence>
</div>
<p className="mt-4 text-sm text-muted-foreground">
Net savings with a DataSync enterprise plan
</p>
<FancyButton
size="lg"
className="mt-6 w-full text-base font-medium"
>
Start your free trial
</FancyButton>
</div>
<div className="flex w-full flex-col gap-3 text-sm">
<p className="text-xs font-medium tracking-wider text-muted-foreground uppercase">
Breakdown
</p>
{[
{ label: "Reports automated monthly", value: monthlyReports },
{
label: "Capital preserved",
value: `$${monthlyValueSaved.toLocaleString("en-US")}`,
},
{ label: "DataSync platform fee", value: `$${platformCost}` },
].map(({ label, value }) => (
<div
key={label}
className="flex items-start justify-between gap-4"
>
<span className="text-muted-foreground">{label}</span>
<span className="min-w-0 text-right font-medium break-all text-foreground">
{value}
</span>
</div>
))}
<Separator className="my-1 bg-border/50" />
<div className="flex items-start justify-between gap-4 font-semibold">
<span className="text-foreground">Total monthly ROI</span>
<span className="min-w-0 text-right break-all text-foreground">
${totalRoi.toLocaleString("en-US")}
</span>
</div>
</div>
</div>
</div>
</section>
)
}