Compare commits

..

2 Commits

Author SHA1 Message Date
hamid zarghami 194533ec5f fast navigate + cache
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-06-03 15:08:09 +03:30
hamid zarghami b9608cd486 change food item type 2026-06-03 14:59:47 +03:30
32 changed files with 532 additions and 504 deletions
@@ -1,134 +0,0 @@
'use client';
import React from 'react';
import Link from 'next/link';
import { useParams, useRouter, usePathname } from 'next/navigation';
import Button from '@/components/button/PrimaryButton';
import { useGetFoods } from '@/app/[name]/(Main)/hooks/useMenuData';
import type { Food } from '@/app/[name]/(Main)/types/Types';
import { useCart } from '../hook/useCart';
import { useTranslation } from 'react-i18next';
import { useGetProfile } from '@/app/[name]/(Profile)/profile/hooks/userProfileData';
import { useGetCartItems } from '../hooks/useCartData';
import { useCartStore } from '../store/Store';
interface CartSummaryProps {
isPremium: boolean;
}
const CartSummary = ({ isPremium }: CartSummaryProps) => {
const params = useParams();
const router = useRouter();
const pathname = usePathname();
const name = params.name as string;
const { isSuccess } = useGetProfile();
const { data: foodsResponse } = useGetFoods();
const foods = React.useMemo(() => foodsResponse?.data ?? [], [foodsResponse?.data]);
const { items } = useCart();
const { data: cartData } = useGetCartItems(isSuccess);
const { t } = useTranslation('parallels', { keyPrefix: 'Cart' });
const { description, setDescription } = useCartStore();
const cartFoods = React.useMemo(() => {
if (!foods.length) return [];
return Object.entries(items)
.filter(([, detail]) => detail?.quantity && detail.quantity > 0)
.map(([id]) =>
foods.find((foodItem) => String(foodItem.id) === String(id))
)
.filter((food): food is Food => Boolean(food));
}, [foods, items]);
const totalPrice = React.useMemo(() => {
// اگر داده از API آمده و total وجود دارد، از آن استفاده می‌کنیم
if (isSuccess && cartData?.data?.total !== undefined) {
return cartData.data.total;
}
// در غیر این صورت، محاسبه محلی با در نظر گرفتن تخفیف
return cartFoods.reduce((sum, food) => {
const qty = items[String(food.id)]?.quantity ?? 0;
const basePrice = food.price ?? 0;
const discount = food.discount ?? 0;
const priceAfterDiscount = basePrice * (1 - discount / 100);
return sum + priceAfterDiscount * qty;
}, 0);
}, [cartFoods, items, isSuccess, cartData?.data?.total]);
const isCartEmpty = cartFoods.length === 0;
const formatPrice = React.useCallback(
(value: number) => value.toLocaleString('fa-IR'),
[]
);
return (
<>
{!isCartEmpty && (
<>
{isPremium && (
<div className='mt-2 mb-4'>
<div className='relative'>
<h3 className='text-sm'>{t('InputDescription.Label')}</h3>
<textarea
className='w-full px-4 py-2.5 mt-4 text-sm2 leading-6 outline-0 border border-border rounded-normal resize-none focus:ring-0'
placeholder={t('InputDescription.Placeholder')}
id='description'
name='description'
value={description}
onChange={(e) => setDescription(e.target.value)}
></textarea>
<span className='absolute top-4 right-2 px-2 bg-background text-foreground text-xs'></span>
</div>
</div>
)}
<div className='fixed bottom-0 left-0 right-0 z-50 bg-white dark:bg-container border-t border-border p-4 w-full'>
<div className='flex justify-between items-center gap-4'>
<div className='flex flex-col'>
<div className='text-xs text-gray-400 dark:text-disabled-text'>{t('PayableAmountLabel')}</div>
{
isPremium &&
<div className='text-sm mt-2 font-semibold dark:text-foreground'>{formatPrice(totalPrice)} تومان</div>
}
</div>
{isPremium && (
<Link
href={'order/checkout/0'}
onClick={(event) => {
if (!isSuccess) {
event.preventDefault();
const redirectUrl = encodeURIComponent(pathname);
router.replace(`/${name}/auth?redirect=${redirectUrl}`);
return;
}
}}
>
<Button className='w-fit! px-10 dark:bg-white dark:text-black! dark:hover:bg-gray-100'>{t('ButtonCheckout')}</Button>
</Link>
)}
{!isPremium && (
<div className='text-sm mt-2 font-semibold dark:text-foreground'>{formatPrice(totalPrice)} تومان</div>
)}
</div>
</div>
</>
)}
{/* {!isCartEmpty && (
<div className='flex items-center justify-between rounded-xl border border-border px-4 py-3 text-xs text-muted-foreground'>
<span>{t('CartControlsHint', { defaultValue: 'برای ویرایش تعداد، از دکمه‌های هر آیتم استفاده کنید.' })}</span>
<div className='flex items-center gap-1 text-foreground'>
<Add size={16} />
<Minus size={16} />
</div>
</div>
)} */}
</>
);
};
export default CartSummary;
-17
View File
@@ -1,17 +0,0 @@
'use client';
import dynamic from 'next/dynamic';
const CartPage = dynamic(() => import('./CartPage'), {
ssr: false,
loading: () => (
<div className='flex flex-col items-center justify-center gap-2 py-12 text-sm2 text-muted-foreground'>
<span className='h-4 w-4 animate-spin rounded-full border border-dashed border-foreground border-t-transparent'></span>
<p>در حال دریافت سبد خرید...</p>
</div>
),
});
export default function Page() {
return <CartPage />;
}
@@ -9,7 +9,7 @@ import Button from '@/components/button/PrimaryButton';
import { extractErrorMessage } from '@/lib/func'; import { extractErrorMessage } from '@/lib/func';
import { toast } from '@/components/Toast'; import { toast } from '@/components/Toast';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useGetCartItems } from '@/app/[name]/(Dialogs)/cart/hooks/useCartData'; import { useGetCartItems } from '@/app/[name]/(Main)/cart/hooks/useCartData';
type CouponSectionProps = { type CouponSectionProps = {
couponType: string; couponType: string;
@@ -7,7 +7,7 @@ import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import { useGetPaymentMethod } from '../../hooks/useOrderData'; import { useGetPaymentMethod } from '../../hooks/useOrderData';
import { useCheckoutStore } from '../../store/Store'; import { useCheckoutStore } from '../../store/Store';
import { useGetUserWallet } from '@/app/[name]/(Main)/transactions/hooks/useTransactionData'; import { useGetUserWallet } from '@/app/[name]/(Main)/transactions/hooks/useTransactionData';
import { useGetCartItems } from '@/app/[name]/(Dialogs)/cart/hooks/useCartData'; import { useGetCartItems } from '@/app/[name]/(Main)/cart/hooks/useCartData';
import { useGetProfile } from '@/app/[name]/(Profile)/profile/hooks/userProfileData'; import { useGetProfile } from '@/app/[name]/(Profile)/profile/hooks/userProfileData';
type PaymentSectionProps = { type PaymentSectionProps = {
@@ -9,12 +9,12 @@ import { toast } from '@/components/Toast';
import { useCreateOrder } from '../../hooks/useOrderData'; import { useCreateOrder } from '../../hooks/useOrderData';
import { extractErrorMessage } from '@/lib/func'; import { extractErrorMessage } from '@/lib/func';
import { useParams } from 'next/navigation'; import { useParams } from 'next/navigation';
import { useGetCartItems, useSaveAllMethod } from '@/app/[name]/(Dialogs)/cart/hooks/useCartData'; import { useGetCartItems, useSaveAllMethod } from '@/app/[name]/(Main)/cart/hooks/useCartData';
import { useGetProfile } from '@/app/[name]/(Profile)/profile/hooks/userProfileData'; import { useGetProfile } from '@/app/[name]/(Profile)/profile/hooks/userProfileData';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useGetShipmentMethod } from '../../hooks/useOrderData'; import { useGetShipmentMethod } from '../../hooks/useOrderData';
import CartItemsList from '@/app/[name]/(Dialogs)/cart/components/CartItemsList'; import CartItemsList from '@/app/[name]/(Main)/cart/components/CartItemsList';
import { useCartStore } from '@/app/[name]/(Dialogs)/cart/store/Store'; import { useCartStore } from '@/app/[name]/(Main)/cart/store/Store';
type SummarySectionProps = { type SummarySectionProps = {
cartModal: boolean; cartModal: boolean;
@@ -11,7 +11,7 @@ import { ShippingSection } from './components/ShippingSection';
import { SummarySection } from './components/SummarySection'; import { SummarySection } from './components/SummarySection';
import { TableSection } from './components/TableSection'; import { TableSection } from './components/TableSection';
import { useGetShipmentMethod } from '../hooks/useOrderData'; import { useGetShipmentMethod } from '../hooks/useOrderData';
import { useGetCartItems } from '@/app/[name]/(Dialogs)/cart/hooks/useCartData'; import { useGetCartItems } from '@/app/[name]/(Main)/cart/hooks/useCartData';
import { useGetProfile } from '@/app/[name]/(Profile)/profile/hooks/userProfileData'; import { useGetProfile } from '@/app/[name]/(Profile)/profile/hooks/userProfileData';
import { useCheckoutStore } from '../store/Store'; import { useCheckoutStore } from '../store/Store';
+77 -68
View File
@@ -1,24 +1,23 @@
'use client' "use client";
import MinusIcon from '@/components/icons/MinusIcon' import { useCart } from "@/app/[name]/(Main)/cart/hook/useCart";
import PlusIcon from '@/components/icons/PlusIcon' import MinusIcon from "@/components/icons/MinusIcon";
import { ef } from '@/lib/helpers/utfNumbers' import PlusIcon from "@/components/icons/PlusIcon";
import { useCart } from '@/app/[name]/(Dialogs)/cart/hook/useCart' import { toast } from "@/components/Toast";
import { motion } from 'framer-motion' import { getToken } from "@/lib/api/func";
import { ArrowLeft, Clock, Cup, Heart, TruckTick } from 'iconsax-react' import { ef } from "@/lib/helpers/utfNumbers";
import Image from 'next/image' import { motion } from "framer-motion";
import { useParams, useRouter } from 'next/navigation' import { ArrowLeft, Clock, Cup, Heart, TruckTick } from "iconsax-react";
import React, { useEffect, useMemo, useState } from 'react' import Image from "next/image";
import { useGetFood, useToggleFavorite } from './hooks/useFoodData' import { useParams, useRouter } from "next/navigation";
import { useGetAbout } from '../about/hooks/useAboutData' import { useEffect, useMemo, useState } from "react";
import { useGetProfile } from '../../(Profile)/profile/hooks/userProfileData' import { useGetProfile } from "../../(Profile)/profile/hooks/userProfileData";
import { toast } from '@/components/Toast' import { useGetAbout } from "../about/hooks/useAboutData";
import { getToken } from '@/lib/api/func' import { useGetFood, useToggleFavorite } from "./hooks/useFoodData";
type Props = object type Props = object;
function FoodPage({ }: Props) {
function FoodPage({}: Props) {
const { id, name } = useParams(); const { id, name } = useParams();
const router = useRouter(); const router = useRouter();
const { isSuccess } = useGetProfile(); const { isSuccess } = useGetProfile();
@@ -28,10 +27,10 @@ function FoodPage({ }: Props) {
const [isFavorite, setIsFavorite] = useState<boolean>(false); const [isFavorite, setIsFavorite] = useState<boolean>(false);
const { mutate: toggleFavorite } = useToggleFavorite(); const { mutate: toggleFavorite } = useToggleFavorite();
const foodId = food?.data?.id || id as string; const foodId = food?.data?.id || (id as string);
const quantity = useMemo(() => { const quantity = useMemo(() => {
const item = items?.[foodId]; const item = items?.[foodId];
if (item && typeof item === 'object' && 'quantity' in item) { if (item && typeof item === "object" && "quantity" in item) {
return item.quantity || 0; return item.quantity || 0;
} }
return 0; return 0;
@@ -54,7 +53,7 @@ function FoodPage({ }: Props) {
const token = await getToken(); const token = await getToken();
if (!token || !isSuccess) { if (!token || !isSuccess) {
toast('ابتدا لاگین کنید', 'error'); toast("ابتدا لاگین کنید", "error");
return; return;
} }
@@ -69,7 +68,7 @@ function FoodPage({ }: Props) {
onError: () => { onError: () => {
// بازگرداندن به حالت قبلی در صورت خطا // بازگرداندن به حالت قبلی در صورت خطا
setIsFavorite(previousFavorite); setIsFavorite(previousFavorite);
toast('خطا در تغییر وضعیت علاقه‌مندی', 'error'); toast("خطا در تغییر وضعیت علاقه‌مندی", "error");
}, },
}); });
}; };
@@ -84,41 +83,44 @@ function FoodPage({ }: Props) {
if (isLoading) { if (isLoading) {
return ( return (
<div className='flex items-center justify-center min-h-[400px]'> <div className="flex items-center justify-center min-h-[400px]">
<p className='text-disabled-text'>در حال بارگذاری...</p> <p className="text-disabled-text">در حال بارگذاری...</p>
</div> </div>
); );
} }
if (!food?.data) { if (!food?.data) {
return ( return (
<div className='flex items-center justify-center min-h-[400px]'> <div className="flex items-center justify-center min-h-[400px]">
<p className='text-disabled-text'>غذا یافت نشد</p> <p className="text-disabled-text">غذا یافت نشد</p>
</div> </div>
); );
} }
const foodData = food.data; const foodData = food.data;
const foodName = foodData.title || foodData.name || foodData.foodName || ''; const foodName = foodData.title || foodData.name || foodData.foodName || "";
const foodImage = typeof foodData.image === 'string' const foodImage =
typeof foodData.image === "string"
? foodData.image ? foodData.image
: Array.isArray(foodData.images) && foodData.images.length > 0 : Array.isArray(foodData.images) && foodData.images.length > 0
? typeof foodData.images[0] === 'string' ? typeof foodData.images[0] === "string"
? foodData.images[0] ? foodData.images[0]
: typeof foodData.images[0] === 'object' && foodData.images[0] !== null && 'url' in foodData.images[0] : typeof foodData.images[0] === "object" &&
foodData.images[0] !== null &&
"url" in foodData.images[0]
? (foodData.images[0] as { url: string }).url ? (foodData.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 = foodData.category?.title; const categoryName = foodData.category?.title;
const prepareTime = foodData.prepareTime || 0; const prepareTime = foodData.prepareTime || 0;
const price = foodData.price || 0; const price = foodData.price || 0;
const content = Array.isArray(foodData.content) const content = Array.isArray(foodData.content)
? foodData.content.join('، ') ? foodData.content.join("، ")
: foodData.content || foodData.desc || ''; : foodData.content || foodData.desc || "";
const handleBackToMenu = () => { const handleBackToMenu = () => {
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
const categoryParam = urlParams.get('category') || foodData.category?.id; const categoryParam = urlParams.get("category") || foodData.category?.id;
if (categoryParam) { if (categoryParam) {
router.push(`/${name}?category=${categoryParam}`); router.push(`/${name}?category=${categoryParam}`);
} else { } else {
@@ -127,10 +129,10 @@ function FoodPage({ }: Props) {
}; };
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="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="relative w-full h-full not-lg:bg-container rounded-2xl overflow-hidden lg:rounded-r-none lg:order-1"> <div className="relative w-full h-full not-lg:bg-container rounded-2xl overflow-hidden lg:rounded-r-none lg:order-1">
<Image <Image
className='w-full object-cover bg-[#F2F2F9] h-full' className="w-full object-cover bg-[#F2F2F9] h-full"
src={foodImage} src={foodImage}
alt={foodName} alt={foodName}
height={100} height={100}
@@ -140,55 +142,62 @@ function FoodPage({ }: Props) {
/> />
<div className="absolute top-4 left-0 pe-5.5 ps-7 w-full inline-flex justify-between items-center"> <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"> <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 || '-'} {categoryName || "-"}
</div> </div>
<button onClick={handleBackToMenu} className='p-2 rounded-full bg-container/40'> <button
<ArrowLeft size={18} className='stroke-primary dark:stroke-foreground' /> onClick={handleBackToMenu}
className="p-2 rounded-full bg-container/40"
>
<ArrowLeft
size={18}
className="stroke-primary dark:stroke-foreground"
/>
</button> </button>
</div> </div>
</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="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"> <div className="w-full inline-flex justify-between items-center">
<h5 className="text-base font-bold"> <h5 className="text-base font-bold">{foodName}</h5>
{foodName}
</h5>
<button <button
onClick={handleToggleFavorite} onClick={handleToggleFavorite}
className='p-2 bg-[#EAECF0] dark:bg-neutral-600 rounded-lg' className="p-2 bg-[#EAECF0] dark:bg-neutral-600 rounded-lg"
> >
<Heart <Heart
variant={isFavorite ? 'Bold' : 'Outline'} variant={isFavorite ? "Bold" : "Outline"}
size={24} size={24}
color='currentColor' color="currentColor"
className={isFavorite ? 'fill-primary dark:fill-foreground' : ''} className={isFavorite ? "fill-primary dark:fill-foreground" : ""}
/> />
</button> </button>
</div> </div>
<div className="mt-4 text-xs"> <div className="mt-4 text-xs">
<div className='flex items-center gap-1'> <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( {ef(
Math.round( Math.round(
foodData?.price * foodData?.price *
(Number(about.data.score.purchaseScore) / Number(about.data.score.purchaseAmount)) (Number(about.data.score.purchaseScore) /
).toLocaleString('en-US') Number(about.data.score.purchaseAmount)),
).toLocaleString("en-US"),
)} )}
</div> </div>
<span>امتیاز برای هر بار خرید</span> <span>امتیاز برای هر بار خرید</span>
@@ -198,21 +207,21 @@ function FoodPage({ }: Props) {
)} )}
</span> </span>
</div> </div>
<div className='flex items-center gap-2 mt-2'> <div className="flex items-center gap-2 mt-2">
<TruckTick size={14} className='stroke-disabled-text' /> <TruckTick size={14} className="stroke-disabled-text" />
<span className='text-disabled-text'> <span className="text-disabled-text">
{foodData.pickupServe ? 'ارسال با پیک' : '-'} {foodData.pickupServe ? "ارسال با پیک" : "-"}
</span> </span>
</div> </div>
</div> </div>
<div className="mt-7 text-xs"> <div className="mt-7 text-xs">
<p className='font-bold'>محتویات:</p> <p className="font-bold">محتویات:</p>
<p className='mt-2 leading-6'>{content || '-'}</p> <p className="mt-2 leading-6">{content || "-"}</p>
</div> </div>
<div className='mt-12 flex justify-between items-center'> <div className="mt-12 flex justify-between items-center">
<span dir='ltr'>{ef(price.toLocaleString('en-US'))} T</span> <span dir="ltr">{ef(price.toLocaleString("en-US"))} T</span>
<motion.div <motion.div
whileTap={{ scale: 1.05 }} whileTap={{ scale: 1.05 }}
className="bg-background active:drop-shadow-xs max-w-[115px] rounded-md w-full h-8 inline-flex p-1 justify-between items-center gap-2 relative overflow-hidden" className="bg-background active:drop-shadow-xs max-w-[115px] rounded-md w-full h-8 inline-flex p-1 justify-between items-center gap-2 relative overflow-hidden"
@@ -256,7 +265,7 @@ function FoodPage({ }: Props) {
</div> </div>
</div> </div>
</div> </div>
) );
} }
export default FoodPage export default FoodPage;
@@ -9,6 +9,8 @@ export const useGetAbout = () => {
queryFn: () => api.getAbout(name), queryFn: () => api.getAbout(name),
enabled: !!name, enabled: !!name,
retry: false, retry: false,
staleTime: 5 * 60_000,
refetchOnMount: false,
}); });
}; };
@@ -11,35 +11,54 @@ import Prompt from '@/components/utils/Prompt';
import { useGetFoods } from '@/app/[name]/(Main)/hooks/useMenuData'; import { useGetFoods } from '@/app/[name]/(Main)/hooks/useMenuData';
import MenuItem from '@/components/listview/MenuItem'; import MenuItem from '@/components/listview/MenuItem';
import MenuItemRenderer from '@/components/listview/MenuItemRenderer'; import MenuItemRenderer from '@/components/listview/MenuItemRenderer';
import type { Food } from '@/app/[name]/(Main)/types/Types'; import { useGetAbout } from '../about/hooks/useAboutData';
import { useGetAbout } from '../../(Main)/about/hooks/useAboutData'; import { useGetProfile } from '@/app/[name]/(Profile)/profile/hooks/userProfileData';
import { useGetCartItems } from './hooks/useCartData';
import PagerModal from '@/components/pager/PagerModal'; import PagerModal from '@/components/pager/PagerModal';
import Button from '@/components/button/PrimaryButton'; import Button from '@/components/button/PrimaryButton';
import NotificationBellIcon from '@/components/icons/NotificationBellIcon'; import NotificationBellIcon from '@/components/icons/NotificationBellIcon';
import CartSkeleton from './components/CartSkeleton';
import {
cartItemToFood,
guestCartItemsToFoods,
hasCartEntries,
} from './lib/cartUtils';
const CartPage = () => { const CartPage = () => {
const { t } = useTranslation('parallels', { keyPrefix: 'Cart' }); const { t } = useTranslation('parallels', { keyPrefix: 'Cart' });
const router = useRouter(); const router = useRouter();
const { isSuccess } = useGetProfile();
const { clearCart, items } = useCart(); const { clearCart, items } = useCart();
const { data: cartData, isPending: cartPending } = useGetCartItems(isSuccess);
const { data: foodsResponse } = useGetFoods();
const { data: aboutData } = useGetAbout();
const [showClearConfirm, setShowClearConfirm] = React.useState(false); const [showClearConfirm, setShowClearConfirm] = React.useState(false);
const [showPagerModal, setShowPagerModal] = React.useState(false); const [showPagerModal, setShowPagerModal] = React.useState(false);
const { data: foodsResponse, isFetching } = useGetFoods();
const { data: aboutData } = useGetAbout();
const isPremium = aboutData?.data?.plan === 'premium'; const isPremium = aboutData?.data?.plan === 'premium';
const foods = React.useMemo(() => foodsResponse?.data ?? [], [foodsResponse?.data]);
const isLoading = isFetching && !foods.length;
const cartFoods = React.useMemo(() => { const cartFoods = React.useMemo(() => {
if (!foods.length) return []; if (isSuccess) {
return Object.entries(items) const apiItems =
.filter(([, detail]) => detail?.quantity && detail.quantity > 0) cartData?.data?.items?.filter((item) => item.quantity > 0) ?? [];
.map(([id]) => return apiItems.map(cartItemToFood);
foods.find((foodItem) => String(foodItem.id) === String(id)) }
) return guestCartItemsToFoods(items, foodsResponse?.data ?? []);
.filter((food): food is Food => Boolean(food)); }, [isSuccess, cartData?.data?.items, items, foodsResponse?.data]);
}, [foods, items]);
const isCartEmpty = cartFoods.length === 0; const hasItems = React.useMemo(() => {
if (isSuccess) {
return (cartData?.data?.totalItems ?? 0) > 0 || hasCartEntries(items);
}
return hasCartEntries(items);
}, [isSuccess, cartData?.data?.totalItems, items]);
const guestAwaitingMenu =
!isSuccess && hasItems && cartFoods.length === 0 && !foodsResponse?.data;
const showInitialCartLoad = isSuccess && cartPending && !cartData && hasItems;
const isCartEmpty = !hasItems;
const handleClearCart = (e: React.MouseEvent) => { const handleClearCart = (e: React.MouseEvent) => {
e?.preventDefault(); e?.preventDefault();
@@ -55,7 +74,7 @@ const CartPage = () => {
}; };
return ( return (
<div className='overflow-y-auto h-full noscrollbar flex flex-col bg-background -mx-6 -mt-4 -mb-6 px-6 pt-4 pb-6'> <div className='overflow-y-auto h-full noscrollbar flex flex-col bg-background'>
<div className='grid grid-cols-3 items-center'> <div className='grid grid-cols-3 items-center'>
{isCartEmpty ? ( {isCartEmpty ? (
<span></span> <span></span>
@@ -77,11 +96,8 @@ const CartPage = () => {
</div> </div>
<div className="mt-8 flex-1 h-full"> <div className="mt-8 flex-1 h-full">
{isLoading ? ( {showInitialCartLoad || guestAwaitingMenu ? (
<div className='flex flex-col items-center justify-center gap-2 py-12 text-sm2 text-muted-foreground'> <CartSkeleton />
<span className='h-4 w-4 animate-spin rounded-full border border-dashed border-foreground border-t-transparent'></span>
<p>در حال دریافت سبد خرید...</p>
</div>
) : isCartEmpty ? ( ) : isCartEmpty ? (
<div className='flex flex-col items-center justify-center gap-4 h-full'> <div className='flex flex-col items-center justify-center gap-4 h-full'>
<Image <Image
@@ -131,7 +147,7 @@ const CartPage = () => {
<MenuItem food={food} /> <MenuItem food={food} />
</MenuItemRenderer> </MenuItemRenderer>
))} ))}
<CartSummary isPremium={isPremium} /> <CartSummary isPremium={isPremium} hasItems={hasItems} />
</div> </div>
)} )}
</div> </div>
@@ -0,0 +1,115 @@
'use client';
import React from 'react';
import Link from 'next/link';
import { useParams, useRouter, usePathname } from 'next/navigation';
import Button from '@/components/button/PrimaryButton';
import { useGetFoods } from '@/app/[name]/(Main)/hooks/useMenuData';
import { useCart } from '../hook/useCart';
import { useTranslation } from 'react-i18next';
import { useGetProfile } from '@/app/[name]/(Profile)/profile/hooks/userProfileData';
import { useGetCartItems } from '../hooks/useCartData';
import { useCartStore } from '../store/Store';
import { guestCartItemsToFoods } from '../lib/cartUtils';
interface CartSummaryProps {
isPremium: boolean;
hasItems: boolean;
}
const CartSummary = ({ isPremium, hasItems }: CartSummaryProps) => {
const params = useParams();
const router = useRouter();
const pathname = usePathname();
const name = params.name as string;
const { isSuccess } = useGetProfile();
const { data: foodsResponse } = useGetFoods();
const { items } = useCart();
const { data: cartData } = useGetCartItems(isSuccess);
const { t } = useTranslation('parallels', { keyPrefix: 'Cart' });
const { description, setDescription } = useCartStore();
const totalPrice = React.useMemo(() => {
if (isSuccess && cartData?.data?.total !== undefined) {
return cartData.data.total;
}
const foods = foodsResponse?.data ?? [];
const cartFoods = guestCartItemsToFoods(items, foods);
return cartFoods.reduce((sum, food) => {
const qty = items[String(food.id)]?.quantity ?? 0;
const basePrice = food.price ?? 0;
const discount = food.discount ?? 0;
const priceAfterDiscount = basePrice * (1 - discount / 100);
return sum + priceAfterDiscount * qty;
}, 0);
}, [isSuccess, cartData?.data?.total, foodsResponse?.data, items]);
const formatPrice = React.useCallback(
(value: number) => value.toLocaleString('fa-IR'),
[]
);
if (!hasItems) {
return null;
}
return (
<>
{isPremium && (
<div className='mt-2 mb-4'>
<div className='relative'>
<h3 className='text-sm'>{t('InputDescription.Label')}</h3>
<textarea
className='w-full px-4 py-2.5 mt-4 text-sm2 leading-6 outline-0 border border-border rounded-normal resize-none focus:ring-0'
placeholder={t('InputDescription.Placeholder')}
id='description'
name='description'
value={description}
onChange={(e) => setDescription(e.target.value)}
></textarea>
<span className='absolute top-4 right-2 px-2 bg-background text-foreground text-xs'></span>
</div>
</div>
)}
<div className='fixed bottom-0 left-0 right-0 z-50 bg-white dark:bg-container border-t border-border p-4 w-full'>
<div className='flex justify-between items-center gap-4'>
<div className='flex flex-col'>
<div className='text-xs text-gray-400 dark:text-disabled-text'>{t('PayableAmountLabel')}</div>
{isPremium && (
<div className='text-sm mt-2 font-semibold dark:text-foreground'>
{formatPrice(totalPrice)} تومان
</div>
)}
</div>
{isPremium && (
<Link
href={'order/checkout/0'}
onClick={(event) => {
if (!isSuccess) {
event.preventDefault();
const redirectUrl = encodeURIComponent(pathname);
router.replace(`/${name}/auth?redirect=${redirectUrl}`);
return;
}
}}
>
<Button className='w-fit! px-10 dark:bg-white dark:text-black! dark:hover:bg-gray-100'>
{t('ButtonCheckout')}
</Button>
</Link>
)}
{!isPremium && (
<div className='text-sm mt-2 font-semibold dark:text-foreground'>
{formatPrice(totalPrice)} تومان
</div>
)}
</div>
</div>
</>
);
};
export default CartSummary;
@@ -51,7 +51,7 @@ export const useIncrementCart = () => {
if (food) { if (food) {
const newItem: CartItem = { const newItem: CartItem = {
foodId: String(id), foodId: String(id),
foodTitle: food.title || food.name || "", foodTitle: food.title,
quantity: 1, quantity: 1,
price: food.price, price: food.price,
discount: food.discount || 0, discount: food.discount || 0,
@@ -184,6 +184,7 @@ export const useGetCartItems = (enabled = true) => {
queryKey: ["cart-items"], queryKey: ["cart-items"],
queryFn: api.getCartItems, queryFn: api.getCartItems,
enabled, enabled,
staleTime: 60_000,
refetchOnMount: false, refetchOnMount: false,
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
refetchOnReconnect: false, refetchOnReconnect: false,
@@ -0,0 +1,36 @@
import type { CartItem } from '../types/Types';
import type { Food } from '@/app/[name]/(Main)/types/Types';
export function cartItemToFood(item: CartItem): Food {
return {
id: item.foodId,
category: '',
title: item.foodTitle,
desc: '',
content: [],
price: item.price,
prepareTime: 0,
isActive: true,
images: [],
inPlaceServe: true,
pickupServe: true,
discount: item.discount,
isSpecialOffer: false,
};
}
export function guestCartItemsToFoods(
items: Record<string | number, { quantity: number }>,
foods: Food[]
): Food[] {
return Object.entries(items)
.filter(([, detail]) => detail?.quantity && detail.quantity > 0)
.map(([id]) => foods.find((food) => String(food.id) === String(id)))
.filter((food): food is Food => Boolean(food));
}
export function hasCartEntries(
items: Record<string | number, { quantity: number }>
): boolean {
return Object.values(items).some((detail) => detail?.quantity && detail.quantity > 0);
}
+5
View File
@@ -0,0 +1,5 @@
'use client';
import CartPage from './CartPage';
export default CartPage;
@@ -1,12 +1,11 @@
'use client'; "use client";
import { usePathname } from "next/navigation"; import { Category } from "@/app/[name]/(Main)/types/Types";
import Image from "next/image";
import clsx from "clsx";
import HorizontalScrollView from "@/components/listview/HorizontalScrollView";
import CategoryItemRenderer from "@/components/listview/CategoryItemRenderer"; import CategoryItemRenderer from "@/components/listview/CategoryItemRenderer";
import CategorySmallItemRenderer from "@/components/listview/CategorySmallItemRenderer"; import CategorySmallItemRenderer from "@/components/listview/CategorySmallItemRenderer";
import { Category } from "@/app/[name]/(Main)/types/Types"; import HorizontalScrollView from "@/components/listview/HorizontalScrollView";
import clsx from "clsx";
import { usePathname } from "next/navigation";
const SVG_MASK_STYLE = { const SVG_MASK_STYLE = {
maskSize: "contain", maskSize: "contain",
@@ -71,9 +70,7 @@ function CategoryImage({
); );
} }
return ( return <img src={src} width={size} height={size} alt={alt} loading="lazy" />;
<Image priority src={src} width={size} height={size} alt={alt} />
);
} }
type Variant = "large" | "small"; type Variant = "large" | "small";
@@ -121,7 +118,7 @@ const CategoryScroll = ({
className={clsx( className={clsx(
"w-full noscrollbar py-4!", "w-full noscrollbar py-4!",
variant === "large" && "mt-4!", variant === "large" && "mt-4!",
className className,
)} )}
> >
{/* <Renderer {/* <Renderer
@@ -153,7 +150,9 @@ const CategoryScroll = ({
alt="category image" alt="category image"
proxyBase={proxyBase} proxyBase={proxyBase}
/> />
<span className="text-xs text-foreground text-center">{item.title}</span> <span className="text-xs text-foreground text-center">
{item.title}
</span>
</Renderer> </Renderer>
); );
})} })}
@@ -162,4 +161,3 @@ const CategoryScroll = ({
}; };
export default CategoryScroll; export default CategoryScroll;
@@ -8,6 +8,8 @@ export const useGetFoods = () => {
queryKey: ["menu"], queryKey: ["menu"],
queryFn: () => api.getFoods(name), queryFn: () => api.getFoods(name),
enabled: !!name, enabled: !!name,
staleTime: 5 * 60_000,
refetchOnMount: false,
}); });
}; };
+4 -7
View File
@@ -121,8 +121,8 @@ const MenuIndex = () => {
const selectedCatId = selectedCategory === '0' ? null : selectedCategory; const selectedCatId = selectedCategory === '0' ? null : selectedCategory;
const filtered = foods.filter((item) => { const filtered = foods.filter((item) => {
const matchesCategory = !selectedCatId || item.category?.id === selectedCatId; const matchesCategory = !selectedCatId || item.category === selectedCatId;
const itemName = item.name || item.title || item.foodName || ""; const itemName = item.title;
const matchesSearch = const matchesSearch =
!search || itemName.toLowerCase().includes(lowerSearch); !search || itemName.toLowerCase().includes(lowerSearch);
@@ -134,9 +134,7 @@ const MenuIndex = () => {
content.toLowerCase().includes(lowerIngredients) content.toLowerCase().includes(lowerIngredients)
); );
} }
// اگر محتویات وجود نداشت یا خالی بود، در توضیحات جستجو می‌کنیم return item.desc.toLowerCase().includes(lowerIngredients);
const description = item.description || item.desc || "";
return description.toLowerCase().includes(lowerIngredients);
})(); })();
const matchesDelivery = selectedDeliveryId === "0" || const matchesDelivery = selectedDeliveryId === "0" ||
@@ -151,9 +149,8 @@ const MenuIndex = () => {
return [...filtered].sort((a, b) => { return [...filtered].sort((a, b) => {
switch (sortingKey) { switch (sortingKey) {
case "PopularityDescending": case "PopularityDescending":
return (b.points ?? 0) - (a.points ?? 0);
case "RateDescending": case "RateDescending":
return (b.rate ?? 0) - (a.rate ?? 0); return 0;
case "PriceAscending": case "PriceAscending":
return (a.price ?? 0) - (b.price ?? 0); return (a.price ?? 0) - (b.price ?? 0);
case "PriceDescending": case "PriceDescending":
+19 -1
View File
@@ -30,7 +30,25 @@ export interface InventoryRef {
export type MealType = "breakfast" | "lunch" | "dinner" | "snack"; export type MealType = "breakfast" | "lunch" | "dinner" | "snack";
/** آیتم منو در لیست غذاهای رستوران */
export interface Food { export interface Food {
id: string;
category: string;
title: string;
desc: string;
content: string[];
price: number;
prepareTime: number;
isActive: boolean;
images: string[];
inPlaceServe: boolean;
pickupServe: boolean;
discount: number;
isSpecialOffer: boolean;
}
/** جزئیات کامل یک غذا (endpoint تکی) */
export interface FoodDetail {
id: string; id: string;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
@@ -75,7 +93,7 @@ export interface Food {
} }
export type FoodsResponse = BaseResponse<Food[]>; export type FoodsResponse = BaseResponse<Food[]>;
export type FoodResponse = BaseResponse<Food>; export type FoodResponse = BaseResponse<FoodDetail>;
export interface NotificationsCountData { export interface NotificationsCountData {
count: number; count: number;
+2 -16
View File
@@ -24,32 +24,18 @@ function FavoritePage() {
// تبدیل FavoriteFood به Food برای استفاده در MenuItem // تبدیل FavoriteFood به Food برای استفاده در MenuItem
const food: Food = { const food: Food = {
id: favoriteFood.id, id: favoriteFood.id,
createdAt: favoriteFood.createdAt, category: favoriteFood.category,
updatedAt: favoriteFood.updatedAt,
deletedAt: favoriteFood.deletedAt,
restaurant: favoriteFood.restaurant as unknown as Food['restaurant'],
category: favoriteFood.category as unknown as Food['category'],
inventory: {} as Food['inventory'],
title: favoriteFood.title, title: favoriteFood.title,
desc: favoriteFood.desc, desc: favoriteFood.desc,
content: favoriteFood.content, content: favoriteFood.content,
price: favoriteFood.price, price: favoriteFood.price,
order: favoriteFood.order, prepareTime: favoriteFood.prepareTime ?? 0,
prepareTime: favoriteFood.prepareTime,
weekDays: favoriteFood.weekDays,
mealTypes: favoriteFood.mealTypes,
isActive: favoriteFood.isActive, isActive: favoriteFood.isActive,
images: favoriteFood.images, images: favoriteFood.images,
inPlaceServe: favoriteFood.inPlaceServe, inPlaceServe: favoriteFood.inPlaceServe,
pickupServe: favoriteFood.pickupServe, pickupServe: favoriteFood.pickupServe,
score: favoriteFood.score,
discount: favoriteFood.discount, discount: favoriteFood.discount,
isSpecialOffer: favoriteFood.isSpecialOffer, isSpecialOffer: favoriteFood.isSpecialOffer,
reviews: [],
favorites: [],
isFavorite: true,
name: favoriteFood.title,
description: favoriteFood.desc,
} }
return food return food
}) })
+4 -7
View File
@@ -1,20 +1,17 @@
import { cache } from "react";
import { API_BASE_URL } from "@/config/const"; import { API_BASE_URL } from "@/config/const";
import { AboutResponse, Restaurant } from "../(Main)/about/types/Types"; import { AboutResponse, Restaurant } from "../(Main)/about/types/Types";
import axios from "axios"; import axios from "axios";
export async function getRestaurant(slug: string): Promise<Restaurant> { async function fetchRestaurant(slug: string): Promise<Restaurant> {
try { try {
const response = await axios(`${API_BASE_URL}/public/restaurants/${slug}`, { const response = await axios(`${API_BASE_URL}/public/restaurants/${slug}`, {
// cache: "no-store",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"X-SLUG": slug, "X-SLUG": slug,
}, },
}); });
console.log("response", response);
if (!response.data) { if (!response.data) {
if (response.status === 404) { if (response.status === 404) {
throw new Error("RESTAURANT_NOT_FOUND"); throw new Error("RESTAURANT_NOT_FOUND");
@@ -30,11 +27,11 @@ export async function getRestaurant(slug: string): Promise<Restaurant> {
return data.data; return data.data;
} catch (error) { } catch (error) {
console.log("error", error);
if (error instanceof Error && error.message === "RESTAURANT_NOT_FOUND") { if (error instanceof Error && error.message === "RESTAURANT_NOT_FOUND") {
throw error; throw error;
} }
throw new Error(`Failed to fetch restaurant data: ${error}`); throw new Error(`Failed to fetch restaurant data: ${error}`);
} }
} }
export const getRestaurant = cache(fetchRestaurant);
+1 -1
View File
@@ -1,6 +1,6 @@
"use client" "use client"
import { useCart } from '@/app/[name]/(Dialogs)/cart/hook/useCart' import { useCart } from '@/app/[name]/(Main)/cart/hook/useCart'
import { useGetProfile } from '@/app/[name]/(Profile)/profile/hooks/userProfileData' import { useGetProfile } from '@/app/[name]/(Profile)/profile/hooks/userProfileData'
import { useParams } from 'next/navigation' import { useParams } from 'next/navigation'
import { useEffect, useRef, type FC } from 'react' import { useEffect, useRef, type FC } from 'react'
+27 -32
View File
@@ -1,14 +1,14 @@
/* eslint-disable @next/next/no-img-element */ /* eslint-disable @next/next/no-img-element */
'use client' "use client";
import { memo, useMemo } from "react"; import { useCart } from "@/app/[name]/(Main)/cart/hook/useCart";
import type { Food, FoodImage } from "@/app/[name]/(Main)/types/Types";
import MinusIcon from "@/components/icons/MinusIcon";
import PlusIcon from "@/components/icons/PlusIcon";
import { motion } from "framer-motion";
import Link from "next/link"; import Link from "next/link";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
import PlusIcon from "@/components/icons/PlusIcon"; import { memo, useMemo } from "react";
import MinusIcon from "@/components/icons/MinusIcon";
import { useCart } from "@/app/[name]/(Dialogs)/cart/hook/useCart";
import { motion } from "framer-motion";
import type { Food, FoodImage } from "@/app/[name]/(Main)/types/Types";
interface MenuItemProps { interface MenuItemProps {
food: Food; food: Food;
@@ -17,11 +17,11 @@ interface MenuItemProps {
const MenuItem = ({ food }: MenuItemProps) => { const MenuItem = ({ food }: MenuItemProps) => {
const { items, addToCart, removeFromCart } = useCart(); const { items, addToCart, removeFromCart } = useCart();
const params = useParams<{ name: string }>(); const params = useParams<{ name: string }>();
const name = params?.name || ''; const name = params?.name || "";
const quantity = useMemo(() => { const quantity = useMemo(() => {
const item = items?.[food.id]; const item = items?.[food.id];
if (item && typeof item === 'object' && 'quantity' in item) { if (item && typeof item === "object" && "quantity" in item) {
return item.quantity || 0; return item.quantity || 0;
} }
return 0; return 0;
@@ -29,7 +29,6 @@ const MenuItem = ({ food }: MenuItemProps) => {
const fallbackImage = "/assets/images/no-image.png"; const fallbackImage = "/assets/images/no-image.png";
const resolvedImage = useMemo(() => { const resolvedImage = useMemo(() => {
if (food.image) return food.image;
if (Array.isArray(food.images) && food.images.length > 0) { if (Array.isArray(food.images) && food.images.length > 0) {
const [firstImage] = food.images; const [firstImage] = food.images;
if (typeof firstImage === "string") return firstImage; if (typeof firstImage === "string") return firstImage;
@@ -37,29 +36,28 @@ const MenuItem = ({ food }: MenuItemProps) => {
return (typeof imageObj === "object" && imageObj?.url) || fallbackImage; return (typeof imageObj === "object" && imageObj?.url) || fallbackImage;
} }
return fallbackImage; return fallbackImage;
}, [food.image, food.images]); }, [food.images]);
const foodName = useMemo( const foodName = useMemo(() => food.title || "بدون نام", [food.title]);
() => food.name || food.title || food.foodName || 'بدون نام',
[food.name, food.title, food.foodName]
);
const foodContent = useMemo(() => { const foodContent = useMemo(() => {
const content = food.content; const content = food.content;
if (!content) return ''; if (!content) return "";
if (Array.isArray(content)) { if (Array.isArray(content)) {
return content.filter(item => item && typeof item === 'string').join('، '); return content
.filter((item) => item && typeof item === "string")
.join("، ");
} }
return ''; return "";
}, [food.content]); }, [food.content]);
const foodDescription = useMemo(() => { const foodDescription = useMemo(() => {
const desc = food.description || food.desc; const desc = food.desc;
if (!desc || typeof desc !== 'string') return ''; if (!desc || typeof desc !== "string") return "";
return desc.replace(/,/g, '،'); return desc.replace(/,/g, "،");
}, [food.description, food.desc]); }, [food.desc]);
const finalPrice = useMemo(() => { const finalPrice = useMemo(() => {
const basePrice = food.price || 0; const basePrice = food.price || 0;
@@ -71,19 +69,16 @@ const MenuItem = ({ food }: MenuItemProps) => {
}, [food.price, food.discount]); }, [food.price, food.discount]);
const formattedPrice = useMemo( const formattedPrice = useMemo(
() => (finalPrice ? finalPrice.toLocaleString('fa-IR') : '0'), () => (finalPrice ? finalPrice.toLocaleString("fa-IR") : "0"),
[finalPrice] [finalPrice],
); );
const formattedOriginalPrice = useMemo( const formattedOriginalPrice = useMemo(
() => (food.price ? food.price.toLocaleString('fa-IR') : '0'), () => (food.price ? food.price.toLocaleString("fa-IR") : "0"),
[food.price] [food.price],
); );
const hasDiscount = useMemo( const hasDiscount = useMemo(() => (food.discount || 0) > 0, [food.discount]);
() => (food.discount || 0) > 0,
[food.discount]
);
const handleAddToCart = () => { const handleAddToCart = () => {
addToCart(food.id); addToCart(food.id);
@@ -93,7 +88,7 @@ const MenuItem = ({ food }: MenuItemProps) => {
removeFromCart(food.id); removeFromCart(food.id);
}; };
const foodDetailUrl = `/${name}/${food.id}?category=${food.category.id}`; const foodDetailUrl = `/${name}/${food.id}?category=${food.category}`;
return ( return (
<div className="flex gap-4 w-full h-full items-center"> <div className="flex gap-4 w-full h-full items-center">
@@ -104,6 +99,7 @@ const MenuItem = ({ food }: MenuItemProps) => {
height={112} height={112}
width={112} width={112}
alt={foodName} alt={foodName}
loading="lazy"
/> />
</Link> </Link>
<div className="w-full inline-flex flex-col justify-between min-w-0"> <div className="w-full inline-flex flex-col justify-between min-w-0">
@@ -144,7 +140,6 @@ const MenuItem = ({ food }: MenuItemProps) => {
<button <button
onClick={handleAddToCart} onClick={handleAddToCart}
className="inline-flex w-full justify-center items-center gap-2" className="inline-flex w-full justify-center items-center gap-2"
> >
<PlusIcon /> <PlusIcon />
<div className="text-sm2 pt-0.5 font-normal text-foreground"> <div className="text-sm2 pt-0.5 font-normal text-foreground">
+1 -1
View File
@@ -10,7 +10,7 @@ import HeartIcon from '../icons/HeartIcon'
import { useParams, usePathname, useRouter } from 'next/navigation' import { useParams, usePathname, useRouter } from 'next/navigation'
import HomeIcon from '../icons/HomeIcon' import HomeIcon from '../icons/HomeIcon'
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useCart } from '@/app/[name]/(Dialogs)/cart/hook/useCart'; import { useCart } from '@/app/[name]/(Main)/cart/hook/useCart';
import { Building } from 'iconsax-react'; import { Building } from 'iconsax-react';
import clsx from 'clsx'; import clsx from 'clsx';
import { useGetProfile } from '@/app/[name]/(Profile)/profile/hooks/userProfileData'; import { useGetProfile } from '@/app/[name]/(Profile)/profile/hooks/userProfileData';
+3 -2
View File
@@ -11,12 +11,13 @@ type Props = {
value: string value: string
} & LinkProps & React.AnchorHTMLAttributes<HTMLAnchorElement> } & LinkProps & React.AnchorHTMLAttributes<HTMLAnchorElement>
function BottomNavLink({ icon, value, href, ...restProps }: Props) { function BottomNavLink({ icon, value, href, prefetch, ...restProps }: Props) {
const pathname = usePathname(); const pathname = usePathname();
const isActive = pathname === href; const isActive = pathname === href;
const shouldPrefetch = prefetch ?? true;
return ( return (
<Link {...restProps} href={href} className={clsx( <Link {...restProps} href={href} prefetch={shouldPrefetch} className={clsx(
'flex flex-col justify-arround items-center gap-[5px] text-primary pointer-events-auto **:stroke-primary min-w-0 dark:text-white dark:**:stroke-white', 'flex flex-col justify-arround items-center gap-[5px] text-primary pointer-events-auto **:stroke-primary min-w-0 dark:text-white dark:**:stroke-white',
isActive && 'text-primary-foreground **:stroke-primary-foreground', isActive && 'text-primary-foreground **:stroke-primary-foreground',
restProps.className ?? '', restProps.className ?? '',
+1 -1
View File
@@ -32,7 +32,7 @@ function ClientSideWrapper({ children, className = 'h-full' }: Props) {
initial={isInitial || !pathnameChanged ? false : { y: -10 }} initial={isInitial || !pathnameChanged ? false : { y: -10 }}
animate={{ y: 0 }} animate={{ y: 0 }}
exit={{ y: 0, transition: { duration: 0 } }} exit={{ y: 0, transition: { duration: 0 } }}
transition={{ duration: 0.3 }} transition={{ duration: 0.12 }}
style={{ opacity: 1 }} style={{ opacity: 1 }}
className={clsx(className)} className={clsx(className)}
> >
+2 -1
View File
@@ -1,4 +1,5 @@
// export const API_BASE_URL = "https://dmenuplus-api.dev.danakcorp.com"; // export const API_BASE_URL = "https://dmenuplus-api.dev.danakcorp.com";
export const API_BASE_URL = "https://dmenu-api.danakcorp.com"; // export const API_BASE_URL = "https://dmenu-api.danakcorp.com";
export const API_BASE_URL = "http://192.168.99.131:2000";
export const TOKEN_NAME = "dmenu-t"; export const TOKEN_NAME = "dmenu-t";
export const REFRESH_TOKEN_NAME = "dmenu-rt"; export const REFRESH_TOKEN_NAME = "dmenu-rt";
+1 -1
View File
@@ -9,7 +9,7 @@ import { toast } from '@/components/Toast'
import Button from '@/components/button/PrimaryButton' import Button from '@/components/button/PrimaryButton'
import { extractErrorMessage } from '@/lib/func' import { extractErrorMessage } from '@/lib/func'
import { useReceiptStore } from '@/zustand/receiptStore' import { useReceiptStore } from '@/zustand/receiptStore'
import { useBulkCart } from '@/app/[name]/(Dialogs)/cart/hooks/useCartData' import { useBulkCart } from '@/app/[name]/(Main)/cart/hooks/useCartData'
import { setRefreshToken, setToken } from '@/lib/api/func' import { setRefreshToken, setToken } from '@/lib/api/func'
import { useSearchParams } from 'next/navigation' import { useSearchParams } from 'next/navigation'