Files
playzer/apps/frontend/src/features/onboard/components/steps/plan-select-step.tsx

105 lines
3.9 KiB
TypeScript

import { cn } from '@/lib/utils';
import type { PlanWithFeatures } from '@repo/api-contract';
import { Check, X } from 'lucide-react';
import type { UseFormReturn } from 'react-hook-form';
type PlanSelectStepProps = {
form: UseFormReturn<any>;
plans: PlanWithFeatures[];
};
const FEATURE_LABELS: Record<string, string> = {
publicBookingPage: 'Página de booking pública',
onlinePayments: 'Pagos online',
advancedReports: 'Reportes avanzados',
whatsappReminders: 'Recordatorios WhatsApp',
};
export function PlanSelectStep({ form, plans }: PlanSelectStepProps) {
const selectedCode = form.watch('planCode') as string | undefined;
const errors = form.formState.errors;
const featuresList = [
'publicBookingPage',
'onlinePayments',
'advancedReports',
'whatsappReminders',
] as const;
return (
<div className="space-y-4">
<div>
<h2 className="text-lg font-semibold">Elegí tu plan</h2>
<p className="text-sm text-muted-foreground">
Seleccioná el plan que mejor se adapte a tu complejo.
</p>
</div>
<div className="grid gap-4 md:grid-cols-3">
{plans.map((plan) => {
const isSelected = selectedCode === plan.code;
return (
<button
key={plan.code}
type="button"
onClick={() => form.setValue('planCode', plan.code, { shouldValidate: true })}
className={cn(
'relative flex flex-col rounded-xl border-2 p-5 text-left transition-all',
'hover:border-primary/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
isSelected ? 'border-primary bg-primary/5 shadow-sm' : 'border-border bg-card'
)}
>
{isSelected && (
<div className="absolute right-3 top-3 flex size-6 items-center justify-center rounded-full bg-primary text-primary-foreground">
<Check className="size-4" />
</div>
)}
<div className="mb-3">
<h3 className="text-xl font-bold">{plan.name}</h3>
<div className="mt-1">
<span className="text-3xl font-bold">
{plan.currency === 'ARS'
? `AR$${plan.price.toLocaleString('es-AR')}`
: `$${plan.price.toFixed(2)}`}
</span>
<span className="text-sm text-muted-foreground">/mes</span>
</div>
</div>
<div className="mb-4 space-y-1.5 border-t pt-3 text-xs text-muted-foreground">
<p className="font-medium text-foreground">Incluye:</p>
<p>Hasta {plan.limits.maxCourts} canchas</p>
<p>Hasta {plan.limits.maxBookingsPerDay} reservas/día</p>
{plan.limits.maxActiveUsers && <p>Hasta {plan.limits.maxActiveUsers} usuarios</p>}
</div>
<div className="space-y-1.5 border-t pt-3">
{featuresList.map((feature) => {
const enabled = plan.features[feature as keyof typeof plan.features];
return (
<div key={feature} className="flex items-center gap-2 text-xs">
{enabled ? (
<Check className="size-3.5 shrink-0 text-green-600" />
) : (
<X className="size-3.5 shrink-0 text-muted-foreground" />
)}
<span className={enabled ? 'text-foreground' : 'text-muted-foreground'}>
{FEATURE_LABELS[feature]}
</span>
</div>
);
})}
</div>
</button>
);
})}
</div>
{errors.planCode && (
<p className="text-sm text-destructive">{String(errors.planCode.message ?? '')}</p>
)}
</div>
);
}