- Add billing-usage feature module with types, API clients, hooks, and components - Create PlanCard, PlanSelector, SubscriptionStatusBadge, InvoiceList, UsageSummaryCard, CouponInput components - Add BillingPage, PlansPage, InvoicesPage, UsagePage - Update routes to include billing section Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
91 lines
2.6 KiB
TypeScript
91 lines
2.6 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { PlanCard } from './PlanCard';
|
|
import type { SubscriptionPlan, BillingCycle } from '../types';
|
|
|
|
interface PlanSelectorProps {
|
|
plans: SubscriptionPlan[];
|
|
selectedPlanId?: string;
|
|
currentPlanId?: string;
|
|
onSelect: (plan: SubscriptionPlan, billingCycle: BillingCycle) => void;
|
|
isLoading?: boolean;
|
|
}
|
|
|
|
export const PlanSelector: React.FC<PlanSelectorProps> = ({
|
|
plans,
|
|
selectedPlanId,
|
|
currentPlanId,
|
|
onSelect,
|
|
isLoading = false,
|
|
}) => {
|
|
const [billingCycle, setBillingCycle] = useState<BillingCycle>('monthly');
|
|
|
|
const activePlans = plans.filter((plan) => plan.isActive && plan.isPublic);
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex items-center justify-center py-12">
|
|
<div className="h-8 w-8 animate-spin rounded-full border-4 border-blue-500 border-t-transparent" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (activePlans.length === 0) {
|
|
return (
|
|
<div className="rounded-lg bg-gray-50 p-8 text-center">
|
|
<p className="text-gray-500">No hay planes disponibles</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<div className="mb-8 flex justify-center">
|
|
<div className="inline-flex rounded-lg bg-gray-100 p-1">
|
|
<button
|
|
type="button"
|
|
className={`
|
|
rounded-md px-4 py-2 text-sm font-medium transition-colors
|
|
${billingCycle === 'monthly'
|
|
? 'bg-white text-gray-900 shadow'
|
|
: 'text-gray-600 hover:text-gray-900'
|
|
}
|
|
`}
|
|
onClick={() => setBillingCycle('monthly')}
|
|
>
|
|
Mensual
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={`
|
|
rounded-md px-4 py-2 text-sm font-medium transition-colors
|
|
${billingCycle === 'annual'
|
|
? 'bg-white text-gray-900 shadow'
|
|
: 'text-gray-600 hover:text-gray-900'
|
|
}
|
|
`}
|
|
onClick={() => setBillingCycle('annual')}
|
|
>
|
|
Anual
|
|
<span className="ml-1 rounded bg-green-100 px-1.5 py-0.5 text-xs text-green-700">
|
|
Ahorra
|
|
</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
|
{activePlans.map((plan) => (
|
|
<PlanCard
|
|
key={plan.id}
|
|
plan={plan}
|
|
isSelected={plan.id === selectedPlanId}
|
|
isCurrent={plan.id === currentPlanId}
|
|
billingCycle={billingCycle}
|
|
onSelect={(p) => onSelect(p, billingCycle)}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|