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
+94 -146
View File
@@ -1,30 +1,24 @@
'use client'
"use client";
import { CartQuantityControl } from '@/components/CartQuantityControl'
import { VariableWeightSlider } from '@/components/product/VariableWeightSlider'
import PlusIcon from '@/components/icons/PlusIcon'
import { ef } from '@/lib/helpers/utfNumbers'
import { useCart } from '@/app/[name]/(Dialogs)/cart/hook/useCart'
import {
getPrimaryVariantId,
getPurchaseUnitLabel,
getVariableQuantityRange,
isVariablePricing,
} from '@/app/[name]/(Main)/types/Types'
import { ArrowLeft, Cup, Heart, TruckTick } from 'iconsax-react'
import Image from 'next/image'
import { useParams, useRouter } from 'next/navigation'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
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'
import { useCart } from "@/app/[name]/(Dialogs)/cart/hook/useCart";
import { getPrimaryVariantId, getPurchaseUnitLabel, getVariableQuantityRange, isVariablePricing } from "@/app/[name]/(Main)/types/Types";
import { CartQuantityControl } from "@/components/CartQuantityControl";
import PlusIcon from "@/components/icons/PlusIcon";
import { VariableWeightSlider } from "@/components/product/VariableWeightSlider";
import { toast } from "@/components/Toast";
import { getToken } from "@/lib/api/func";
import { ef } from "@/lib/helpers/utfNumbers";
import { ArrowLeft, Cup, Heart, TruckTick } from "iconsax-react";
import Image from "next/image";
import { useParams, useRouter } from "next/navigation";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useGetProfile } from "../../(Profile)/profile/hooks/userProfileData";
import { useGetAbout } from "../about/hooks/useAboutData";
import { useGetProduct, useToggleFavorite } from "./hooks/useProductData";
type Props = object
type Props = object;
function ProductPage({}: Props) {
const { id, name } = useParams();
const router = useRouter();
const { isSuccess } = useGetProfile();
@@ -39,7 +33,7 @@ function ProductPage({ }: Props) {
const pendingCartWeightRef = useRef<number | null>(null);
const { mutate: toggleFavorite } = useToggleFavorite();
const productId = product?.data?.id || id as string;
const productId = product?.data?.id || (id as string);
const cartId = useMemo(() => {
if (!product?.data) return null;
return selectedVariantId ?? getPrimaryVariantId(product.data) ?? productId;
@@ -47,7 +41,7 @@ function ProductPage({ }: Props) {
const quantity = useMemo(() => {
if (!cartId) return 0;
const item = items?.[cartId] ?? items?.[String(cartId)];
if (item && typeof item === 'object' && 'quantity' in item) {
if (item && typeof item === "object" && "quantity" in item) {
return item.quantity || 0;
}
return 0;
@@ -77,14 +71,18 @@ function ProductPage({ }: Props) {
}
}, []);
const handleVariableWeightChange = useCallback((weight: number) => {
const handleVariableWeightChange = useCallback(
(weight: number) => {
isUserInteractingRef.current = true;
setSelectedWeight(weight);
clearCartSyncTimer();
pendingCartWeightRef.current = null;
}, [clearCartSyncTimer]);
},
[clearCartSyncTimer],
);
const handleVariableWeightCommit = useCallback((weight: number) => {
const handleVariableWeightCommit = useCallback(
(weight: number) => {
if (!isSuccess || !cartId || quantity <= 0) {
isUserInteractingRef.current = false;
return;
@@ -104,16 +102,16 @@ function ProductPage({ }: Props) {
}
const currentItem = items?.[cartId] ?? items?.[String(cartId)];
const currentQty = currentItem && typeof currentItem === 'object' && 'quantity' in currentItem
? currentItem.quantity
: 0;
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]);
},
[isSuccess, cartId, quantity, clearCartSyncTimer, items, setCartQuantity],
);
const handleRemoveFromCart = () => {
if (cartId) {
@@ -126,7 +124,7 @@ function ProductPage({ }: Props) {
const token = await getToken();
if (!token || !isSuccess) {
toast('ابتدا لاگین کنید', 'error');
toast("ابتدا لاگین کنید", "error");
return;
}
@@ -141,7 +139,7 @@ function ProductPage({ }: Props) {
onError: () => {
// بازگرداندن به حالت قبلی در صورت خطا
setIsFavorite(previousFavorite);
toast('خطا در تغییر وضعیت علاقه‌مندی', 'error');
toast("خطا در تغییر وضعیت علاقه‌مندی", "error");
},
});
};
@@ -168,65 +166,59 @@ function ProductPage({ }: Props) {
const { min } = getVariableQuantityRange(product.data);
const targetWeight = quantity > 0 ? quantity : min;
setSelectedWeight((prev) => (
Math.abs(prev - targetWeight) < 0.0001 ? prev : targetWeight
));
setSelectedWeight((prev) => (Math.abs(prev - targetWeight) < 0.0001 ? prev : targetWeight));
}, [product?.data, cartId, quantity]);
useEffect(() => () => {
useEffect(
() => () => {
clearCartSyncTimer();
}, [clearCartSyncTimer]);
},
[clearCartSyncTimer],
);
if (isLoading) {
return (
<div className='flex items-center justify-center min-h-[400px]'>
<p className='text-disabled-text'>در حال بارگذاری...</p>
<div className="flex items-center justify-center min-h-100">
<p className="text-disabled-text">در حال بارگذاری...</p>
</div>
);
}
if (!product?.data) {
return (
<div className='flex items-center justify-center min-h-[400px]'>
<p className='text-disabled-text'>کالا یافت نشد</p>
<div className="flex items-center justify-center min-h-100">
<p className="text-disabled-text">کالا یافت نشد</p>
</div>
);
}
const productData = product.data;
const productName = productData.title || productData.name || '';
const productImage = typeof productData.image === 'string'
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'
? typeof productData.images[0] === "string"
? productData.images[0]
: typeof productData.images[0] === 'object' && productData.images[0] !== null && 'url' in 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';
: "/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 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 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 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;
const categoryParam = urlParams.get("category") || productData.category?.id;
if (categoryParam) {
router.push(`/${name}?category=${categoryParam}`);
} else {
@@ -235,42 +227,24 @@ function ProductPage({ }: Props) {
};
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={`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
/>
<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' />
<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' : ''}
/>
<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>
@@ -281,24 +255,17 @@ function ProductPage({ }: Props) {
زمان پخت و آماده سازی: {prepareTime > 0 ? `${ef(String(prepareTime))} دقیقه` : '-'}
</span>
</div> */}
<div className='flex items-center gap-2 mt-2'>
<Cup size={14} className='stroke-disabled-text' />
<span className='text-disabled-text flex gap-1'>
{about?.data?.plan === 'base' ? (
<div className="flex items-center gap-2 mt-2">
<Cup size={14} className="stroke-disabled-text" />
<span className="text-disabled-text flex gap-1">
{about?.data?.plan === "base" ? (
<>
<div>۰</div>
<span>امتیاز برای هر بار خرید</span>
</>
) : about?.data?.score?.purchaseScore && about?.data?.score?.purchaseAmount ? (
<>
<div>
{ef(
Math.round(
price *
(Number(about.data.score.purchaseScore) / Number(about.data.score.purchaseAmount))
).toLocaleString('en-US')
)}
</div>
<div>{ef(Math.round(price * (Number(about.data.score.purchaseScore) / Number(about.data.score.purchaseAmount))).toLocaleString("en-US"))}</div>
<span>امتیاز برای هر بار خرید</span>
</>
) : (
@@ -306,32 +273,27 @@ function ProductPage({ }: Props) {
)}
</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 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>
<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'>
<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'
}`}
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>
@@ -341,14 +303,13 @@ function ProductPage({ }: Props) {
)}
{isVariable && weightRange ? (
<div className='mt-8'>
<p className='text-sm font-bold'>
قیمت هر {unitPriceLabel}{' '}
<span dir='ltr'>T {ef(price.toLocaleString('en-US'))}</span>
<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'
className="mt-10 mb-8"
value={selectedWeight}
min={weightRange.min}
max={weightRange.max}
@@ -358,43 +319,30 @@ function ProductPage({ }: Props) {
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 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'
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'
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>
{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 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 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>
<span className="inline-block rounded-md bg-primary text-primary-foreground text-[10px] font-medium px-1.5 py-0.5 leading-tight">پیشنهاد ویژه</span>
) : null;
const quantityLabel = useMemo(() => {
@@ -135,7 +133,7 @@ const MenuItem = ({ product, variantId: variantIdProp, variantValue: variantValu
return (
<div className="relative flex flex-col w-full h-full">
{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} />
</Link>
<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 ? (
<div className="flex flex-col justify-end shrink-0">
{showDetailsInsteadOfAdd ? (
<div className="w-[115px]">
<div className="w-28.75">
<Link href={productDetailUrl} className={`${actionButtonClass} w-full`}>
<span className={actionButtonLabelClass}>جزئیات</span>
<ArrowLeft size={16} className="stroke-foreground" />
</Link>
</div>
) : (
<div className="w-[115px]">
<div className="w-28.75">
<CartQuantityControl quantity={quantity} onAdd={handleAddToCart} onRemove={handleRemoveFromCart} isMutating={isCartMutating} />
</div>
)}