Files
HRM-System/resources/js/pages/plans/index.tsx
2026-04-13 08:16:56 +08:00

947 lines
32 KiB
TypeScript
Executable File

import React, { useState, useEffect } from 'react';
import { PageTemplate } from '@/components/page-template';
import { Card, CardContent, CardHeader, CardFooter } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Switch } from '@/components/ui/switch';
import { Button } from '@/components/ui/button';
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { router, usePage } from '@inertiajs/react';
import {
CheckCircle2,
X,
Edit,
Trash2,
Globe,
FileText,
Bot,
BarChart2,
Mail,
Box,
Store,
Users,
HardDrive,
Plus,
Sparkles,
Info,
Crown,
Zap,
Clock,
Banknote,
CreditCard,
IndianRupee,
Wallet,
Coins
} from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { CrudDeleteModal } from '@/components/CrudDeleteModal';
import { useForm } from '@inertiajs/react';
import { toast } from '@/components/custom-toast';
import { PlanSubscriptionModal } from '@/components/plan-subscription-modal';
interface Plan {
id: number;
name: string;
price: string | number;
formatted_price?: string;
duration: string;
description: string;
trial_days: number;
features: string[];
stats: {
users: number | string;
employees: number | string;
storage: string;
};
status: boolean;
recommended?: boolean;
is_default?: boolean;
is_current?: boolean;
is_trial_available?: boolean;
}
interface Props {
plans: Plan[];
billingCycle: 'monthly' | 'yearly';
hasDefaultPlan?: boolean;
isAdmin?: boolean;
currentPlan?: any;
userTrialUsed?: boolean;
paymentMethods?: any[];
currency?: string;
currencySymbol?: string;
}
export default function Plans({ plans: initialPlans, billingCycle: initialBillingCycle = 'monthly', hasDefaultPlan, isAdmin = false, currentPlan, userTrialUsed, paymentMethods = [], currency, currencySymbol }: Props) {
const { t } = useTranslation();
const { flash, globalSettings } = usePage().props as any;
const [plans, setPlans] = useState<Plan[]>(initialPlans);
const [billingCycle, setBillingCycle] = useState<'monthly' | 'yearly'>(initialBillingCycle);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [planToDelete, setPlanToDelete] = useState<Plan | null>(null);
const [isSubscriptionModalOpen, setIsSubscriptionModalOpen] = useState(false);
const [selectedPlan, setSelectedPlan] = useState<Plan | null>(null);
const { post, processing } = useForm();
// Helper function to safely format currency
const formatCurrency = (amount: string | number) => {
if (typeof window !== 'undefined' && window.appSettings?.formatCurrency) {
// Use numeric value if available, otherwise parse the string
const numericAmount = typeof amount === 'number' ? amount : parseFloat(amount);
return window.appSettings.formatCurrency(numericAmount, { showSymbol: true });
}
// Fallback if appSettings is not available
return amount;
};
const breadcrumbs = [
{ title: t('Dashboard'), href: route('dashboard') },
{ title: t('Plan') }
]
// Update plans when initialPlans changes
useEffect(() => {
setPlans(initialPlans);
}, [initialPlans]);
// Handle flash messages from controller
useEffect(() => {
if (flash?.success) {
toast.success(t(flash.success));
}
if (flash?.error) {
toast.error(t(flash.error));
}
}, [flash, t]);
// Function to handle billing cycle change
const handleBillingCycleChange = (value: 'monthly' | 'yearly') => {
setBillingCycle(value);
router.get(route('plans.index'), { billing_cycle: value }, { preserveState: true });
};
// Company plan actions
const handlePlanRequest = (planId: number) => {
if (!globalSettings?.is_demo) {
toast.loading(t('Submitting plan request...'));
}
router.post(route('plans.request'), {
plan_id: planId,
billing_cycle: billingCycle
}, {
onSuccess: () => {
toast.dismiss();
},
onError: (errors) => {
toast.dismiss();
if (typeof errors === 'string') {
toast.error(t(errors));
} else {
toast.error(t('Failed to submit plan request: {{errors}}', { errors: Object.values(errors).join(', ') }));
}
}
});
};
const handleStartTrial = (planId: number) => {
if (!globalSettings?.is_demo) {
toast.loading(t('Starting trial...'));
}
router.post(route('plans.trial'), {
plan_id: planId
}, {
onSuccess: () => {
toast.dismiss();
},
onError: (errors) => {
toast.dismiss();
if (typeof errors === 'string') {
toast.error(t(errors));
} else {
toast.error(t('Failed to start trial: {{errors}}', { errors: Object.values(errors).join(', ') }));
}
}
});
};
const handleSubscribe = async (planId: number) => {
const plan = plans.find(p => p.id === planId);
if (plan) {
try {
const response = await fetch(route('payment.methods'));
const paymentMethods = await response.json();
setSelectedPlan({ ...plan, paymentMethods });
setIsSubscriptionModalOpen(true);
} catch (error) {
toast.error(t('Failed to load payment methods'));
}
}
};
const formatPaymentMethods = (paymentSettings: any) => {
const methods = [];
if (paymentSettings?.is_bank_enabled === true || paymentSettings?.is_bank_enabled === '1') {
methods.push({
id: 'bank',
name: t('Bank Transfer'),
icon: <Banknote className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_stripe_enabled === true || paymentSettings?.is_stripe_enabled === '1') {
methods.push({
id: 'stripe',
name: t('Stripe'),
icon: <CreditCard className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_paypal_enabled === true || paymentSettings?.is_paypal_enabled === '1') {
methods.push({
id: 'paypal',
name: t('PayPal'),
icon: <CreditCard className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_razorpay_enabled === true || paymentSettings?.is_razorpay_enabled === '1') {
methods.push({
id: 'razorpay',
name: t('Razorpay'),
icon: <IndianRupee className="h-5 w-5" />,
enabled: true
});
}
if ((paymentSettings?.is_mercadopago_enabled === true || paymentSettings?.is_mercadopago_enabled === '1') && paymentSettings?.mercadopago_access_token) {
methods.push({
id: 'mercadopago',
name: t('MercadoPago'),
icon: <Wallet className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_paystack_enabled === true || paymentSettings?.is_paystack_enabled === '1') {
methods.push({
id: 'paystack',
name: t('Paystack'),
icon: <CreditCard className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_flutterwave_enabled === true || paymentSettings?.is_flutterwave_enabled === '1') {
methods.push({
id: 'flutterwave',
name: t('Flutterwave'),
icon: <CreditCard className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_paytabs_enabled === true || paymentSettings?.is_paytabs_enabled === '1') {
methods.push({
id: 'paytabs',
name: t('PayTabs'),
icon: <CreditCard className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_skrill_enabled === true || paymentSettings?.is_skrill_enabled === '1') {
methods.push({
id: 'skrill',
name: t('Skrill'),
icon: <Wallet className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_coingate_enabled === true || paymentSettings?.is_coingate_enabled === '1') {
methods.push({
id: 'coingate',
name: t('CoinGate'),
icon: <Coins className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_payfast_enabled === true || paymentSettings?.is_payfast_enabled === '1') {
methods.push({
id: 'payfast',
name: t('Payfast'),
icon: <CreditCard className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_tap_enabled === true || paymentSettings?.is_tap_enabled === '1') {
methods.push({
id: 'tap',
name: t('Tap'),
icon: <CreditCard className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_xendit_enabled === true || paymentSettings?.is_xendit_enabled === '1') {
methods.push({
id: 'xendit',
name: t('Xendit'),
icon: <CreditCard className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_paytr_enabled === true || paymentSettings?.is_paytr_enabled === '1') {
methods.push({
id: 'paytr',
name: t('PayTR'),
icon: <CreditCard className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_mollie_enabled === true || paymentSettings?.is_mollie_enabled === '1') {
methods.push({
id: 'mollie',
name: t('Mollie'),
icon: <CreditCard className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_toyyibpay_enabled === true || paymentSettings?.is_toyyibpay_enabled === '1') {
methods.push({
id: 'toyyibpay',
name: t('toyyibPay'),
icon: <CreditCard className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_cashfree_enabled === true || paymentSettings?.is_cashfree_enabled === '1') {
methods.push({
id: 'cashfree',
name: t('Cashfree'),
icon: <IndianRupee className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_khalti_enabled === true || paymentSettings?.is_khalti_enabled === '1') {
methods.push({
id: 'khalti',
name: t('Khalti'),
icon: <CreditCard className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_iyzipay_enabled === true || paymentSettings?.is_iyzipay_enabled === '1') {
methods.push({
id: 'iyzipay',
name: t('Iyzipay'),
icon: <CreditCard className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_benefit_enabled === true || paymentSettings?.is_benefit_enabled === '1') {
methods.push({
id: 'benefit',
name: t('Benefit'),
icon: <CreditCard className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_ozow_enabled === true || paymentSettings?.is_ozow_enabled === '1') {
methods.push({
id: 'ozow',
name: t('Ozow'),
icon: <CreditCard className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_easebuzz_enabled === true || paymentSettings?.is_easebuzz_enabled === '1') {
methods.push({
id: 'easebuzz',
name: t('Easebuzz'),
icon: <IndianRupee className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_authorizenet_enabled === true || paymentSettings?.is_authorizenet_enabled === '1') {
methods.push({
id: 'authorizenet',
name: t('AuthorizeNet'),
icon: <CreditCard className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_fedapay_enabled === true || paymentSettings?.is_fedapay_enabled === '1') {
methods.push({
id: 'fedapay',
name: t('FedaPay'),
icon: <CreditCard className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_payhere_enabled === true || paymentSettings?.is_payhere_enabled === '1') {
methods.push({
id: 'payhere',
name: t('PayHere'),
icon: <CreditCard className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_cinetpay_enabled === true || paymentSettings?.is_cinetpay_enabled === '1') {
methods.push({
id: 'cinetpay',
name: t('CinetPay'),
icon: <CreditCard className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_paiement_enabled === true || paymentSettings?.is_paiement_enabled === '1') {
methods.push({
id: 'paiement',
name: t('Paiement Pro'),
icon: <CreditCard className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_nepalste_enabled === true || paymentSettings?.is_nepalste_enabled === '1') {
methods.push({
id: 'nepalste',
name: t('Nepalste'),
icon: <CreditCard className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_yookassa_enabled === true || paymentSettings?.is_yookassa_enabled === '1') {
methods.push({
id: 'yookassa',
name: t('YooKassa'),
icon: <CreditCard className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_aamarpay_enabled === true || paymentSettings?.is_aamarpay_enabled === '1') {
methods.push({
id: 'aamarpay',
name: t('Aamarpay'),
icon: <CreditCard className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_midtrans_enabled === true || paymentSettings?.is_midtrans_enabled === '1') {
methods.push({
id: 'midtrans',
name: t('Midtrans'),
icon: <CreditCard className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_paymentwall_enabled === true || paymentSettings?.is_paymentwall_enabled === '1') {
methods.push({
id: 'paymentwall',
name: t('PaymentWall'),
icon: <CreditCard className="h-5 w-5" />,
enabled: true
});
}
if (paymentSettings?.is_sspay_enabled === true || paymentSettings?.is_sspay_enabled === '1') {
methods.push({
id: 'sspay',
name: t('SSPay'),
icon: <CreditCard className="h-5 w-5" />,
enabled: true
});
}
return methods;
};
const getActionButton = (plan: Plan) => {
// Check if user has active subscription to this plan
if (currentPlan && currentPlan.id === plan.id && currentPlan.expires_at && new Date(currentPlan.expires_at) > new Date()) {
return (
<Button disabled className="w-full h-12 bg-green-50 text-green-700 border border-green-200 font-medium">
<Crown className="h-4 w-4 mr-2" />
{t('Already Subscribed')}
</Button>
);
}
if (plan.is_current) {
return (
<Button disabled className="w-full h-12 bg-primary/10 text-primary border border-primary/20 font-medium">
<Crown className="h-4 w-4 mr-2" />
{t('Current Plan')}
</Button>
);
}
if (plan.is_trial_available && !userTrialUsed) {
return (
<div className="space-y-3">
<Button
onClick={() => handleStartTrial(plan.id)}
disabled={processing}
variant="outline"
className="w-full h-12 border-primary text-primary hover:bg-primary/5 font-medium"
>
<Zap className="h-4 w-4 mr-2" />
{t('Start {{days}} Day Trial', { days: plan.trial_days })}
</Button>
<Button
onClick={() => handleSubscribe(plan.id)}
disabled={processing}
className={`w-full h-12 font-medium ${plan.recommended
? 'bg-primary hover:bg-primary/90 text-white'
: 'bg-primary hover:bg-primary/90 text-white'
}`}
>
{t('Subscribe Now')}
</Button>
</div>
);
}
return (
<div className="space-y-3">
<Button
onClick={() => handlePlanRequest(plan.id)}
disabled={processing}
variant="outline"
className="w-full h-12 border-gray-300 text-gray-700 hover:bg-gray-50 font-medium"
>
<Clock className="h-4 w-4 mr-2" />
{t('Request Plan')}
</Button>
<Button
onClick={() => handleSubscribe(plan.id)}
disabled={processing || (currentPlan && currentPlan.id === plan.id && currentPlan.expires_at && new Date(currentPlan.expires_at) > new Date())}
className={`w-full h-12 font-medium ${plan.recommended
? 'bg-primary hover:bg-primary/90 text-white'
: 'bg-primary hover:bg-primary/90 text-white'
}`}
>
{currentPlan && currentPlan.id === plan.id && currentPlan.expires_at && new Date(currentPlan.expires_at) > new Date()
? t('Already Subscribed')
: t('Subscribe Now')
}
</Button>
</div>
);
};
// Function to get the appropriate icon for a feature
const getFeatureIcon = (feature: string) => {
switch (feature) {
case 'AI Integration':
return <Bot className="h-4 w-4" />;
default:
return <CheckCircle2 className="h-4 w-4" />;
}
};
// Function to check if a feature is included in the plan
const isFeatureIncluded = (plan: Plan, feature: string) => {
return plan.features.includes(feature);
};
// Function to toggle plan status
const togglePlanStatus = (planId: number) => {
const plan = plans.find(p => p.id === planId);
const newStatus = !plan?.status;
if (!globalSettings?.is_demo) {
toast.loading(`${newStatus ? t('Activating') : t('Deactivating')} plan...`);
}
router.post(route('plans.toggle-status', planId), {}, {
preserveState: true,
onSuccess: (page) => {
if (!globalSettings?.is_demo) {
toast.dismiss();
setPlans(plans.map(p =>
p.id === planId ? { ...p, status: !p.status } : p
));
}
},
onError: (errors) => {
if (!globalSettings?.is_demo) {
toast.dismiss();
}
if (typeof errors === 'string') {
toast.error(t(errors));
} else {
toast.error(t('Failed to update plan status: {{errors}}', { errors: Object.values(errors).join(', ') }));
}
}
});
};
// Function to handle delete
const handleDelete = (plan: Plan) => {
setPlanToDelete(plan);
setIsDeleteModalOpen(true);
};
// Function to handle delete confirmation
const handleDeleteConfirm = () => {
if (planToDelete) {
if (!globalSettings?.is_demo) {
toast.loading(t('Deleting plan...'));
}
router.delete(route('plans.destroy', planToDelete.id), {
onSuccess: () => {
setIsDeleteModalOpen(false);
setPlanToDelete(null);
toast.dismiss();
},
onError: (errors) => {
toast.dismiss();
if (typeof errors === 'string') {
toast.error(t(errors));
} else {
toast.error(t('Failed to delete plan: {{errors}}', { errors: Object.values(errors).join(', ') }));
}
}
});
}
};
// Common features to display for all plans
const commonFeatures = [
'AI Integration'
];
// Define stat icons
const statIcons = {
users: <Users className="h-5 w-5" />,
employees: <Users className="h-5 w-5" />,
storage: <HardDrive className="h-5 w-5" />
};
return (
<PageTemplate
title={t("Plans")}
breadcrumbs={breadcrumbs}
description={t("Manage subscription plans for your customers")}
url="/plans"
>
<div className="space-y-6 sm:space-y-8">
{/* Header with controls */}
<div className="flex flex-col items-center text-center mb-12">
<div className="max-w-3xl mx-auto mb-8">
<h1 className="text-3xl font-bold text-gray-900 mb-4">
{isAdmin ? t("Subscription Plans") : t("Choose Your Plan")}
</h1>
<p className="text-lg text-gray-600">
{isAdmin
? t("Create and manage subscription plans to offer different service tiers to your customers.")
: t("Select the perfect plan for your business needs and start growing today.")
}
</p>
</div>
<div className="flex flex-col sm:flex-row items-center gap-4">
{/* Billing Cycle Toggle */}
<div className="bg-gray-100 rounded-lg p-1">
<Tabs
value={billingCycle}
onValueChange={(v) => handleBillingCycleChange(v as 'monthly' | 'yearly')}
className="w-full"
>
<TabsList className="grid w-full grid-cols-2 bg-transparent p-0 h-auto">
<TabsTrigger
value="monthly"
className="px-6 py-2 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-gray-900 data-[state=active]:shadow-sm rounded-md"
>
{t("Monthly")}
</TabsTrigger>
<TabsTrigger
value="yearly"
className="px-6 py-2 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-gray-900 data-[state=active]:shadow-sm rounded-md relative"
>
{t("Yearly")}
<Badge className="ml-2 bg-green-500 text-white text-xs px-2 py-0.5">
{t("Save 20%")}
</Badge>
</TabsTrigger>
</TabsList>
</Tabs>
</div>
{/* Add Plan Button - Admin only */}
{isAdmin && (
<Button
className="bg-primary hover:bg-primary/90 text-white px-6 py-2 font-medium"
onClick={() => router.get(route('plans.create'))}
>
<Plus className="h-4 w-4 mr-2" />
{t("Add Plan")}
</Button>
)}
</div>
</div>
{/* Plans grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 max-w-6xl mx-auto">
{plans.map((plan) => (
<div
key={plan.id}
className={`relative h-full transition-all duration-200 ${plan.recommended ? 'transform scale-105' : ''
}`}
>
{/* Main Card */}
<div className={`
relative h-full flex flex-col rounded-lg border-2 transition-all duration-200
${plan.recommended
? 'border-primary shadow-xl bg-white'
: 'border-gray-200 bg-white hover:border-gray-300 hover:shadow-lg'
}
${isAdmin && !plan.status && !globalSettings?.is_demo ? 'grayscale opacity-60' : ''}
`}>
{/* Recommended Badge */}
{plan.recommended && (
<div className="absolute -top-4 left-1/2 transform -translate-x-1/2 z-10">
<div className="bg-primary text-white px-4 py-1 rounded-full text-sm font-semibold">
{t("Recommended")}
</div>
</div>
)}
{/* Status Indicators */}
<div className="absolute top-4 right-4 z-10 flex flex-col gap-1">
{isAdmin && (
<>
{plan.is_default && (
<span className="inline-flex items-center rounded-md bg-blue-50 px-2 py-1 text-xs font-medium text-blue-700 ring-1 ring-inset ring-blue-700/10">
{t("Default")}
</span>
)}
<span className={`inline-flex items-center rounded-md px-2 py-1 text-xs font-medium ring-1 ring-inset ${
plan.status
? 'bg-green-50 text-green-700 ring-green-600/20'
: 'bg-red-50 text-red-700 ring-red-600/20'
}`}>
{plan.status ? t("Active") : t("Inactive")}
</span>
</>
)}
{!isAdmin && plan.is_current && (
<span className="inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20">
<Crown className="h-3 w-3 mr-1" />
{t("Current")}
</span>
)}
</div>
{/* Card Header */}
<div className="p-6 text-center border-b border-gray-100">
<h3 className="text-xl font-bold text-gray-900 mb-2">
{plan.name}
</h3>
{/* Pricing */}
<div className="mb-4">
<div className="flex items-center justify-center">
<span className="text-4xl font-bold text-gray-900">
{currencySymbol}{plan.price}
</span>
<span className="text-gray-500 ml-1">
/{t(plan.duration.toLowerCase())}
</span>
</div>
</div>
{/* Description */}
<p className="text-gray-600 text-sm mb-4">
{plan.description}
</p>
{/* Trial Badge */}
{plan.trial_days > 0 && (
<div className="inline-flex items-center gap-1 px-3 py-1 bg-blue-50 text-blue-700 rounded-full text-sm font-medium">
<Zap className="h-3 w-3" />
{t("{{days}} days free trial", { days: plan.trial_days })}
</div>
)}
</div>
{/* Card Content */}
<div className="flex flex-col flex-1 p-6">
{/* Usage Stats */}
<div className="mb-6">
<h4 className="text-sm font-semibold text-gray-900 mb-3 uppercase tracking-wide">
{t("What's Included")}
</h4>
<div className="space-y-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Users className="h-4 w-4 text-blue-500" />
<span className="text-sm text-gray-700">{t("Users")}</span>
</div>
<span className="text-sm font-semibold text-gray-900">{plan.stats.users}</span>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Users className="h-4 w-4 text-green-500" />
<span className="text-sm text-gray-700">{t("Employees")}</span>
</div>
<span className="text-sm font-semibold text-gray-900">{plan.stats.employees}</span>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<HardDrive className="h-4 w-4 text-purple-500" />
<span className="text-sm text-gray-700">{t("Storage")}</span>
</div>
<span className="text-sm font-semibold text-gray-900">{plan.stats.storage}</span>
</div>
</div>
</div>
{/* Features */}
<div className="mb-6 flex-1">
<h4 className="text-sm font-semibold text-gray-900 mb-3 uppercase tracking-wide">
{t("Features")}
</h4>
<ul className="space-y-2">
{commonFeatures.map((feature, index) => {
const included = isFeatureIncluded(plan, feature);
return (
<li key={index} className="flex items-center gap-2">
<div className={`flex-shrink-0 w-5 h-5 rounded-full flex items-center justify-center ${included
? 'bg-green-100 text-green-600'
: 'bg-gray-100 text-gray-400'
}`}>
{included ? (
<CheckCircle2 className="h-3 w-3" />
) : (
<X className="h-3 w-3" />
)}
</div>
<span className={`text-sm ${included ? 'text-gray-700' : 'text-gray-400'
}`}>
{t(feature)}
</span>
</li>
);
})}
</ul>
</div>
{/* Actions */}
<div className="mt-auto">
{isAdmin ? (
<div className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div className="flex items-center gap-2">
<Switch
checked={plan.status}
onCheckedChange={() => togglePlanStatus(plan.id)}
className={plan.status ? "data-[state=checked]:bg-primary" : ""}
/>
<span className="text-sm text-gray-700">
{plan.status ? t("Active") : t("Inactive")}
</span>
</div>
<div className="flex items-center gap-1">
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="text-amber-500 hover:text-amber-700"
onClick={() => router.get(route('plans.edit', plan.id))}
>
<Edit className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>{t('Edit')}</TooltipContent>
</Tooltip>
{!plan.is_default && (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="text-red-500 hover:text-red-700"
onClick={() => handleDelete(plan)}
>
<Trash2 className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>{t('Delete')}</TooltipContent>
</Tooltip>
)}
</div>
</div>
) : (
getActionButton(plan)
)}
</div>
</div>
</div>
</div>
))}
</div>
{/* Delete Modal - Admin only */}
{isAdmin && (
<CrudDeleteModal
isOpen={isDeleteModalOpen}
onClose={() => setIsDeleteModalOpen(false)}
onConfirm={handleDeleteConfirm}
itemName={planToDelete?.name || ''}
entityName="plan"
/>
)}
{/* Subscription Modal - Company only */}
{!isAdmin && selectedPlan && (
<PlanSubscriptionModal
isOpen={isSubscriptionModalOpen}
onClose={() => {
setIsSubscriptionModalOpen(false);
setSelectedPlan(null);
}}
plan={selectedPlan}
billingCycle={billingCycle}
paymentMethods={formatPaymentMethods(selectedPlan.paymentMethods)}
currencySymbol={currencySymbol}
/>
)}
</div>
</PageTemplate>
);
}