Per design feedback, restructure the mobile product page (desktop layout unchanged except color swatches): - remove the top seller bar; the seller/brand (logo + name + ارسال پیام) now sits in a row below the gallery. - add a floating circular back button over the image (safe-area aware). - move the rating out of the action row into a prominent body row, shown only when the average rating is > 0. - color swatches are now circles with a selection ring (shared with desktop for consistency) instead of squares. - make the افزودن به سبد خرید button full-width/taller with a compact price beside it. - the action row keeps save / share / add-to-collection / try-on. - the red discount icon is kept as-is. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
217 lines
6.6 KiB
TypeScript
217 lines
6.6 KiB
TypeScript
import { ShoppingCart, X, Check } from "lucide-react";
|
||
import { Button } from "~/components/ui/button";
|
||
import discountRed from "~/assets/icons/product/discount-red.svg";
|
||
import { useState } from "react";
|
||
import { useToast } from "~/hooks/use-toast";
|
||
import {
|
||
Drawer,
|
||
DrawerContent,
|
||
DrawerHeader,
|
||
DrawerTitle,
|
||
} from "~/components/ui/drawer";
|
||
import { Link } from "@remix-run/react";
|
||
import { useAddToCart } from "../../requestHandler/use-cart-hooks";
|
||
import { findVariantId, formatPrice } from "../../utils/helpers";
|
||
import { ProductDetail } from "src/api/types";
|
||
import { useRootData } from "~/hooks/use-root-data";
|
||
import { LoginRequiredDialog } from "../LoginRequiredDialog";
|
||
|
||
interface BottomBarProps {
|
||
product: ProductDetail;
|
||
selectedColor: string | null;
|
||
selectedSize: string | null;
|
||
currentPrice: string;
|
||
currentDiscountPrice: string | null;
|
||
currentStock: number | null;
|
||
}
|
||
|
||
interface PriceDisplayProps {
|
||
currentPrice: string;
|
||
currentDiscountPrice: string | null;
|
||
}
|
||
|
||
const PriceDisplay = ({
|
||
currentPrice,
|
||
currentDiscountPrice,
|
||
}: PriceDisplayProps) => {
|
||
return (
|
||
<div className="flex flex-col gap-1">
|
||
{currentDiscountPrice && (
|
||
<div className="flex items-center gap-3">
|
||
<p className="text-GRAY line-through decoration-RED decoration-2 text-B14">
|
||
{formatPrice(currentPrice)}
|
||
</p>
|
||
<img src={discountRed} alt="discount-red" className="w-6 h-6" />
|
||
</div>
|
||
)}
|
||
<div className="flex items-center gap-3">
|
||
<p className="text-B14 font-bold">
|
||
{formatPrice(currentDiscountPrice || currentPrice)}
|
||
</p>
|
||
<p className="text-B14 font-bold">تومان</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
interface AddToCartButtonProps {
|
||
isOutOfStock: boolean;
|
||
onClick: () => void;
|
||
}
|
||
|
||
const AddToCartButton = ({ isOutOfStock, onClick }: AddToCartButtonProps) => (
|
||
<Button
|
||
variant="dark"
|
||
size="xl"
|
||
className="flex-1 h-[52px] rounded-[14px] text-B14 font-bold flex items-center justify-center gap-2 bg-VITROWN_BLUE text-white"
|
||
disabled={isOutOfStock}
|
||
onClick={onClick}
|
||
>
|
||
{isOutOfStock ? (
|
||
<span className="text-B14 font-bold">ناموجود</span>
|
||
) : (
|
||
<>
|
||
<ShoppingCart size={22} />
|
||
افزودن به سبد خرید
|
||
</>
|
||
)}
|
||
</Button>
|
||
);
|
||
|
||
interface CartDrawerProps {
|
||
isOpen: boolean;
|
||
onOpenChange: (open: boolean) => void;
|
||
}
|
||
|
||
const CartDrawer = ({ isOpen, onOpenChange }: CartDrawerProps) => (
|
||
<Drawer open={isOpen} onOpenChange={onOpenChange}>
|
||
<DrawerContent>
|
||
<DrawerHeader className="flex flex-col py-3">
|
||
<div className="flex relative pt-2 text-B12 font-bold items-center justify-center w-full">
|
||
<X
|
||
className="absolute right-2 cursor-pointer"
|
||
size={24}
|
||
onClick={() => onOpenChange(false)}
|
||
/>
|
||
<DrawerTitle className="text-B12 font-bold">سبد خرید</DrawerTitle>
|
||
</div>
|
||
</DrawerHeader>
|
||
<div className="p-4 flex flex-col gap-4">
|
||
<div className="flex flex-col gap-10 w-full">
|
||
<div className="flex items-center justify-center w-full">
|
||
<Check className="text-green-500" size={48} />
|
||
<p className="text-B14 font-bold mr-2">
|
||
محصول به سبد خرید اضافه شد!
|
||
</p>
|
||
</div>
|
||
<div className="flex flex-col w-full gap-1">
|
||
<Link to="/cart" className="w-full">
|
||
<Button
|
||
variant="dark"
|
||
size="lg"
|
||
className="w-full font-bold"
|
||
onClick={() => onOpenChange(false)}
|
||
>
|
||
مشاهده سبد خرید
|
||
</Button>
|
||
</Link>
|
||
<Button
|
||
variant="outline"
|
||
size="lg"
|
||
className="w-full font-bold"
|
||
onClick={() => onOpenChange(false)}
|
||
>
|
||
ادامه خرید
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</DrawerContent>
|
||
</Drawer>
|
||
);
|
||
|
||
export const BottomBar = ({
|
||
product,
|
||
selectedColor,
|
||
selectedSize,
|
||
currentPrice,
|
||
currentDiscountPrice,
|
||
currentStock,
|
||
}: BottomBarProps) => {
|
||
const { toast } = useToast();
|
||
const { user } = useRootData();
|
||
const [isCartDrawerOpen, setIsCartDrawerOpen] = useState(false);
|
||
const [isLoginModalOpen, setIsLoginModalOpen] = useState(false);
|
||
|
||
// Use the addToCart mutation hook
|
||
const addToCartMutation = useAddToCart();
|
||
|
||
const isOutOfStock = currentStock === null || currentStock <= 0;
|
||
|
||
const handleAddToCart = async () => {
|
||
// Check if user is logged in
|
||
if (!user?.access_token) {
|
||
setIsLoginModalOpen(true);
|
||
return;
|
||
}
|
||
if (currentStock !== null && currentStock > 0) {
|
||
// Find the variant ID based on selected color and size
|
||
const variantId = findVariantId(product, selectedColor, selectedSize);
|
||
|
||
if (!variantId) {
|
||
toast({
|
||
title: "خطا",
|
||
description: "محصول با ویژگیهای انتخابی شما یافت نشد",
|
||
variant: "destructive",
|
||
});
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// Add to cart using React Query mutation
|
||
await addToCartMutation.mutateAsync({
|
||
variantId,
|
||
quantity: 1,
|
||
});
|
||
|
||
toast({
|
||
title: "سبد خرید",
|
||
description: "محصول با موفقیت به سبد خرید اضافه شد",
|
||
variant: "default",
|
||
});
|
||
|
||
setIsCartDrawerOpen(true);
|
||
} catch (error) {
|
||
console.error("Error adding to cart:", error);
|
||
toast({
|
||
title: "خطا",
|
||
description: "افزودن محصول به سبد خرید با مشکل مواجه شد",
|
||
variant: "destructive",
|
||
});
|
||
}
|
||
}
|
||
};
|
||
|
||
return (
|
||
<>
|
||
<div className="max-w-md mx-auto fixed gap-3 items-center bottom-[calc(50px+env(safe-area-inset-bottom))] left-0 right-0 border-t border-inner-border bg-WHITE p-4 z-50 flex flex-row lg:static lg:max-w-none lg:mx-0 lg:border lg:rounded-xl lg:mt-6 lg:z-auto">
|
||
<PriceDisplay
|
||
currentPrice={currentPrice}
|
||
currentDiscountPrice={currentDiscountPrice}
|
||
/>
|
||
<AddToCartButton isOutOfStock={isOutOfStock} onClick={handleAddToCart} />
|
||
</div>
|
||
|
||
<CartDrawer
|
||
isOpen={isCartDrawerOpen}
|
||
onOpenChange={setIsCartDrawerOpen}
|
||
/>
|
||
<LoginRequiredDialog
|
||
loginTitle="برای افزودن محصول به سبد خرید ابتدا باید وارد حساب کاربری خود شوید."
|
||
isOpen={isLoginModalOpen}
|
||
onOpenChange={setIsLoginModalOpen}
|
||
/>
|
||
</>
|
||
);
|
||
};
|