single food
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import * as api from "../service/Service";
|
||||||
|
|
||||||
|
export const useGetFood = (id: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["food", id],
|
||||||
|
queryFn: () => api.getFood(id),
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -3,39 +3,96 @@
|
|||||||
import MinusIcon from '@/components/icons/MinusIcon'
|
import MinusIcon from '@/components/icons/MinusIcon'
|
||||||
import PlusIcon from '@/components/icons/PlusIcon'
|
import PlusIcon from '@/components/icons/PlusIcon'
|
||||||
import { ef } from '@/lib/helpers/utfNumbers'
|
import { ef } from '@/lib/helpers/utfNumbers'
|
||||||
import { useReceiptStore } from '@/zustand/receiptStore'
|
import { useCart } from '@/app/[name]/(Dialogs)/cart/hook/useCart'
|
||||||
import { motion } from 'framer-motion'
|
import { motion } from 'framer-motion'
|
||||||
import { ArrowLeft, Clock, Cup, Heart, TruckTick } from 'iconsax-react'
|
import { ArrowLeft, Clock, Cup, Heart, TruckTick } from 'iconsax-react'
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import React from 'react'
|
import { useParams, useRouter } from 'next/navigation'
|
||||||
|
import React, { useMemo } from 'react'
|
||||||
|
import { useGetFood } from './hooks/useFoodData'
|
||||||
|
import { useGetAbout } from '../about/hooks/useAboutData'
|
||||||
|
|
||||||
type Props = object
|
type Props = object
|
||||||
|
|
||||||
function FoodPage({ }: Props) {
|
function FoodPage({ }: Props) {
|
||||||
const quantity = useReceiptStore(state => state.items[0]?.quantity || 0);
|
|
||||||
const increment = useReceiptStore(state => state.increment);
|
|
||||||
const decrement = useReceiptStore(state => state.decrement);
|
|
||||||
|
|
||||||
const price = 0; // TODO: Replace with actual price if needed
|
const { id } = useParams();
|
||||||
|
const router = useRouter();
|
||||||
|
const { data: food, isLoading } = useGetFood(id as string);
|
||||||
|
const { data: about } = useGetAbout();
|
||||||
|
const { items, addToCart, removeFromCart } = useCart();
|
||||||
|
|
||||||
|
const foodId = food?.data?.id || id as string;
|
||||||
|
const quantity = useMemo(() => {
|
||||||
|
const item = items?.[foodId];
|
||||||
|
if (item && typeof item === 'object' && 'quantity' in item) {
|
||||||
|
return item.quantity || 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}, [items, foodId]);
|
||||||
|
|
||||||
|
const handleAddToCart = () => {
|
||||||
|
if (foodId) {
|
||||||
|
addToCart(foodId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveFromCart = () => {
|
||||||
|
if (foodId) {
|
||||||
|
removeFromCart(foodId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className='flex items-center justify-center min-h-[400px]'>
|
||||||
|
<p className='text-disabled-text'>در حال بارگذاری...</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!food?.data) {
|
||||||
|
return (
|
||||||
|
<div className='flex items-center justify-center min-h-[400px]'>
|
||||||
|
<p className='text-disabled-text'>غذا یافت نشد</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const foodData = food.data;
|
||||||
|
const foodName = foodData.title || foodData.name || foodData.foodName || '';
|
||||||
|
const foodImage = typeof foodData.image === 'string'
|
||||||
|
? foodData.image
|
||||||
|
: Array.isArray(foodData.images) && foodData.images.length > 0
|
||||||
|
? typeof foodData.images[0] === 'string'
|
||||||
|
? foodData.images[0]
|
||||||
|
: foodData.images[0]?.url
|
||||||
|
: '/assets/images/food-preview-hq.png';
|
||||||
|
const categoryName = foodData.category?.title;
|
||||||
|
const prepareTime = foodData.prepareTime || 0;
|
||||||
|
const price = foodData.price || 0;
|
||||||
|
const content = Array.isArray(foodData.content)
|
||||||
|
? foodData.content.join('، ')
|
||||||
|
: foodData.content || foodData.desc || '';
|
||||||
|
|
||||||
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'>
|
<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'>
|
||||||
<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 h-full'
|
className='w-full object-cover h-full'
|
||||||
src={'/assets/images/food-preview-hq.png'}
|
src={foodImage}
|
||||||
alt='Food image'
|
alt={foodName}
|
||||||
height={100}
|
height={100}
|
||||||
width={100}
|
width={100}
|
||||||
unoptimized
|
unoptimized
|
||||||
priority
|
priority
|
||||||
/>
|
/>
|
||||||
<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 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 || '-'}
|
||||||
</div>
|
</div>
|
||||||
<button>
|
<button onClick={() => router.back()} className='p-2 rounded-full bg-container/40'>
|
||||||
<ArrowLeft size={24} className='stroke-white' />
|
<ArrowLeft size={18} className='stroke-black' />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -43,7 +100,7 @@ function FoodPage({ }: Props) {
|
|||||||
<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>
|
</h5>
|
||||||
<button className='p-2 bg-[#EAECF0] dark:bg-neutral-600 rounded-lg'>
|
<button className='p-2 bg-[#EAECF0] dark:bg-neutral-600 rounded-lg'>
|
||||||
<Heart variant='Bold' size={24} className='fill-black dark:fill-foreground' />
|
<Heart variant='Bold' size={24} className='fill-black dark:fill-foreground' />
|
||||||
@@ -53,44 +110,66 @@ function FoodPage({ }: Props) {
|
|||||||
<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'>زمان پخت و آماده سازی: {ef('25')} دقیقه</span>
|
<span className='text-disabled-text'>
|
||||||
|
زمان پخت و آماده سازی: {prepareTime > 0 ? `${ef(String(prepareTime))} دقیقه` : '-'}
|
||||||
|
</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'>{ef('1500')} امتیاز برای هر بار خرید</span>
|
<span className='text-disabled-text flex gap-1'>
|
||||||
|
{about?.data?.score?.purchaseScore && about?.data?.score?.purchaseAmount ? (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
{ef(
|
||||||
|
Math.round(
|
||||||
|
foodData?.price *
|
||||||
|
(Number(about.data.score.purchaseScore) / Number(about.data.score.purchaseAmount))
|
||||||
|
).toLocaleString('en-US')
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<span>امتیاز برای هر بار خرید</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<span>-</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>
|
<span className='text-disabled-text'>
|
||||||
|
{foodData.pickupServe ? 'ارسال با پیک' : '-'}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4 text-xs">
|
<div className="mt-4 text-xs">
|
||||||
<p>محتویات:</p>
|
<p>محتویات:</p>
|
||||||
<p>گوشت، سبزیجات، پیاز، ادویه جات معطر و ....</p>
|
<p>{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'>{price} 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"
|
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"
|
||||||
>
|
>
|
||||||
{quantity <= 0 ? (
|
{quantity <= 0 ? (
|
||||||
<button
|
<button
|
||||||
onClick={() => increment(0)}
|
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 />
|
||||||
<span className="text-sm2 pt-0.5 font-normal">افزودن</span>
|
<div className="text-sm2 pt-0.5 font-normal text-foreground">
|
||||||
|
افزودن
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={() => increment(0)}
|
onClick={handleAddToCart}
|
||||||
className="bg-container hover:bg-container/60 active:bg-container/30 rounded-sm p-2"
|
className="bg-container hover:bg-container/60 active:bg-container/30 rounded-sm p-2 transition-colors"
|
||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon className="text-foreground" />
|
||||||
</button>
|
</button>
|
||||||
<motion.div
|
<motion.div
|
||||||
key={quantity}
|
key={quantity}
|
||||||
@@ -102,10 +181,10 @@ function FoodPage({ }: Props) {
|
|||||||
{quantity}
|
{quantity}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<button
|
<button
|
||||||
onClick={() => decrement(0)}
|
onClick={handleRemoveFromCart}
|
||||||
className="bg-container hover:bg-container/60 active:bg-container/30 rounded-sm p-2"
|
className="bg-container hover:bg-container/60 active:bg-container/30 rounded-sm p-2 transition-colors"
|
||||||
>
|
>
|
||||||
<MinusIcon />
|
<MinusIcon className="text-foreground" />
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { api } from "@/config/axios";
|
||||||
|
import { FoodResponse } from "../../types/Types";
|
||||||
|
|
||||||
|
export const getFood = async (id: string): Promise<FoodResponse> => {
|
||||||
|
const { data } = await api.get<FoodResponse>(`/public/foods/${id}`);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
@@ -32,7 +32,7 @@ export interface Food {
|
|||||||
foodName?: string;
|
foodName?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
desc?: string | null;
|
desc?: string | null;
|
||||||
content?: string | null;
|
content?: string[] | null;
|
||||||
price: number;
|
price: number;
|
||||||
image?: string | null;
|
image?: string | null;
|
||||||
images?: FoodImage[] | null;
|
images?: FoodImage[] | null;
|
||||||
@@ -52,7 +52,9 @@ export interface Food {
|
|||||||
pickupServe?: boolean;
|
pickupServe?: boolean;
|
||||||
rate?: number;
|
rate?: number;
|
||||||
discount?: number;
|
discount?: number;
|
||||||
|
isSpecialOffer?: boolean;
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FoodsResponse = BaseResponse<Food[]>;
|
export type FoodsResponse = BaseResponse<Food[]>;
|
||||||
|
export type FoodResponse = BaseResponse<Food>;
|
||||||
|
|||||||
Reference in New Issue
Block a user