prmbr-image-mksaas/src/components/pricing/pricing-table.tsx

181 lines
5.6 KiB
TypeScript

'use client';
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
import { usePricePlans } from '@/config/price-config';
import { cn } from '@/lib/utils';
import {
PaymentTypes,
type PlanInterval,
PlanIntervals,
type PricePlan,
} from '@/payment/types';
import { useTranslations } from 'next-intl';
import { useState } from 'react';
import { PricingCard } from './pricing-card';
interface PricingTableProps {
metadata?: Record<string, string>;
currentPlan?: PricePlan | null;
className?: string;
}
/**
* Pricing Table Component
*
* 1. Displays all pricing plans with interval selection tabs for subscription plans,
* free plans and one-time purchase plans are always displayed
* 2. If a plan is disabled, it will not be displayed in the pricing table
* 3. If a price is disabled, it will not be displayed in the pricing table
*/
export function PricingTable({
metadata,
currentPlan,
className,
}: PricingTableProps) {
const t = useTranslations('PricingPage');
const [interval, setInterval] = useState<PlanInterval>(PlanIntervals.MONTH);
// Get price plans with translations
const pricePlans = usePricePlans();
const plans = Object.values(pricePlans);
// Current plan ID for comparison
const currentPlanId = currentPlan?.id || null;
// Filter plans into free, subscription and one-time plans
const freePlans = plans.filter((plan) => plan.isFree && !plan.disabled);
const subscriptionPlans = plans.filter(
(plan) =>
!plan.isFree &&
!plan.disabled &&
plan.prices.some(
(price) => !price.disabled && price.type === PaymentTypes.SUBSCRIPTION
)
);
const oneTimePlans = plans.filter(
(plan) =>
!plan.isFree &&
!plan.disabled &&
plan.prices.some(
(price) => !price.disabled && price.type === PaymentTypes.ONE_TIME
)
);
// Check if any plan has a monthly price option
const hasMonthlyOption = subscriptionPlans.some((plan) =>
plan.prices.some(
(price) =>
price.type === PaymentTypes.SUBSCRIPTION &&
price.interval === PlanIntervals.MONTH
)
);
// Check if any plan has a yearly price option
const hasYearlyOption = subscriptionPlans.some((plan) =>
plan.prices.some(
(price) =>
price.type === PaymentTypes.SUBSCRIPTION &&
price.interval === PlanIntervals.YEAR
)
);
const handleIntervalChange = (value: string) => {
setInterval(value as PlanInterval);
};
return (
<div className={cn('flex flex-col gap-12', className)}>
{/* Show interval toggle if there are subscription plans */}
{(hasMonthlyOption || hasYearlyOption) &&
subscriptionPlans.length > 0 && (
<div className="flex justify-center">
<ToggleGroup
size="sm"
type="single"
value={interval}
onValueChange={(value) => value && handleIntervalChange(value)}
className="border rounded-lg p-1"
>
{hasMonthlyOption && (
<ToggleGroupItem
value="month"
className={cn(
'px-3 py-0 cursor-pointer text-sm rounded-md',
'data-[state=on]:bg-primary data-[state=on]:text-primary-foreground'
)}
>
{t('monthly')}
</ToggleGroupItem>
)}
{hasYearlyOption && (
<ToggleGroupItem
value="year"
className={cn(
'px-3 py-0 cursor-pointer text-sm rounded-md',
'data-[state=on]:bg-primary data-[state=on]:text-primary-foreground'
)}
>
{t('yearly')}
</ToggleGroupItem>
)}
</ToggleGroup>
</div>
)}
{/* Calculate total number of visible plans */}
{(() => {
const totalVisiblePlans =
freePlans.length + subscriptionPlans.length + oneTimePlans.length;
return (
<div
className={cn(
'grid gap-6',
// Universal solution that handles any number of cards
totalVisiblePlans === 1 && 'grid-cols-1 max-w-md mx-auto w-full',
totalVisiblePlans === 2 &&
'grid-cols-1 md:grid-cols-2 max-w-2xl mx-auto w-full',
totalVisiblePlans >= 3 &&
'grid-cols-1 md:grid-cols-2 lg:grid-cols-3'
)}
>
{/* Render free plans (always visible) */}
{freePlans.map((plan) => (
<PricingCard
key={plan.id}
plan={plan}
metadata={metadata}
isCurrentPlan={currentPlanId === plan.id}
/>
))}
{/* Render subscription plans with the selected interval */}
{subscriptionPlans.map((plan) => (
<PricingCard
key={plan.id}
plan={plan}
interval={interval}
paymentType={PaymentTypes.SUBSCRIPTION}
metadata={metadata}
isCurrentPlan={currentPlanId === plan.id}
/>
))}
{/* Render one-time plans (always visible) */}
{oneTimePlans.map((plan) => (
<PricingCard
key={plan.id}
plan={plan}
paymentType={PaymentTypes.ONE_TIME}
metadata={metadata}
isCurrentPlan={currentPlanId === plan.id}
/>
))}
</div>
);
})()}
</div>
);
}