174 lines
7.2 KiB
TypeScript
174 lines
7.2 KiB
TypeScript
"use client";
|
|
|
|
import { Card, CardContent, CardHeader, CardTitle, CardFooter } from "@/components/ui/card";
|
|
import { Button } from "@/components/ui/button";
|
|
import { formatCurrency, cn } from "@/lib/utils";
|
|
|
|
interface MembershipPlan {
|
|
id: string;
|
|
name: string;
|
|
description: string | null;
|
|
price: number | string;
|
|
durationMonths: number;
|
|
courtHours: number | null;
|
|
discountPercent: number | string | null;
|
|
benefits: string[] | null;
|
|
isActive: boolean;
|
|
subscriberCount: number;
|
|
benefitsSummary?: {
|
|
freeHours: number;
|
|
bookingDiscount: number;
|
|
extraBenefits: string[];
|
|
};
|
|
}
|
|
|
|
interface PlanCardProps {
|
|
plan: MembershipPlan;
|
|
onEdit?: (plan: MembershipPlan) => void;
|
|
onDelete?: (plan: MembershipPlan) => void;
|
|
isAdmin?: boolean;
|
|
}
|
|
|
|
export function PlanCard({ plan, onEdit, onDelete, isAdmin = false }: PlanCardProps) {
|
|
const price = typeof plan.price === "string" ? parseFloat(plan.price) : plan.price;
|
|
const discountPercent = plan.benefitsSummary?.bookingDiscount ??
|
|
(plan.discountPercent ? Number(plan.discountPercent) : 0);
|
|
const freeHours = plan.benefitsSummary?.freeHours ?? plan.courtHours ?? 0;
|
|
const extraBenefits = plan.benefitsSummary?.extraBenefits ?? plan.benefits ?? [];
|
|
|
|
// Extract store discount from benefits array if present
|
|
const storeDiscountBenefit = extraBenefits.find(b => b.includes("store discount"));
|
|
const storeDiscount = storeDiscountBenefit
|
|
? parseInt(storeDiscountBenefit.match(/(\d+)%/)?.[1] || "0", 10)
|
|
: 0;
|
|
const otherBenefits = extraBenefits.filter(b => !b.includes("store discount"));
|
|
|
|
return (
|
|
<Card className={cn(
|
|
"flex flex-col h-full transition-all hover:shadow-lg",
|
|
!plan.isActive && "opacity-60"
|
|
)}>
|
|
<CardHeader className="pb-2">
|
|
<div className="flex items-start justify-between gap-2">
|
|
<CardTitle className="text-lg">{plan.name}</CardTitle>
|
|
<span className={cn(
|
|
"px-2 py-1 text-xs font-medium rounded-full",
|
|
plan.subscriberCount > 0
|
|
? "bg-accent-100 text-accent-700"
|
|
: "bg-primary-100 text-primary-600"
|
|
)}>
|
|
{plan.subscriberCount} {plan.subscriberCount === 1 ? "subscriber" : "subscribers"}
|
|
</span>
|
|
</div>
|
|
{plan.description && (
|
|
<p className="text-sm text-primary-500 mt-1">{plan.description}</p>
|
|
)}
|
|
</CardHeader>
|
|
|
|
<CardContent className="flex-1 space-y-4">
|
|
{/* Price */}
|
|
<div className="text-center py-3 border-y border-primary-100">
|
|
<div className="text-3xl font-bold text-primary-800">
|
|
{formatCurrency(price)}
|
|
</div>
|
|
<div className="text-sm text-primary-500">
|
|
/{plan.durationMonths} {plan.durationMonths === 1 ? "month" : "months"}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Key Benefits */}
|
|
<div className="space-y-3">
|
|
{/* Free Hours */}
|
|
{freeHours > 0 && (
|
|
<div className="flex items-center gap-3">
|
|
<div className="flex-shrink-0 w-8 h-8 rounded-full bg-accent-100 flex items-center justify-center">
|
|
<svg className="w-4 h-4 text-accent-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<p className="font-medium text-primary-800">{freeHours} free hours</p>
|
|
<p className="text-xs text-primary-500">of court time per month</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Booking Discount */}
|
|
{discountPercent > 0 && (
|
|
<div className="flex items-center gap-3">
|
|
<div className="flex-shrink-0 w-8 h-8 rounded-full bg-green-100 flex items-center justify-center">
|
|
<svg className="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<p className="font-medium text-primary-800">{discountPercent}% discount</p>
|
|
<p className="text-xs text-primary-500">on additional bookings</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Store Discount */}
|
|
{storeDiscount > 0 && (
|
|
<div className="flex items-center gap-3">
|
|
<div className="flex-shrink-0 w-8 h-8 rounded-full bg-purple-100 flex items-center justify-center">
|
|
<svg className="w-4 h-4 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z" />
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<p className="font-medium text-primary-800">{storeDiscount}% discount</p>
|
|
<p className="text-xs text-primary-500">in store</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Other Benefits */}
|
|
{otherBenefits.length > 0 && (
|
|
<div className="pt-2 border-t border-primary-100">
|
|
<p className="text-xs font-medium text-primary-600 mb-2">Additional benefits:</p>
|
|
<ul className="space-y-1">
|
|
{otherBenefits.map((benefit, index) => (
|
|
<li key={index} className="flex items-start gap-2 text-sm text-primary-700">
|
|
<svg className="w-4 h-4 text-accent-500 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
<span>{benefit}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</CardContent>
|
|
|
|
{isAdmin && (
|
|
<CardFooter className="border-t border-primary-100 pt-4 gap-2">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
className="flex-1"
|
|
onClick={() => onEdit?.(plan)}
|
|
>
|
|
<svg className="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
|
</svg>
|
|
Edit
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
className="flex-1 text-red-600 hover:text-red-700 hover:bg-red-50"
|
|
onClick={() => onDelete?.(plan)}
|
|
>
|
|
<svg className="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
|
</svg>
|
|
Delete
|
|
</Button>
|
|
</CardFooter>
|
|
)}
|
|
</Card>
|
|
);
|
|
}
|