Vitron-Front/app/routes/profile.sets._index.tsx
2026-04-29 01:44:16 +03:30

289 lines
9.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { MetaFunction } from "@remix-run/node";
import { Link } from "@remix-run/react";
import { Layers, Trash2, Loader2, ChevronLeft, ShoppingBag } from "lucide-react";
import { useState } from "react";
import { Button } from "~/components/ui/button";
import { Skeleton } from "~/components/ui/skeleton";
import { Dialog, DialogContent, DialogOverlay } from "~/components/ui/dialog";
import {
useUserCollections,
useDeleteCollection,
} from "../requestHandler/use-user-collection-hooks";
import { UserCollection, ProductDetail } from "../../src/api/types";
import ProfilePagesHeader from "~/components/ProfilePagesHeader";
import { useQuery } from "@tanstack/react-query";
export default function ProfileSets() {
const { data: collections, isLoading, refetch } = useUserCollections();
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
const [selectedCollection, setSelectedCollection] =
useState<UserCollection | null>(null);
const handleDeleteClick = (
e: React.MouseEvent,
collection: UserCollection
) => {
e.preventDefault();
e.stopPropagation();
setSelectedCollection(collection);
setDeleteModalOpen(true);
};
return (
<div className="flex flex-col min-h-screen bg-WHITE2">
{/* Header */}
<ProfilePagesHeader title="ست‌های من" />
{/* Content */}
<div className="flex-1 p-4">
{isLoading ? (
<div className="flex flex-col gap-4">
{[1, 2, 3].map((i) => (
<Skeleton key={i} className="h-28 w-full rounded-2xl" />
))}
</div>
) : collections && collections.length > 0 ? (
<div className="flex flex-col gap-4">
{collections.map((collection) => (
<SetCard
key={collection.id}
collection={collection}
onDelete={(e) => handleDeleteClick(e, collection)}
/>
))}
</div>
) : (
<div className="flex flex-col items-center justify-center py-16">
<div className="w-24 h-24 rounded-full bg-PRIMARY/10 flex items-center justify-center mb-4">
<Layers size={48} className="text-PRIMARY" />
</div>
<p className="text-B16 text-BLACK mb-2">هنوز ستی ندارید</p>
<p className="text-R14 text-GRAY text-center px-8">
با رفتن به صفحه محصولات و زدن دکمه افزودن به ست، اولین ست خود را
بسازید
</p>
</div>
)}
</div>
{/* Delete confirmation modal */}
<DeleteSetModal
open={deleteModalOpen}
onOpenChange={setDeleteModalOpen}
collection={selectedCollection}
onSuccess={() => {
refetch();
setDeleteModalOpen(false);
setSelectedCollection(null);
}}
/>
</div>
);
}
// Set Card Component
const SetCard = ({
collection,
onDelete,
}: {
collection: UserCollection;
onDelete: (e: React.MouseEvent) => void;
}) => {
const productIds = collection.products?.slice(0, 4) || [];
return (
<Link
to={`/profile/sets/${collection.id}`}
className="flex flex-col rounded-2xl bg-WHITE shadow-sm hover:shadow-md transition-all overflow-hidden"
>
{/* Product Images Row */}
<div className="flex h-24 bg-WHITE2">
{productIds.length > 0 ? (
<>
{productIds.slice(0, 4).map((productId, index) => (
<ProductImageCell
key={productId}
productId={productId}
isLast={index === 3 && (collection.products?.length || 0) > 4}
extraCount={(collection.products?.length || 0) - 4}
/>
))}
{/* Fill empty slots if less than 4 products */}
{productIds.length < 4 &&
Array.from({ length: 4 - productIds.length }).map((_, i) => (
<div
key={`empty-${i}`}
className="flex-1 bg-WHITE2 border-l border-WHITE first:border-l-0"
/>
))}
</>
) : (
<div className="flex-1 flex items-center justify-center">
<Layers size={32} className="text-GRAY/30" />
</div>
)}
</div>
{/* Info Section */}
<div className="flex items-center gap-3 p-4">
<div className="w-10 h-10 rounded-full bg-PRIMARY/10 flex items-center justify-center flex-shrink-0">
<Layers size={20} className="text-PRIMARY" />
</div>
<div className="flex-1 min-w-0">
<h3 className="text-B14 font-bold text-BLACK truncate">
{collection.title}
</h3>
<p className="text-R12 text-GRAY">
{collection.products?.length || 0} محصول
</p>
</div>
<div className="flex items-center gap-1">
<button
onClick={onDelete}
className="p-2 rounded-full hover:bg-RED/10 transition-colors"
>
<Trash2 size={18} className="text-RED" />
</button>
<ChevronLeft size={20} className="text-GRAY" />
</div>
</div>
</Link>
);
};
// Product Image Cell for the grid
const ProductImageCell = ({
productId,
isLast,
extraCount,
}: {
productId: string;
isLast: boolean;
extraCount: number;
}) => {
const { data: product, isLoading } = useQuery({
queryKey: ["product-thumb-cell", productId],
queryFn: async (): Promise<ProductDetail> => {
const response = await fetch(
`${
typeof window !== "undefined"
? import.meta.env.VITE_API_BASE_URL || "https://api.prod.vitrown.com"
: process.env.VITE_API_BASE_URL || "https://api.prod.vitrown.com"
}/api/product/v1/products/${productId}/`
);
if (!response.ok) throw new Error("Failed to fetch product");
return response.json();
},
enabled: !!productId,
staleTime: 10 * 60 * 1000,
});
if (isLoading) {
return (
<div className="flex-1 border-l border-WHITE first:border-l-0">
<Skeleton className="w-full h-full" />
</div>
);
}
return (
<div className="flex-1 border-l border-WHITE first:border-l-0 relative">
{product?.imageUrls?.[0] ? (
<img
src={product.imageUrls[0]}
alt=""
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full bg-GRAY/5 flex items-center justify-center">
<ShoppingBag size={20} className="text-GRAY/40" />
</div>
)}
{isLast && extraCount > 0 && (
<div className="absolute inset-0 bg-BLACK/60 flex items-center justify-center">
<span className="text-WHITE text-B16 font-bold">+{extraCount}</span>
</div>
)}
</div>
);
};
const DeleteSetModal = ({
open,
onOpenChange,
collection,
onSuccess,
}: {
open: boolean;
onOpenChange: (open: boolean) => void;
collection: UserCollection | null;
onSuccess: () => void;
}) => {
const { mutate: deleteCollection, isPending } = useDeleteCollection();
const handleDelete = () => {
if (!collection?.id) return;
deleteCollection(collection.id, {
onSuccess: () => {
onSuccess();
},
});
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogOverlay />
<DialogContent className="p-4 bg-WHITE rounded-[15px] w-[calc(100%-40px)]">
<div className="flex flex-col gap-6">
<div className="flex flex-col gap-3 items-center w-full">
<div className="w-16 h-16 rounded-full bg-RED/10 flex items-center justify-center">
<Trash2 size={32} className="text-RED" />
</div>
<p className="text-B14 font-bold mt-1">حذف ست</p>
<p className="text-R12 text-GRAY text-center">
آیا از حذف ست &quot;{collection?.title}&quot; مطمئن هستید؟
</p>
</div>
<div className="flex flex-row gap-4 w-full">
<Button
className="w-[100%]"
size="sm"
variant="dark"
onClick={() => onOpenChange(false)}
disabled={isPending}
>
انصراف
</Button>
<Button
className="w-[100%] bg-RED hover:bg-RED/90"
variant="primary"
size="sm"
onClick={handleDelete}
disabled={isPending}
>
{isPending ? (
<>
<Loader2 className="h-4 w-4 animate-spin ml-2" />
در حال حذف...
</>
) : (
"حذف ست"
)}
</Button>
</div>
</div>
</DialogContent>
</Dialog>
);
};
export const meta: MetaFunction = () => {
return [
{ title: "ست‌های من | ویترون" },
{ name: "description", content: "مدیریت ست‌های محصولات شما" },
{ name: "robots", content: "noindex, follow" },
];
};