Vitron-Front/app/routes/cart.$cartId.tsx
2026-04-29 01:44:16 +03:30

327 lines
11 KiB
TypeScript

import {
json,
type LoaderFunctionArgs,
type MetaFunction,
} from "@remix-run/node";
import { useParams, useSearchParams } from "@remix-run/react";
import { useToast } from "../hooks/use-toast";
import {
useGetCartDetails,
useUpdateCartItemQuantity,
useRemoveFromCart,
} from "../requestHandler/use-cart-hooks";
import { useCreateShippingInfo } from "../requestHandler/use-shipping-hooks";
import { useCallback, useEffect, useState } from "react";
import { CartHeader } from "../components/cart/CartHeader";
import { CartItem } from "../components/cart/CartItem";
import { CartSummary } from "../components/cart/CartSummary";
import { ShippingInformation } from "../components/cart/ShippingInformation";
import { ReceiverInformation } from "../components/cart/ReceiverInformation";
import { useCashOutCart } from "../requestHandler/use-order-hooks";
import {
Address,
CartDetail as CartDetailType,
CartItem as CartItemType,
SellerCourierList,
} from "src/api/types";
import UiProvider from "~/components/UiProvider";
export const meta: MetaFunction = () => {
return [
{ title: "سبد خرید - ویترون" },
{
name: "description",
content:
"مدیریت سبد خرید شما در ویترون. بررسی محصولات، انتخاب روش ارسال و تکمیل فرآیند خرید.",
},
{
name: "keywords",
content: "سبد خرید، خرید آنلاین، پرداخت، ارسال، ویترون",
},
{ name: "robots", content: "noindex, follow" },
{ property: "og:title", content: "سبد خرید - ویترون" },
{
property: "og:description",
content:
"مدیریت سبد خرید شما در ویترون. بررسی محصولات، انتخاب روش ارسال و تکمیل فرآیند خرید.",
},
{ property: "og:type", content: "website" },
{ name: "twitter:card", content: "summary" },
{ name: "twitter:title", content: "سبد خرید - ویترون" },
{
name: "twitter:description",
content:
"مدیریت سبد خرید شما در ویترون. بررسی محصولات، انتخاب روش ارسال و تکمیل فرآیند خرید.",
},
];
};
// Server-side loader to fetch initial cart data
export async function loader({ params }: LoaderFunctionArgs) {
const { cartId } = params;
if (!cartId) {
throw new Response("Missing cart ID", { status: 400 });
}
return json({ cartId });
}
// Main Cart Component
export default function CartDetail() {
const { cartId } = useParams();
const [searchParams, setSearchParams] = useSearchParams();
const {
data: cart,
isLoading: isLoadingCart,
refetch,
isError,
} = useGetCartDetails(cartId || "");
const updateMutation = useUpdateCartItemQuantity();
const removeMutation = useRemoveFromCart();
const { toast } = useToast();
const [step, setStep] = useState<number>(0);
const createShippingInfo = useCreateShippingInfo();
const cashOutMutation = useCashOutCart(cartId || "");
// Calculate total price of all items in the cart
const calculateTotal = useCallback(
(
cart: CartDetailType | null | undefined,
deliveryPrice?: number | null,
discountValue?: number | null
) => {
if (!cart || !cart.items || cart.items.length === 0) return "0";
let total = cart.items.reduce((sum: number, item: CartItemType) => {
if (item.productVariant?.stock && item.productVariant?.stock > 0) {
const price =
Number(
item.productVariant?.discountPrice || item.productVariant?.price
) * item.quantity;
return sum + price;
}
return sum;
}, 0);
if (deliveryPrice) {
total += deliveryPrice;
}
if (discountValue) {
total -= discountValue;
}
return total.toString();
},
[]
);
// Handle quantity change
const handleQuantityChange = async (itemId: string, newQuantity: number) => {
if (newQuantity < 1) return;
try {
await updateMutation.mutateAsync({ itemId, quantity: newQuantity });
} catch (error) {
console.error("Error updating item quantity:", error);
toast({
title: "خطا",
description: "بروزرسانی تعداد محصول با مشکل مواجه شد",
variant: "destructive",
});
}
};
// Handle item removal
const handleRemoveItem = async (itemId: string) => {
try {
await removeMutation.mutateAsync(itemId);
toast({
title: "سبد خرید",
description: "محصول با موفقیت از سبد خرید حذف شد",
variant: "default",
});
} catch (error) {
console.error("Error removing item:", error);
toast({
title: "خطا",
description: "حذف محصول از سبد خرید با مشکل مواجه شد",
variant: "destructive",
});
}
};
const [selectedAddressInfo, setSelectedAddressInfo] =
useState<Address | null>(null);
const [selectedCourierInfo, setSelectedCourierInfo] =
useState<SellerCourierList | null>(null);
const [shippingInfoId, setShippingInfoId] = useState<string>("");
const [isCreatingOrder, setIsCreatingOrder] = useState<boolean>(false);
const [shippingInfoIsLoading, setShippingInfoIsLoading] =
useState<boolean>(false);
// Initialize step from URL query parameter when component mounts
useEffect(() => {
const stepParam = searchParams.get("step");
if (stepParam) {
const newStep = parseInt(stepParam, 10);
if (!isNaN(newStep) && newStep >= 0 && newStep <= 2) {
setStep(newStep);
}
// Clean up the URL by removing the step parameter
const newSearchParams = new URLSearchParams(searchParams);
newSearchParams.delete("step");
setSearchParams(newSearchParams, { replace: true });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // Only run once on mount
useEffect(() => {
console.log("selectedCourierInfo", selectedCourierInfo);
}, [selectedCourierInfo]);
const continueHandler = async () => {
if (step === 0) {
setStep(1);
} else if (step === 1) {
const isFaceToFace = selectedCourierInfo?.courierIsFaceToFace;
if (!isFaceToFace && (!selectedAddressInfo || !selectedAddressInfo?.id)) {
toast({
title: "خطا",
description: "لطفا آدرس را انتخاب کنید",
variant: "destructive",
});
return;
}
if (!selectedCourierInfo || !selectedCourierInfo?.id) {
toast({
title: "خطا",
description: "لطفا روش ارسال را انتخاب کنید",
variant: "destructive",
});
return;
}
try {
const response = await createShippingInfo.mutateAsync({
address: isFaceToFace ? undefined : selectedAddressInfo?.id,
sellerCourier: selectedCourierInfo.id,
});
setShippingInfoId(response.id || "");
setStep(2);
} catch (error) {
console.error("Error creating shipping info:", error);
toast({
title: "خطا",
description: "ثبت اطلاعات ارسال با مشکل مواجه شد",
variant: "destructive",
});
}
} else if (step === 2) {
if (!shippingInfoId) {
toast({
title: "Error",
description: "Shipping information ID is missing.",
variant: "destructive",
});
return;
}
setIsCreatingOrder(true);
try {
console.log(
`Initiating cash out for cart ${cartId} with shipping info ID: ${shippingInfoId}`
);
const response = await cashOutMutation.mutateAsync({
shippingInfoId: shippingInfoId,
});
console.log("Cash out mutation initiated.");
window.location.href = response.redirect;
} catch (error) {
console.error(
"Error caught in continueHandler during cash out:",
error
);
toast({
title: "عدم وجود محصول",
description:
"موجودی این محصول کمتر از تعداد درخواستی شما است. لطفا تعداد محصولات موجود را چک کنید.",
variant: "destructive",
});
} finally {
setIsCreatingOrder(false);
}
}
};
return (
<div className="bg-background pb-14">
<CartHeader cart={cart} step={step} setStep={setStep} />
{/* Cart Content */}
<div className=" gap-1 flex flex-col">
<UiProvider
isLoading={isLoadingCart}
isEmpty={!cart || cart.items?.length === 0}
type="cart"
isError={isError}
onRetry={refetch}
>
<>
{step === 0 && (
<div className="flex flex-col w-full">
{cart?.items?.map((item, index) => (
<CartItem
key={item.id}
item={item}
onQuantityChange={handleQuantityChange}
onRemoveItem={handleRemoveItem}
isLoading={removeMutation.isPending}
hasBorder={
!!(cart?.items && index !== cart?.items?.length - 1)
}
/>
))}
</div>
)}
{step === 1 && (
<ShippingInformation
sellerId={cart?.seller?.username || ""}
total={calculateTotal(cart)}
setSelectedAddressInfo={setSelectedAddressInfo}
setSelectedCourierInfo={setSelectedCourierInfo}
setShippingInfoIsLoading={setShippingInfoIsLoading}
selectedAddressInfo={selectedAddressInfo}
cartId={cartId}
/>
)}
{step === 2 && (
<ReceiverInformation
discountValue={Number(cart?.discountValue) || null}
cartItems={cart?.items || []}
selectedAddressInfo={selectedAddressInfo}
selectedCourierInfo={selectedCourierInfo}
total={calculateTotal(
cart,
selectedCourierInfo?.courierIsFreightCollect ? 0 : selectedCourierInfo?.deliveryPrice,
Number(cart?.discountValue)
)}
/>
)}
<CartSummary
total={calculateTotal(
cart,
step === 2 && !selectedCourierInfo?.courierIsFreightCollect ? selectedCourierInfo?.deliveryPrice : 0,
step === 2 ? Number(cart?.discountValue) : 0
)}
onContinue={() => continueHandler()}
step={step}
isLoading={
isCreatingOrder ||
cashOutMutation.isPending ||
shippingInfoIsLoading
}
/>
</>
</UiProvider>
</div>
</div>
);
}