it/web/components/pricing/PlanCard.tsx
2025-10-26 17:52:17 +01:00

88 lines
2.8 KiB
TypeScript

// components/pricing/PlanCard.tsx
import { Plan } from "./types";
import { formatEUR, monthlyForYearly } from "./money";
import { CheckIcon } from "./icons";
export default function PlanCard({
plan,
billing,
}: {
plan: Plan;
billing: "monthly" | "yearly";
}) {
const price =
billing === "monthly"
? plan.monthlyPrice
: monthlyForYearly(plan.monthlyPrice, plan.yearlyDiscount);
return (
<div
className={[
"relative flex h-full flex-col rounded-2xl border border-gray-200 bg-white p-6 shadow-sm transition hover:shadow-md",
plan.popular ? "ring-2 ring-emerald-500" : "",
].join(" ")}
>
{plan.popular && (
<div className="absolute -top-3 left-6 rounded-full bg-emerald-600 px-3 py-1 text-xs font-semibold text-white shadow">
Most popular
</div>
)}
<h3 className="text-xl font-semibold">{plan.name}</h3>
<p className="mt-1 text-sm text-gray-500">{plan.bestFor}</p>
<div className="mt-6 flex items-baseline gap-2">
<span className="text-4xl font-bold">{formatEUR(price)}</span>
<span className="text-sm text-gray-500">/mo</span>
</div>
{billing === "yearly" && (
<p className="mt-1 text-xs text-emerald-700">
Billed annually (save {Math.round(plan.yearlyDiscount * 100)}%)
</p>
)}
<ul className="mt-6 space-y-2 text-sm">
{plan.outcomes.map((o) => (
<li key={o} className="flex items-start gap-2">
<CheckIcon className="mt-0.5 h-5 w-5 text-emerald-600" />
<span className="font-medium">{o}</span>
</li>
))}
</ul>
<div className="mt-4 rounded-lg bg-gray-50 p-3 text-sm">
<p className="font-medium">Includes:</p>
<ul className="mt-2 space-y-1">
{plan.inclusions.map((i) => (
<li key={i} className="flex items-start gap-2">
<CheckIcon className="mt-0.5 h-4 w-4 text-gray-400" />
<span>{i}</span>
</li>
))}
</ul>
</div>
<p className="mt-4 text-sm text-gray-600">
<span className="font-medium">SLA:</span> {plan.sla}
</p>
<div className="mt-6">
<a
href={plan.contactOnly ? "/contact" : `/checkout?plan=${plan.id}&billing=${billing}`}
className={[
"inline-flex w-full items-center justify-center rounded-xl px-4 py-3 text-sm font-semibold transition",
plan.contactOnly
? "border border-gray-300 text-gray-900 hover:bg-gray-50"
: "bg-gray-900 text-white hover:bg-black",
].join(" ")}
aria-label={plan.ctaLabel}
>
{plan.ctaLabel}
</a>
<p className="mt-2 text-center text-xs text-gray-500">
30-day cancel. Incident credits if we miss SLAs.
</p>
</div>
</div>
);
}