181 lines
10 KiB
TypeScript
181 lines
10 KiB
TypeScript
'use client';
|
||
|
||
import { UserProduct } from '@/types/user';
|
||
import { formatDate, formatPrice, getProductTypeLabel } from '@/lib/utils';
|
||
import { CheckCircleIcon, XCircleIcon, PlusCircleIcon } from '@heroicons/react/24/outline';
|
||
import { useState } from 'react';
|
||
|
||
interface UserProductsProps {
|
||
products: UserProduct[];
|
||
onSubscribe: (productId: number) => void;
|
||
onUnsubscribe: (productId: number) => void;
|
||
isLoading: boolean;
|
||
availableProducts: { id: number; title: string | null; packageName?: string }[];
|
||
selectedPackage?: string | null;
|
||
}
|
||
|
||
export default function UserProducts({
|
||
products,
|
||
onSubscribe,
|
||
onUnsubscribe,
|
||
isLoading,
|
||
availableProducts,
|
||
selectedPackage
|
||
}: UserProductsProps) {
|
||
const [selectedProductId, setSelectedProductId] = useState<number | null>(null);
|
||
|
||
const getSubscriptionStatus = (product: UserProduct) => {
|
||
if (!product.pivot?.expire_at) {
|
||
return {
|
||
isActive: false,
|
||
text: 'نامشخص',
|
||
color: 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400',
|
||
dotColor: 'bg-gray-400',
|
||
};
|
||
}
|
||
|
||
const expireDate = new Date(product.pivot.expire_at);
|
||
const now = new Date();
|
||
const isActive = expireDate > now;
|
||
|
||
return {
|
||
isActive,
|
||
text: isActive ? 'فعال' : 'منقضی شده',
|
||
color: isActive
|
||
? 'bg-green-50 dark:bg-green-500/10 text-green-700 dark:text-green-400'
|
||
: 'bg-red-50 dark:bg-red-500/10 text-red-700 dark:text-red-400',
|
||
dotColor: isActive ? 'bg-green-500' : 'bg-red-500',
|
||
};
|
||
};
|
||
|
||
const getSubscriptionCount = (productId: number): number => {
|
||
return products.filter(p => p.id === productId).length;
|
||
};
|
||
|
||
const productsByPackage = products.reduce((acc, product) => {
|
||
const packageName = product.package_name?.name || 'نامشخص';
|
||
if (!acc[packageName]) {
|
||
acc[packageName] = [];
|
||
}
|
||
acc[packageName].push(product);
|
||
return acc;
|
||
}, {} as Record<string, UserProduct[]>);
|
||
|
||
const handleSubscribeClick = (productId: number) => {
|
||
setSelectedProductId(productId);
|
||
onSubscribe(productId);
|
||
};
|
||
|
||
return (
|
||
<div className="bg-white dark:bg-gray-900 rounded-2xl ring-1 ring-gray-200 dark:ring-gray-800 shadow-sm overflow-hidden">
|
||
<div className="px-5 py-4 border-b border-gray-200 dark:border-gray-800">
|
||
<h3 className="text-base font-semibold text-gray-900 dark:text-gray-100">
|
||
اشتراکهای کاربر
|
||
</h3>
|
||
</div>
|
||
|
||
{/* Current Subscriptions */}
|
||
<div className="divide-y divide-gray-200 dark:divide-gray-800">
|
||
{Object.keys(productsByPackage).length > 0 ? (
|
||
Object.entries(productsByPackage).map(([packageName, packageProducts]) => (
|
||
<div key={packageName}>
|
||
<div className="px-5 py-2.5 bg-gray-50 dark:bg-gray-800/60">
|
||
<span className="text-xs font-medium text-gray-500 dark:text-gray-400">
|
||
پکیج: {packageName}
|
||
</span>
|
||
</div>
|
||
<ul className="divide-y divide-gray-100 dark:divide-gray-800/60">
|
||
{packageProducts.map((product) => {
|
||
const status = getSubscriptionStatus(product);
|
||
const subscriptionCount = getSubscriptionCount(product.id);
|
||
|
||
return (
|
||
<li key={`${product.id}-${product.pivot?.purchase_token || Math.random()}`} className="px-5 py-4">
|
||
<div className="flex items-start justify-between gap-4">
|
||
<div className="flex-1 min-w-0">
|
||
<div className="flex items-center gap-2 flex-wrap">
|
||
<p className="text-sm font-semibold text-indigo-600 dark:text-indigo-400">
|
||
{product.title || 'محصول'}
|
||
</p>
|
||
{subscriptionCount > 1 && (
|
||
<span className="px-2 py-0.5 text-xs font-medium bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 rounded-lg">
|
||
×{subscriptionCount}
|
||
</span>
|
||
)}
|
||
<span className={`inline-flex items-center gap-1.5 px-2.5 py-0.5 text-xs font-medium rounded-lg ${status.color}`}>
|
||
<span className={`h-1.5 w-1.5 rounded-full ${status.dotColor}`} />
|
||
{status.text}
|
||
</span>
|
||
</div>
|
||
<div className="mt-2 flex flex-wrap gap-x-5 gap-y-1 text-xs text-gray-500 dark:text-gray-400">
|
||
<span>قیمت: {formatPrice(product.price)} تومان</span>
|
||
<span>نوع: {getProductTypeLabel(product.type)}</span>
|
||
{product.pivot?.expire_at && (
|
||
<span>انقضا: {formatDate(product.pivot.expire_at)}</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
<button
|
||
onClick={() => onUnsubscribe(product.id)}
|
||
disabled={isLoading}
|
||
className="flex-shrink-0 px-3 py-1.5 text-xs font-medium text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-500/10 hover:bg-red-100 dark:hover:bg-red-500/20 rounded-lg disabled:opacity-50 transition-colors"
|
||
>
|
||
لغو اشتراک
|
||
</button>
|
||
</div>
|
||
</li>
|
||
);
|
||
})}
|
||
</ul>
|
||
</div>
|
||
))
|
||
) : (
|
||
<div className="px-5 py-10 text-center">
|
||
<div className="mx-auto h-10 w-10 rounded-xl bg-gray-100 dark:bg-gray-800 flex items-center justify-center mb-2">
|
||
<XCircleIcon className="h-5 w-5 text-gray-400" />
|
||
</div>
|
||
<p className="text-sm text-gray-500 dark:text-gray-400">کاربر هیچ اشتراکی ندارد</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Available Products for Subscription */}
|
||
{availableProducts && availableProducts.length > 0 && selectedPackage && (
|
||
<div className="border-t border-gray-200 dark:border-gray-800 px-5 py-5">
|
||
<h4 className="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-3">
|
||
افزودن اشتراک جدید
|
||
</h4>
|
||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3">
|
||
{availableProducts.map((product) => {
|
||
const subscriptionCount = getSubscriptionCount(product.id);
|
||
|
||
return (
|
||
<button
|
||
key={product.id}
|
||
onClick={() => handleSubscribeClick(product.id)}
|
||
disabled={isLoading}
|
||
className="flex items-center gap-3 p-3.5 border border-dashed border-indigo-200 dark:border-indigo-800/60 rounded-xl text-right hover:border-indigo-400 hover:bg-indigo-50 dark:hover:border-indigo-600 dark:hover:bg-indigo-500/5 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-150 group"
|
||
>
|
||
<div className="h-8 w-8 rounded-lg bg-indigo-100 dark:bg-indigo-500/15 flex items-center justify-center flex-shrink-0 group-hover:bg-indigo-200 dark:group-hover:bg-indigo-500/25 transition-colors">
|
||
<PlusCircleIcon className="h-4 w-4 text-indigo-600 dark:text-indigo-400" />
|
||
</div>
|
||
<div className="min-w-0">
|
||
<p className="text-sm font-medium text-gray-900 dark:text-gray-100 truncate">
|
||
{product.title || 'محصول'}
|
||
</p>
|
||
{subscriptionCount > 0 && (
|
||
<p className="text-xs text-amber-600 dark:text-amber-400">
|
||
{subscriptionCount} اشتراک فعال
|
||
</p>
|
||
)}
|
||
</div>
|
||
</button>
|
||
);
|
||
})}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|