Vitron-Front/app/routes/cart._index.tsx
Arda Samadi 9650073b04 Desktop responsive: search page, cart list, masonry grid
- MondrianProductList: add desktop 4-column masonry (mobile 2-col unchanged);
  improves home "for you", search results and seller feed at lg+
- search: hide mobile SearchTopBar at lg, add results heading + centered
  1320px container
- cart list: responsive 2-column grid of cart cards + desktop heading
- MyLayout: desktop chrome gate now covers / , /search/* and /cart

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 21:06:34 +03:30

223 lines
8.4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 { Link, useNavigate } from "@remix-run/react";
import { MetaFunction } from "@remix-run/node";
import { ArrowLeft, Trash2 } from "lucide-react";
import { useToast } from "../hooks/use-toast";
import { useGetAllCarts, useClearCart } from "../requestHandler/use-cart-hooks";
import { formatPrice } from "../utils/helpers";
import { ProductImageCarousel } from "~/components/product";
import { CartDetail, CartItem } from "src/api/types";
import ProfilePagesHeader from "../components/ProfilePagesHeader";
import UiProvider from "~/components/UiProvider";
import SellerLogo from "~/components/SellerLogo";
import { Dialog, DialogContent, DialogOverlay } from "~/components/ui/dialog";
import { Button } from "~/components/ui/button";
import { useState } from "react";
export default function CartIndex() {
const { data: carts = [], isLoading, refetch, isError } = useGetAllCarts();
const clearCartMutation = useClearCart();
const { toast } = useToast();
const navigate = useNavigate();
const [confirmDialogOpen, setConfirmDialogOpen] = useState(false);
const [cartToDelete, setCartToDelete] = useState<string | null>(null);
const calculateCartTotal = (cart: CartDetail) => {
if (!cart.items || cart.items.length === 0) return "0";
const total = cart.items.reduce((sum: number, item: CartItem) => {
const price = parseInt(item.priceAtPurchase) * item.quantity;
return sum + price;
}, 0);
return total.toString();
};
// Handle clearing a cart
const handleClearCart = async () => {
if (!cartToDelete) return;
try {
await clearCartMutation.mutateAsync(cartToDelete);
toast({
title: "سبد خرید",
description: "سبد خرید با موفقیت خالی شد",
variant: "default",
});
setConfirmDialogOpen(false);
setCartToDelete(null);
} catch (error) {
console.error("Error clearing cart:", error);
toast({
title: "خطا",
description: "خالی کردن سبد خرید با مشکل مواجه شد",
variant: "destructive",
});
}
};
const openConfirmDialog = (cartId: string) => {
setCartToDelete(cartId);
setConfirmDialogOpen(true);
};
return (
<div className="bg-background">
{/* Header (mobile) */}
<div className="lg:hidden">
<ProfilePagesHeader hideBackButton={true} title="لیست سبد خرید" />
</div>
{/* Cart Content */}
<div className="flex flex-col gap-4 p-4 lg:max-w-[1320px] lg:mx-auto lg:w-full lg:px-8 lg:py-8">
<h1 className="hidden lg:block text-[28px] font-extrabold mb-2">
سبد خرید
</h1>
<UiProvider
isLoading={isLoading}
isError={isError}
isEmpty={carts.length === 0}
onRetry={() => {
refetch();
}}
type="cart"
>
<div className="space-y-4 w-full lg:space-y-0 lg:grid lg:grid-cols-2 lg:gap-5">
{carts.map((cart) => {
return (
<div
role="button"
tabIndex={0}
onKeyDown={() => {}}
onClick={() => {
navigate(`/cart/${cart.id}`);
}}
key={cart.id}
className="bg-WHITE overflow-hidden shadow-md rounded-[15px] border flex gap-2"
>
<div className="w-2/5">
<ProductImageCarousel
images={
cart.items?.map(
(item) => item.productVariant?.productImage || ""
) || []
}
hideIndex={true}
/>
</div>
<div className="w-3/5 flex p-4 justify-between gap-2">
<div className="w-full flex justify-between flex-col gap-3">
<a
href={`/seller/${cart.seller?.username}`}
className="w-full gap-2 flex items-center"
>
<SellerLogo
size="sm"
src={cart.seller?.logo}
alt={"seller-image"}
/>
<p className="text-B12 font-bold">
{cart.seller?.name}
</p>
</a>
<p className="text-B12 font-bold line-clamp-1">
{cart.items
?.map(
(item) => item.productVariant?.productTitle || ""
)
.join(", ")}
</p>
<div className="w-full gap-1 flex">
<p className="text-B12 font-bold">مبلغ فاکتور:</p>
<p className="text-R12">
{formatPrice(calculateCartTotal(cart))} تومان
</p>
</div>
</div>
<div className="flex justify-between flex-col gap-3">
<Trash2
className={`h-6 w-6 ${
clearCartMutation.isPending ? "text-GRAY" : "text-RED"
}`}
onClick={(e) => {
e.stopPropagation();
if (!clearCartMutation.isPending) {
openConfirmDialog(cart.id || "");
}
}}
/>
<Link to={`/cart/${cart.id}`}>
<ArrowLeft className="h-6 w-6" />
</Link>
</div>
</div>
</div>
);
})}
</div>
</UiProvider>
</div>
<Dialog open={confirmDialogOpen} onOpenChange={setConfirmDialogOpen}>
<DialogOverlay />
<DialogContent className="p-4 bg-WHITE rounded-[15px] w-[calc(100%-40px)]">
<div className="flex flex-col gap-10">
<div className={"gap-3 items-center w-full"}>
<p className="text-B12 font-bold mt-1">تایید حذف سبد خرید</p>
<p className="text-R12">
آیا مطمئن هستید که میخواهید این سبد خرید را خالی کنید؟ این عمل
قابل بازگشت نیست.
</p>
</div>
<div className="flex flex-row gap-6 w-full">
<Button
className="w-[100%]"
size={"sm"}
variant="dark"
onClick={() => setConfirmDialogOpen(false)}
disabled={clearCartMutation.isPending}
>
انصراف
</Button>
<Button
onClick={handleClearCart}
disabled={clearCartMutation.isPending}
className="w-[100%]"
size={"sm"}
variant="primary"
>
{clearCartMutation.isPending ? "در حال حذف..." : "حذف سبد"}
</Button>
</div>
</div>
</DialogContent>
</Dialog>
</div>
);
}
export const meta: MetaFunction = () => {
const title = "سبد خرید | ویترون";
const description =
"مشاهده و مدیریت سبد خرید شما در ویترون. بررسی محصولات، تغییر تعداد و نهایی کردن خرید.";
const imageUrl =
"https://vitrownstatics.s3.ir-thr-at1.arvanstorage.ir/SVG-06.svg?versionId=";
return [
{ title },
{ name: "description", content: description },
{ name: "keywords", content: "سبد خرید، خرید آنلاین، پرداخت، ویترون" },
{ name: "robots", content: "noindex, follow" },
// Open Graph
{ property: "og:type", content: "website" },
{ property: "og:title", content: title },
{ property: "og:description", content: description },
{ property: "og:image", content: imageUrl },
{ property: "og:site_name", content: "ویترون" },
{ property: "og:locale", content: "fa_IR" },
// Twitter Card
{ name: "twitter:card", content: "summary" },
{ name: "twitter:title", content: title },
{ name: "twitter:description", content: description },
];
};