90 lines
3.7 KiB
TypeScript
90 lines
3.7 KiB
TypeScript
// components/pricing/ComparisonTable.tsx
|
||
import { Plan } from "./types";
|
||
import { CheckIcon, MinusIcon } from "./icons";
|
||
|
||
const rows: { label: string; keys: Array<keyof Plan | string> }[] = [
|
||
{ label: "Email deliverability monitoring", keys: [] },
|
||
{ label: "Automated backups", keys: [] },
|
||
{ label: "Restore test cadence", keys: [] },
|
||
{ label: "Cloudflare WAF & bot tuning", keys: [] },
|
||
{ label: "Web Vitals reporting", keys: [] },
|
||
{ label: "On-call paging", keys: [] },
|
||
{ label: "First response SLA", keys: [] },
|
||
{ label: "Uptime target", keys: [] },
|
||
];
|
||
|
||
export default function ComparisonTable({ plans }: { plans: Plan[] }) {
|
||
// Hard-code feature mapping for clarity (keeps copy outcome-driven)
|
||
const matrix: Record<string, (planId: string) => string | boolean> = {
|
||
"Email deliverability monitoring": () => true,
|
||
"Automated backups": () => true,
|
||
"Restore test cadence": (id) =>
|
||
id === "essential" ? "Quarterly" : id === "growth" ? "Quarterly" : "Monthly",
|
||
"Cloudflare WAF & bot tuning": (id) => id !== "essential",
|
||
"Web Vitals reporting": (id) => (id === "growth" || id === "mission" ? "Monthly" : false),
|
||
"On-call paging": (id) => (id === "mission" ? "24/7" : false),
|
||
"First response SLA": (id) =>
|
||
id === "essential" ? "NBD (8×5)" : id === "growth" ? "4h (8×5)" : "1h (24/7)",
|
||
"Uptime target": (id) =>
|
||
id === "essential" ? "99.9%" : id === "growth" ? "99.95%" : "99.99%",
|
||
};
|
||
|
||
const features = Object.keys(matrix);
|
||
|
||
return (
|
||
<div className="overflow-hidden rounded-2xl border border-gray-200">
|
||
<div className="bg-gray-50 px-6 py-4">
|
||
<h2 className="text-xl font-semibold">What’s inside each plan</h2>
|
||
<p className="mt-1 text-sm text-gray-600">
|
||
Outcome-driven features, not laundry lists.
|
||
</p>
|
||
</div>
|
||
<div className="overflow-x-auto">
|
||
<table className="min-w-full divide-y divide-gray-200">
|
||
<thead className="bg-white">
|
||
<tr>
|
||
<th className="py-3 pl-6 pr-3 text-left text-sm font-semibold text-gray-600">Feature</th>
|
||
{plans.map((p) => (
|
||
<th key={p.id} className="px-3 py-3 text-left text-sm font-semibold text-gray-600">
|
||
{p.name}
|
||
</th>
|
||
))}
|
||
</tr>
|
||
</thead>
|
||
<tbody className="divide-y divide-gray-100 bg-white">
|
||
{features.map((feature) => (
|
||
<tr key={feature}>
|
||
<td className="whitespace-nowrap py-3 pl-6 pr-3 text-sm font-medium text-gray-900">
|
||
{feature}
|
||
</td>
|
||
{plans.map((p) => {
|
||
const val = matrix[feature](p.id);
|
||
const isBool = typeof val === "boolean";
|
||
return (
|
||
<td key={p.id + feature} className="whitespace-nowrap px-3 py-3 text-sm">
|
||
{isBool ? (
|
||
val ? (
|
||
<CheckIcon className="h-5 w-5 text-emerald-600" />
|
||
) : (
|
||
<MinusIcon className="h-5 w-5 text-gray-300" />
|
||
)
|
||
) : (
|
||
<span className="rounded-full bg-gray-50 px-2 py-0.5 text-xs text-gray-700">
|
||
{val as string}
|
||
</span>
|
||
)}
|
||
</td>
|
||
);
|
||
})}
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div className="bg-gray-50 px-6 py-3 text-xs text-gray-500">
|
||
“Uptime target” refers to target SLO over a rolling 30-day period. Credits apply if response SLAs are missed.
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|