Vitron-Front/app/components/product/BottomBar.tsx
Arda Samadi 692d385d09 feat(product): mobile redesign — brand below image, floating back, circular colors
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>
2026-06-27 15:06:54 +03:30

217 lines
6.6 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 { 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}
/>
</>
);
};