88 lines
2.8 KiB
TypeScript
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>
|
|
);
|
|
}
|