fix tailwind error
deploy to danak / build_and_deploy (push) Has been cancelled

This commit is contained in:
hamid zarghami
2026-07-22 15:55:50 +03:30
parent 80552458cd
commit bc3cb0714b
2 changed files with 316 additions and 370 deletions
+312 -364
View File
@@ -1,400 +1,348 @@
'use client' "use client";
import { CartQuantityControl } from '@/components/CartQuantityControl' import { useCart } from "@/app/[name]/(Dialogs)/cart/hook/useCart";
import { VariableWeightSlider } from '@/components/product/VariableWeightSlider' import { getPrimaryVariantId, getPurchaseUnitLabel, getVariableQuantityRange, isVariablePricing } from "@/app/[name]/(Main)/types/Types";
import PlusIcon from '@/components/icons/PlusIcon' import { CartQuantityControl } from "@/components/CartQuantityControl";
import { ef } from '@/lib/helpers/utfNumbers' import PlusIcon from "@/components/icons/PlusIcon";
import { useCart } from '@/app/[name]/(Dialogs)/cart/hook/useCart' import { VariableWeightSlider } from "@/components/product/VariableWeightSlider";
import { import { toast } from "@/components/Toast";
getPrimaryVariantId, import { getToken } from "@/lib/api/func";
getPurchaseUnitLabel, import { ef } from "@/lib/helpers/utfNumbers";
getVariableQuantityRange, import { ArrowLeft, Cup, Heart, TruckTick } from "iconsax-react";
isVariablePricing, import Image from "next/image";
} from '@/app/[name]/(Main)/types/Types' import { useParams, useRouter } from "next/navigation";
import { ArrowLeft, Cup, Heart, TruckTick } from 'iconsax-react' import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import Image from 'next/image' import { useGetProfile } from "../../(Profile)/profile/hooks/userProfileData";
import { useParams, useRouter } from 'next/navigation' import { useGetAbout } from "../about/hooks/useAboutData";
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useGetProduct, useToggleFavorite } from "./hooks/useProductData";
import { useGetProduct, useToggleFavorite } from './hooks/useProductData'
import { useGetAbout } from '../about/hooks/useAboutData'
import { useGetProfile } from '../../(Profile)/profile/hooks/userProfileData'
import { toast } from '@/components/Toast'
import { getToken } from '@/lib/api/func'
type Props = object type Props = object;
function ProductPage({ }: Props) { function ProductPage({}: Props) {
const { id, name } = useParams();
const router = useRouter();
const { isSuccess } = useGetProfile();
const { data: product, isLoading } = useGetProduct(id as string);
const { data: about } = useGetAbout();
const { items, addToCart, removeFromCart, setCartQuantity, isCartMutating } = useCart();
const [isFavorite, setIsFavorite] = useState<boolean>(false);
const [selectedVariantId, setSelectedVariantId] = useState<string | null>(null);
const [selectedWeight, setSelectedWeight] = useState<number>(1);
const isUserInteractingRef = useRef(false);
const cartSyncTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const pendingCartWeightRef = useRef<number | null>(null);
const { mutate: toggleFavorite } = useToggleFavorite();
const { id, name } = useParams(); const productId = product?.data?.id || (id as string);
const router = useRouter(); const cartId = useMemo(() => {
const { isSuccess } = useGetProfile(); if (!product?.data) return null;
const { data: product, isLoading } = useGetProduct(id as string); return selectedVariantId ?? getPrimaryVariantId(product.data) ?? productId;
const { data: about } = useGetAbout(); }, [product?.data, selectedVariantId, productId]);
const { items, addToCart, removeFromCart, setCartQuantity, isCartMutating } = useCart(); const quantity = useMemo(() => {
const [isFavorite, setIsFavorite] = useState<boolean>(false); if (!cartId) return 0;
const [selectedVariantId, setSelectedVariantId] = useState<string | null>(null); const item = items?.[cartId] ?? items?.[String(cartId)];
const [selectedWeight, setSelectedWeight] = useState<number>(1); if (item && typeof item === "object" && "quantity" in item) {
const isUserInteractingRef = useRef(false); return item.quantity || 0;
const cartSyncTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); }
const pendingCartWeightRef = useRef<number | null>(null); return 0;
const { mutate: toggleFavorite } = useToggleFavorite(); }, [items, cartId]);
const productId = product?.data?.id || id as string; const handleAddToCart = () => {
const cartId = useMemo(() => { if (cartId) {
if (!product?.data) return null; addToCart(cartId);
return selectedVariantId ?? getPrimaryVariantId(product.data) ?? productId; }
}, [product?.data, selectedVariantId, productId]); };
const quantity = useMemo(() => {
if (!cartId) return 0;
const item = items?.[cartId] ?? items?.[String(cartId)];
if (item && typeof item === 'object' && 'quantity' in item) {
return item.quantity || 0;
}
return 0;
}, [items, cartId]);
const handleAddToCart = () => { const handleAddVariableToCart = () => {
if (cartId) { if (!cartId) return;
addToCart(cartId); if (cartSyncTimerRef.current) {
} clearTimeout(cartSyncTimerRef.current);
}; cartSyncTimerRef.current = null;
}
pendingCartWeightRef.current = null;
isUserInteractingRef.current = false;
setCartQuantity(cartId, selectedWeight);
};
const handleAddVariableToCart = () => { const clearCartSyncTimer = useCallback(() => {
if (!cartId) return; if (cartSyncTimerRef.current) {
if (cartSyncTimerRef.current) { clearTimeout(cartSyncTimerRef.current);
clearTimeout(cartSyncTimerRef.current); cartSyncTimerRef.current = null;
cartSyncTimerRef.current = null; }
} }, []);
pendingCartWeightRef.current = null;
const handleVariableWeightChange = useCallback(
(weight: number) => {
isUserInteractingRef.current = true;
setSelectedWeight(weight);
clearCartSyncTimer();
pendingCartWeightRef.current = null;
},
[clearCartSyncTimer],
);
const handleVariableWeightCommit = useCallback(
(weight: number) => {
if (!isSuccess || !cartId || quantity <= 0) {
isUserInteractingRef.current = false; isUserInteractingRef.current = false;
setCartQuantity(cartId, selectedWeight); return;
}; }
const clearCartSyncTimer = useCallback(() => { pendingCartWeightRef.current = weight;
if (cartSyncTimerRef.current) { clearCartSyncTimer();
clearTimeout(cartSyncTimerRef.current);
cartSyncTimerRef.current = null;
}
}, []);
const handleVariableWeightChange = useCallback((weight: number) => { cartSyncTimerRef.current = setTimeout(() => {
isUserInteractingRef.current = true; cartSyncTimerRef.current = null;
setSelectedWeight(weight); const pendingWeight = pendingCartWeightRef.current;
clearCartSyncTimer();
pendingCartWeightRef.current = null; pendingCartWeightRef.current = null;
}, [clearCartSyncTimer]);
const handleVariableWeightCommit = useCallback((weight: number) => { if (pendingWeight == null || !cartId) {
if (!isSuccess || !cartId || quantity <= 0) { isUserInteractingRef.current = false;
isUserInteractingRef.current = false; return;
return;
} }
pendingCartWeightRef.current = weight; const currentItem = items?.[cartId] ?? items?.[String(cartId)];
clearCartSyncTimer(); const currentQty = currentItem && typeof currentItem === "object" && "quantity" in currentItem ? currentItem.quantity : 0;
cartSyncTimerRef.current = setTimeout(() => { if (Math.abs(pendingWeight - currentQty) > 0.0001) {
cartSyncTimerRef.current = null; setCartQuantity(cartId, pendingWeight);
const pendingWeight = pendingCartWeightRef.current;
pendingCartWeightRef.current = null;
if (pendingWeight == null || !cartId) {
isUserInteractingRef.current = false;
return;
}
const currentItem = items?.[cartId] ?? items?.[String(cartId)];
const currentQty = currentItem && typeof currentItem === 'object' && 'quantity' in currentItem
? currentItem.quantity
: 0;
if (Math.abs(pendingWeight - currentQty) > 0.0001) {
setCartQuantity(cartId, pendingWeight);
}
isUserInteractingRef.current = false;
}, 1000);
}, [isSuccess, cartId, quantity, clearCartSyncTimer, items, setCartQuantity]);
const handleRemoveFromCart = () => {
if (cartId) {
removeFromCart(cartId);
} }
}; isUserInteractingRef.current = false;
}, 1000);
},
[isSuccess, cartId, quantity, clearCartSyncTimer, items, setCartQuantity],
);
const handleToggleFavorite = async () => { const handleRemoveFromCart = () => {
if (!productId) return; if (cartId) {
removeFromCart(cartId);
}
};
const token = await getToken(); const handleToggleFavorite = async () => {
if (!token || !isSuccess) { if (!productId) return;
toast('ابتدا لاگین کنید', 'error');
return;
}
// به‌روزرسانی خوش‌بینانه: تغییر state قبل از ارسال درخواست const token = await getToken();
const previousFavorite = isFavorite; if (!token || !isSuccess) {
setIsFavorite(!isFavorite); toast("ابتدا لاگین کنید", "error");
return;
toggleFavorite(productId, {
onSuccess: () => {
// تایید تغییر در صورت موفقیت
},
onError: () => {
// بازگرداندن به حالت قبلی در صورت خطا
setIsFavorite(previousFavorite);
toast('خطا در تغییر وضعیت علاقه‌مندی', 'error');
},
});
};
useEffect(() => {
const typed = product as unknown as { data?: { isFavorite?: boolean } };
const isFav = Boolean(typed?.data?.isFavorite);
setIsFavorite(isFav);
}, [product]);
useEffect(() => {
if (!product?.data) return;
const variants = product.data.variants ?? [];
if (!variants.length) {
setSelectedVariantId(product.data.id);
return;
}
setSelectedVariantId((prev) => prev ?? variants[0].id);
}, [product?.data]);
useEffect(() => {
if (!product?.data || !isVariablePricing(product.data) || !cartId) return;
if (isUserInteractingRef.current) return;
const { min } = getVariableQuantityRange(product.data);
const targetWeight = quantity > 0 ? quantity : min;
setSelectedWeight((prev) => (
Math.abs(prev - targetWeight) < 0.0001 ? prev : targetWeight
));
}, [product?.data, cartId, quantity]);
useEffect(() => () => {
clearCartSyncTimer();
}, [clearCartSyncTimer]);
if (isLoading) {
return (
<div className='flex items-center justify-center min-h-[400px]'>
<p className='text-disabled-text'>در حال بارگذاری...</p>
</div>
);
} }
if (!product?.data) { // به‌روزرسانی خوش‌بینانه: تغییر state قبل از ارسال درخواست
return ( const previousFavorite = isFavorite;
<div className='flex items-center justify-center min-h-[400px]'> setIsFavorite(!isFavorite);
<p className='text-disabled-text'>کالا یافت نشد</p>
</div> toggleFavorite(productId, {
); onSuccess: () => {
// تایید تغییر در صورت موفقیت
},
onError: () => {
// بازگرداندن به حالت قبلی در صورت خطا
setIsFavorite(previousFavorite);
toast("خطا در تغییر وضعیت علاقه‌مندی", "error");
},
});
};
useEffect(() => {
const typed = product as unknown as { data?: { isFavorite?: boolean } };
const isFav = Boolean(typed?.data?.isFavorite);
setIsFavorite(isFav);
}, [product]);
useEffect(() => {
if (!product?.data) return;
const variants = product.data.variants ?? [];
if (!variants.length) {
setSelectedVariantId(product.data.id);
return;
} }
setSelectedVariantId((prev) => prev ?? variants[0].id);
}, [product?.data]);
const productData = product.data; useEffect(() => {
const productName = productData.title || productData.name || ''; if (!product?.data || !isVariablePricing(product.data) || !cartId) return;
const productImage = typeof productData.image === 'string' if (isUserInteractingRef.current) return;
? productData.image
: Array.isArray(productData.images) && productData.images.length > 0
? typeof productData.images[0] === 'string'
? productData.images[0]
: typeof productData.images[0] === 'object' && productData.images[0] !== null && 'url' in productData.images[0]
? (productData.images[0] as { url: string }).url
: '/assets/images/no-image.png'
: '/assets/images/no-image.png';
const categoryName = productData.category?.title;
const variants = productData.variants ?? [];
const hasVariants = Boolean(productData.attribute?.trim());
const selectedVariant = selectedVariantId
? variants.find((v) => v.id === selectedVariantId) ?? variants[0]
: variants[0];
const price = hasVariants
? (selectedVariant?.price ?? 0)
: (productData.price ?? selectedVariant?.price ?? 0);
const isVariable = isVariablePricing(productData);
const weightRange = isVariable ? getVariableQuantityRange(productData) : null;
const unitPriceLabel = isVariable
? getPurchaseUnitLabel(productData.purchaseUnit)
: '';
const calculatedPrice = isVariable ? Math.round(price * selectedWeight) : price;
// const prepareTime = productData.prepareTime || 0;
const content = Array.isArray(productData.content)
? productData.content.join('، ')
: productData.content || productData.desc || '';
const handleBackToMenu = () => { const { min } = getVariableQuantityRange(product.data);
const urlParams = new URLSearchParams(window.location.search); const targetWeight = quantity > 0 ? quantity : min;
const categoryParam = urlParams.get('category') || productData.category?.id; setSelectedWeight((prev) => (Math.abs(prev - targetWeight) < 0.0001 ? prev : targetWeight));
if (categoryParam) { }, [product?.data, cartId, quantity]);
router.push(`/${name}?category=${categoryParam}`);
} else {
router.push(`/${name}`);
}
};
useEffect(
() => () => {
clearCartSyncTimer();
},
[clearCartSyncTimer],
);
if (isLoading) {
return ( return (
<div className={`lg:absolute lg:top-1/2 not-lg:max-w-lg not-lg:place-self-center lg:-translate-y-1/2 xl:right-[285px] lg:left-5 lg:right-4 lg:bottom-4 not-lg:w-full not-md:-translate-y-2 pt-6 flex flex-col lg:grid grid-cols-2 bottom-10`}> <div className="flex items-center justify-center min-h-100">
<div className="relative w-full h-full not-lg:bg-container rounded-2xl overflow-hidden lg:rounded-r-none lg:order-1"> <p className="text-disabled-text">در حال بارگذاری...</p>
<Image </div>
className='w-full object-cover bg-[#F2F2F9] h-full' );
src={productImage} }
alt={productName}
height={100}
width={100}
unoptimized
priority
/>
<div className="absolute top-4 left-0 pe-5.5 ps-7 w-full inline-flex justify-between items-center">
<div className="bg-container whitespace-nowrap rounded-[34px] py-1.5 px-4 w-min text-xs text-disabled2 dark:text-disabled-text font-bold">
{categoryName || '-'}
</div>
<button onClick={handleBackToMenu} className='p-2 rounded-full bg-container/40'>
<ArrowLeft size={18} className='stroke-primary dark:stroke-foreground' />
</button>
</div>
</div>
<div className="relative px-6 pt-6 pb-7.5 lg:flex lg:flex-col lg:justify-center bg-container w-full rounded-3xl overflow-hidden not-lg:-translate-y-6 lg:rounded-l-none"> if (!product?.data) {
<div className="w-full inline-flex justify-between items-center"> return (
<h5 className="text-base font-bold"> <div className="flex items-center justify-center min-h-100">
{productName} <p className="text-disabled-text">کالا یافت نشد</p>
</h5> </div>
<button );
onClick={handleToggleFavorite} }
className='p-2 bg-[#EAECF0] dark:bg-neutral-600 rounded-lg'
>
<Heart
variant={isFavorite ? 'Bold' : 'Outline'}
size={24}
color='currentColor'
className={isFavorite ? 'fill-primary dark:fill-foreground' : ''}
/>
</button>
</div>
<div className="mt-4 text-xs"> const productData = product.data;
{/* <div className='flex items-center gap-1'> const productName = productData.title || productData.name || "";
const productImage =
typeof productData.image === "string"
? productData.image
: Array.isArray(productData.images) && productData.images.length > 0
? typeof productData.images[0] === "string"
? productData.images[0]
: typeof productData.images[0] === "object" && productData.images[0] !== null && "url" in productData.images[0]
? (productData.images[0] as { url: string }).url
: "/assets/images/no-image.png"
: "/assets/images/no-image.png";
const categoryName = productData.category?.title;
const variants = productData.variants ?? [];
const hasVariants = Boolean(productData.attribute?.trim());
const selectedVariant = selectedVariantId ? (variants.find((v) => v.id === selectedVariantId) ?? variants[0]) : variants[0];
const price = hasVariants ? (selectedVariant?.price ?? 0) : (productData.price ?? selectedVariant?.price ?? 0);
const isVariable = isVariablePricing(productData);
const weightRange = isVariable ? getVariableQuantityRange(productData) : null;
const unitPriceLabel = isVariable ? getPurchaseUnitLabel(productData.purchaseUnit) : "";
const calculatedPrice = isVariable ? Math.round(price * selectedWeight) : price;
// const prepareTime = productData.prepareTime || 0;
const content = Array.isArray(productData.content) ? productData.content.join("، ") : productData.content || productData.desc || "";
const handleBackToMenu = () => {
const urlParams = new URLSearchParams(window.location.search);
const categoryParam = urlParams.get("category") || productData.category?.id;
if (categoryParam) {
router.push(`/${name}?category=${categoryParam}`);
} else {
router.push(`/${name}`);
}
};
return (
<div
className={`lg:absolute lg:top-1/2 not-lg:max-w-lg not-lg:place-self-center lg:-translate-y-1/2 xl:right-71.25 lg:left-5 lg:right-4 lg:bottom-4 not-lg:w-full not-md:-translate-y-2 pt-6 flex flex-col lg:grid grid-cols-2 bottom-10`}
>
<div className="relative w-full h-full not-lg:bg-container rounded-2xl overflow-hidden lg:rounded-r-none lg:order-1">
<Image className="w-full object-cover bg-[#F2F2F9] h-full" src={productImage} alt={productName} height={100} width={100} unoptimized priority />
<div className="absolute top-4 left-0 pe-5.5 ps-7 w-full inline-flex justify-between items-center">
<div className="bg-container whitespace-nowrap rounded-[34px] py-1.5 px-4 w-min text-xs text-disabled2 dark:text-disabled-text font-bold">{categoryName || "-"}</div>
<button onClick={handleBackToMenu} className="p-2 rounded-full bg-container/40">
<ArrowLeft size={18} className="stroke-primary dark:stroke-foreground" />
</button>
</div>
</div>
<div className="relative px-6 pt-6 pb-7.5 lg:flex lg:flex-col lg:justify-center bg-container w-full rounded-3xl overflow-hidden not-lg:-translate-y-6 lg:rounded-l-none">
<div className="w-full inline-flex justify-between items-center">
<h5 className="text-base font-bold">{productName}</h5>
<button onClick={handleToggleFavorite} className="p-2 bg-[#EAECF0] dark:bg-neutral-600 rounded-lg">
<Heart variant={isFavorite ? "Bold" : "Outline"} size={24} color="currentColor" className={isFavorite ? "fill-primary dark:fill-foreground" : ""} />
</button>
</div>
<div className="mt-4 text-xs">
{/* <div className='flex items-center gap-1'>
<Clock size={14} className='stroke-disabled-text' /> <Clock size={14} className='stroke-disabled-text' />
<span className='text-disabled-text'> <span className='text-disabled-text'>
زمان پخت و آماده سازی: {prepareTime > 0 ? `${ef(String(prepareTime))} دقیقه` : '-'} زمان پخت و آماده سازی: {prepareTime > 0 ? `${ef(String(prepareTime))} دقیقه` : '-'}
</span> </span>
</div> */} </div> */}
<div className='flex items-center gap-2 mt-2'> <div className="flex items-center gap-2 mt-2">
<Cup size={14} className='stroke-disabled-text' /> <Cup size={14} className="stroke-disabled-text" />
<span className='text-disabled-text flex gap-1'> <span className="text-disabled-text flex gap-1">
{about?.data?.plan === 'base' ? ( {about?.data?.plan === "base" ? (
<> <>
<div>۰</div> <div>۰</div>
<span>امتیاز برای هر بار خرید</span> <span>امتیاز برای هر بار خرید</span>
</> </>
) : about?.data?.score?.purchaseScore && about?.data?.score?.purchaseAmount ? ( ) : about?.data?.score?.purchaseScore && about?.data?.score?.purchaseAmount ? (
<> <>
<div> <div>{ef(Math.round(price * (Number(about.data.score.purchaseScore) / Number(about.data.score.purchaseAmount))).toLocaleString("en-US"))}</div>
{ef( <span>امتیاز برای هر بار خرید</span>
Math.round( </>
price * ) : (
(Number(about.data.score.purchaseScore) / Number(about.data.score.purchaseAmount)) <span>-</span>
).toLocaleString('en-US') )}
)} </span>
</div> </div>
<span>امتیاز برای هر بار خرید</span> <div className="flex items-center gap-2 mt-2">
</> <TruckTick size={14} className="stroke-disabled-text" />
) : ( <span className="text-disabled-text">{productData.pickupServe ? "ارسال با پیک" : "-"}</span>
<span>-</span> </div>
)}
</span>
</div>
<div className='flex items-center gap-2 mt-2'>
<TruckTick size={14} className='stroke-disabled-text' />
<span className='text-disabled-text'>
{productData.pickupServe ? 'ارسال با پیک' : '-'}
</span>
</div>
</div>
<div className="mt-7 text-xs">
<p className='font-bold'>محتویات:</p>
<p className='mt-2 leading-6'>{content || '-'}</p>
</div>
{hasVariants && variants.length > 0 && (
<div className='mt-6 text-xs'>
<p className='font-bold'>{productData.attribute}</p>
<div className='flex flex-wrap gap-2 mt-2'>
{variants.map((variant) => (
<button
key={variant.id}
type="button"
onClick={() => setSelectedVariantId(variant.id)}
className={`px-3 py-1 rounded-full border text-xs ${variant.id === selectedVariant?.id
? 'bg-primary text-white border-primary'
: 'bg-background text-foreground border-border'
}`}
>
{variant.value}
</button>
))}
</div>
</div>
)}
{isVariable && weightRange ? (
<div className='mt-8'>
<p className='text-sm font-bold'>
قیمت هر {unitPriceLabel}{' '}
<span dir='ltr'>T {ef(price.toLocaleString('en-US'))}</span>
</p>
<VariableWeightSlider
className='mt-10 mb-8'
value={selectedWeight}
min={weightRange.min}
max={weightRange.max}
step={weightRange.pitch}
purchaseUnit={productData.purchaseUnit}
onChange={handleVariableWeightChange}
onCommit={handleVariableWeightCommit}
/>
<div className='flex items-center gap-3'>
<div
dir='ltr'
className='flex-1 rounded-xl bg-[#EAECF0] dark:bg-neutral-700 px-4 py-3.5 text-sm font-semibold text-center'
>
{ef(calculatedPrice.toLocaleString('en-US'))} T
</div>
<button
type='button'
onClick={handleAddVariableToCart}
disabled={isCartMutating || !cartId}
className='flex-1 inline-flex items-center justify-center gap-2 rounded-xl bg-black text-white px-4 py-3.5 text-sm font-semibold disabled:opacity-60 disabled:cursor-not-allowed'
>
{isCartMutating ? (
<span className="size-4 border-2 border-white/30 border-t-white rounded-full animate-spin" />
) : (
<PlusIcon className='text-white' />
)}
<span>{quantity > 0 ? 'بروزرسانی' : 'افزودن'}</span>
</button>
</div>
</div>
) : (
<div className='mt-12 flex justify-between items-center'>
<span dir='ltr'>{ef(price.toLocaleString('en-US'))} T</span>
<CartQuantityControl
quantity={quantity}
onAdd={handleAddToCart}
onRemove={handleRemoveFromCart}
isMutating={isCartMutating}
addDisabled={!cartId}
/>
</div>
)}
</div>
</div> </div>
)
<div className="mt-7 text-xs">
<p className="font-bold">محتویات:</p>
<p className="mt-2 leading-6">{content || "-"}</p>
</div>
{hasVariants && variants.length > 0 && (
<div className="mt-6 text-xs">
<p className="font-bold">{productData.attribute}</p>
<div className="flex flex-wrap gap-2 mt-2">
{variants.map((variant) => (
<button
key={variant.id}
type="button"
onClick={() => setSelectedVariantId(variant.id)}
className={`px-3 py-1 rounded-full border text-xs ${variant.id === selectedVariant?.id ? "bg-primary text-white border-primary" : "bg-background text-foreground border-border"}`}
>
{variant.value}
</button>
))}
</div>
</div>
)}
{isVariable && weightRange ? (
<div className="mt-8">
<p className="text-sm font-bold">
قیمت هر {unitPriceLabel} <span dir="ltr">T {ef(price.toLocaleString("en-US"))}</span>
</p>
<VariableWeightSlider
className="mt-10 mb-8"
value={selectedWeight}
min={weightRange.min}
max={weightRange.max}
step={weightRange.pitch}
purchaseUnit={productData.purchaseUnit}
onChange={handleVariableWeightChange}
onCommit={handleVariableWeightCommit}
/>
<div className="flex items-center gap-3">
<div dir="ltr" className="flex-1 rounded-xl bg-[#EAECF0] dark:bg-neutral-700 px-4 py-3.5 text-sm font-semibold text-center">
{ef(calculatedPrice.toLocaleString("en-US"))} T
</div>
<button
type="button"
onClick={handleAddVariableToCart}
disabled={isCartMutating || !cartId}
className="flex-1 inline-flex items-center justify-center gap-2 rounded-xl bg-black text-white px-4 py-3.5 text-sm font-semibold disabled:opacity-60 disabled:cursor-not-allowed"
>
{isCartMutating ? <span className="size-4 border-2 border-white/30 border-t-white rounded-full animate-spin" /> : <PlusIcon className="text-white" />}
<span>{quantity > 0 ? "بروزرسانی" : "افزودن"}</span>
</button>
</div>
</div>
) : (
<div className="mt-12 flex justify-between items-center">
<span dir="ltr">{ef(price.toLocaleString("en-US"))} T</span>
<CartQuantityControl quantity={quantity} onAdd={handleAddToCart} onRemove={handleRemoveFromCart} isMutating={isCartMutating} addDisabled={!cartId} />
</div>
)}
</div>
</div>
);
} }
export default ProductPage export default ProductPage;
+4 -6
View File
@@ -106,9 +106,7 @@ const MenuItem = ({ product, variantId: variantIdProp, variantValue: variantValu
const isSpecialOffer = product.isSpecialOffer === true; const isSpecialOffer = product.isSpecialOffer === true;
const specialOfferBadge = isSpecialOffer ? ( const specialOfferBadge = isSpecialOffer ? (
<span className="inline-block rounded-md bg-primary text-primary-foreground text-[10px] font-medium px-1.5 py-0.5 leading-tight"> <span className="inline-block rounded-md bg-primary text-primary-foreground text-[10px] font-medium px-1.5 py-0.5 leading-tight">پیشنهاد ویژه</span>
پیشنهاد ویژه
</span>
) : null; ) : null;
const quantityLabel = useMemo(() => { const quantityLabel = useMemo(() => {
@@ -135,7 +133,7 @@ const MenuItem = ({ product, variantId: variantIdProp, variantValue: variantValu
return ( return (
<div className="relative flex flex-col w-full h-full"> <div className="relative flex flex-col w-full h-full">
{specialOfferBadge && <div className="absolute top-0 left-0 z-10">{specialOfferBadge}</div>} {specialOfferBadge && <div className="absolute top-0 left-0 z-10">{specialOfferBadge}</div>}
<Link href={productDetailUrl} className="cursor-pointer rounded-xl aspect-square overflow-hidden flex items-center justify-center max-h-[100px]"> <Link href={productDetailUrl} className="cursor-pointer rounded-xl aspect-square overflow-hidden flex items-center justify-center max-h-25">
<img className="rounded-xl max-w-full max-h-full object-center" src={resolvedImage} alt={productName} /> <img className="rounded-xl max-w-full max-h-full object-center" src={resolvedImage} alt={productName} />
</Link> </Link>
<Link href={productDetailUrl} className="cursor-pointer min-w-0 mt-4 flex items-center justify-between gap-2"> <Link href={productDetailUrl} className="cursor-pointer min-w-0 mt-4 flex items-center justify-between gap-2">
@@ -211,14 +209,14 @@ const MenuItem = ({ product, variantId: variantIdProp, variantValue: variantValu
) : !readOnly ? ( ) : !readOnly ? (
<div className="flex flex-col justify-end shrink-0"> <div className="flex flex-col justify-end shrink-0">
{showDetailsInsteadOfAdd ? ( {showDetailsInsteadOfAdd ? (
<div className="w-[115px]"> <div className="w-28.75">
<Link href={productDetailUrl} className={`${actionButtonClass} w-full`}> <Link href={productDetailUrl} className={`${actionButtonClass} w-full`}>
<span className={actionButtonLabelClass}>جزئیات</span> <span className={actionButtonLabelClass}>جزئیات</span>
<ArrowLeft size={16} className="stroke-foreground" /> <ArrowLeft size={16} className="stroke-foreground" />
</Link> </Link>
</div> </div>
) : ( ) : (
<div className="w-[115px]"> <div className="w-28.75">
<CartQuantityControl quantity={quantity} onAdd={handleAddToCart} onRemove={handleRemoveFromCart} isMutating={isCartMutating} /> <CartQuantityControl quantity={quantity} onAdd={handleAddToCart} onRemove={handleRemoveFromCart} isMutating={isCartMutating} />
</div> </div>
)} )}