change forder file name + change service url

This commit is contained in:
hamid zarghami
2026-02-10 10:05:33 +03:30
parent f9308b470a
commit 8670e811b0
29 changed files with 302 additions and 294 deletions
+4
View File
@@ -0,0 +1,4 @@
<svg width="32" height="40" viewBox="0 0 32 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- Simple location pin -->
<path d="M16 0C7.163 0 0 7.163 0 16C0 28 16 40 16 40C16 40 32 28 32 16C32 7.163 24.837 0 16 0Z" fill="#EF4444"/>
</svg>

After

Width:  |  Height:  |  Size: 249 B

@@ -3,33 +3,33 @@
import React from 'react';
import MenuItem from '@/components/listview/MenuItem';
import MenuItemRenderer from '@/components/listview/MenuItemRenderer';
import { useGetFoods } from '@/app/[name]/(Main)/hooks/useMenuData';
import type { Food } from '@/app/[name]/(Main)/types/Types';
import { useGetProducts } from '@/app/[name]/(Main)/hooks/useMenuData';
import type { Product } from '@/app/[name]/(Main)/types/Types';
import { useCart } from '../hook/useCart';
import { useTranslation } from 'react-i18next';
import { CartItemsSkeleton } from './CartSkeleton';
const CartItemsList = () => {
const { data: foodsResponse, isFetching } = useGetFoods();
const foods = React.useMemo(() => foodsResponse?.data ?? [], [foodsResponse?.data]);
const { data: productsResponse, isFetching } = useGetProducts();
const products = React.useMemo(() => productsResponse?.data ?? [], [productsResponse?.data]);
const { items } = useCart();
const { t } = useTranslation('parallels', { keyPrefix: 'Cart' });
const cartFoods = React.useMemo(() => {
if (!foods.length) return [];
const cartProducts = React.useMemo(() => {
if (!products.length) return [];
return Object.entries(items)
.filter(([, detail]) => detail?.quantity && detail.quantity > 0)
.map(([id]) =>
foods.find((foodItem) => String(foodItem.id) === String(id))
products.find((productItem) => String(productItem.id) === String(id))
)
.filter((food): food is Food => Boolean(food));
}, [foods, items]);
.filter((product): product is Product => Boolean(product));
}, [products, items]);
if (isFetching && !foods.length) {
if (isFetching && !products.length) {
return <CartItemsSkeleton />;
}
if (cartFoods.length === 0) {
if (cartProducts.length === 0) {
return (
<div className='flex flex-col items-center justify-center gap-2 py-12 text-sm2 text-muted-foreground'>
<p>{t('EmptyStateTitle', { defaultValue: 'سبد خرید شما خالی است' })}</p>
@@ -44,9 +44,9 @@ const CartItemsList = () => {
return (
<div className='flex-1 space-y-4'>
{cartFoods.map((food) => (
<MenuItemRenderer key={food.id}>
<MenuItem food={food} />
{cartProducts.map((product) => (
<MenuItemRenderer key={product.id}>
<MenuItem product={product} />
</MenuItemRenderer>
))}
</div>
@@ -4,8 +4,8 @@ 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 { useGetProducts } from '@/app/[name]/(Main)/hooks/useMenuData';
import type { Product } 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';
@@ -22,22 +22,22 @@ const CartSummary = ({ isPremium }: CartSummaryProps) => {
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 { data: productsResponse } = useGetProducts();
const products = React.useMemo(() => productsResponse?.data ?? [], [productsResponse?.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 [];
const cartProducts = React.useMemo(() => {
if (!products.length) return [];
return Object.entries(items)
.filter(([, detail]) => detail?.quantity && detail.quantity > 0)
.map(([id]) =>
foods.find((foodItem) => String(foodItem.id) === String(id))
products.find((productItem) => String(productItem.id) === String(id))
)
.filter((food): food is Food => Boolean(food));
}, [foods, items]);
.filter((product): product is Product => Boolean(product));
}, [products, items]);
const totalPrice = React.useMemo(() => {
// اگر داده از API آمده و total وجود دارد، از آن استفاده می‌کنیم
@@ -46,16 +46,16 @@ const CartSummary = ({ isPremium }: CartSummaryProps) => {
}
// در غیر این صورت، محاسبه محلی با در نظر گرفتن تخفیف
return cartFoods.reduce((sum, food) => {
const qty = items[String(food.id)]?.quantity ?? 0;
const basePrice = food.price ?? 0;
const discount = food.discount ?? 0;
return cartProducts.reduce((sum, product) => {
const qty = items[String(product.id)]?.quantity ?? 0;
const basePrice = product.price ?? 0;
const discount = product.discount ?? 0;
const priceAfterDiscount = basePrice * (1 - discount / 100);
return sum + priceAfterDiscount * qty;
}, 0);
}, [cartFoods, items, isSuccess, cartData?.data?.total]);
}, [cartProducts, items, isSuccess, cartData?.data?.total]);
const isCartEmpty = cartFoods.length === 0;
const isCartEmpty = cartProducts.length === 0;
const formatPrice = React.useCallback(
(value: number) => value.toLocaleString('fa-IR'),
@@ -1,7 +1,7 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import * as api from "../service/CartService";
import type { CartResponse, CartItem } from "../types/Types";
import type { FoodsResponse } from "@/app/[name]/(Main)/types/Types";
import type { ProductsResponse } from "@/app/[name]/(Main)/types/Types";
export const useBulkCart = () => {
const queryClient = useQueryClient();
@@ -24,9 +24,9 @@ export const useIncrementCart = () => {
const previousCart = queryClient.getQueryData<CartResponse>([
"cart-items",
]);
const foodsData = queryClient.getQueryData<FoodsResponse>(["menu"]);
const productsData = queryClient.getQueryData<ProductsResponse>(["menu"]);
if (previousCart?.data && foodsData?.data) {
if (previousCart?.data && productsData?.data) {
const existingItem = previousCart.data.items.find(
(item) => item.foodId === String(id)
);
@@ -47,15 +47,15 @@ export const useIncrementCart = () => {
});
newTotalItems = previousCart.data.totalItems + 1;
} else {
const food = foodsData.data.find((f) => String(f.id) === String(id));
if (food) {
const product = productsData.data.find((f) => String(f.id) === String(id));
if (product) {
const newItem: CartItem = {
foodId: String(id),
foodTitle: food.title || food.name || "",
foodTitle: product.title || product.name || "",
quantity: 1,
price: food.price,
discount: food.discount || 0,
totalPrice: food.price * (1 - (food.discount || 0) / 100),
price: product.price,
discount: product.discount || 0,
totalPrice: product.price * (1 - (product.discount || 0) / 100),
};
updatedItems = [...previousCart.data.items, newItem];
newTotalItems = previousCart.data.totalItems + 1;
+14 -14
View File
@@ -8,10 +8,10 @@ import Image from 'next/image';
import { useCart } from './hook/useCart';
import CartSummary from './components/CartSummary';
import Prompt from '@/components/utils/Prompt';
import { useGetFoods } from '@/app/[name]/(Main)/hooks/useMenuData';
import { useGetProducts } from '@/app/[name]/(Main)/hooks/useMenuData';
import MenuItem from '@/components/listview/MenuItem';
import MenuItemRenderer from '@/components/listview/MenuItemRenderer';
import type { Food } from '@/app/[name]/(Main)/types/Types';
import type { Product } from '@/app/[name]/(Main)/types/Types';
import { useGetAbout } from '../../(Main)/about/hooks/useAboutData';
import PagerModal from '@/components/pager/PagerModal';
import Button from '@/components/button/PrimaryButton';
@@ -23,23 +23,23 @@ const CartIndex = () => {
const { clearCart, items } = useCart();
const [showClearConfirm, setShowClearConfirm] = React.useState(false);
const [showPagerModal, setShowPagerModal] = React.useState(false);
const { data: foodsResponse, isFetching } = useGetFoods();
const { data: productsResponse, isFetching } = useGetProducts();
const { data: aboutData } = useGetAbout();
const isPremium = aboutData?.data?.plan === 'premium';
const foods = React.useMemo(() => foodsResponse?.data ?? [], [foodsResponse?.data]);
const isLoading = isFetching && !foods.length;
const products = React.useMemo(() => productsResponse?.data ?? [], [productsResponse?.data]);
const isLoading = isFetching && !products.length;
const cartFoods = React.useMemo(() => {
if (!foods.length) return [];
const cartProducts = React.useMemo(() => {
if (!products.length) return [];
return Object.entries(items)
.filter(([, detail]) => detail?.quantity && detail.quantity > 0)
.map(([id]) =>
foods.find((foodItem) => String(foodItem.id) === String(id))
products.find((productItem) => String(productItem.id) === String(id))
)
.filter((food): food is Food => Boolean(food));
}, [foods, items]);
.filter((product): product is Product => Boolean(product));
}, [products, items]);
const isCartEmpty = cartFoods.length === 0;
const isCartEmpty = cartProducts.length === 0;
const handleClearCart = (e: React.MouseEvent) => {
e?.preventDefault();
@@ -126,9 +126,9 @@ const CartIndex = () => {
</Button>
</div>
)}
{cartFoods.map((food) => (
<MenuItemRenderer key={food.id}>
<MenuItem food={food} />
{cartProducts.map((product) => (
<MenuItemRenderer key={product.id}>
<MenuItem product={product} />
</MenuItemRenderer>
))}
<CartSummary isPremium={isPremium} />
@@ -8,14 +8,14 @@ import {
export const getShipmentMethod = async (): Promise<ShipmentMethodsResponse> => {
const { data } = await api.get<ShipmentMethodsResponse>(
"/public/delivery-methods/restaurant"
"/public/delivery-methods/shop"
);
return data;
};
export const getPaymentMethod = async (): Promise<PaymentMethodsResponse> => {
const { data } = await api.get<PaymentMethodsResponse>(
"/public/payments/methods/restaurant"
"/public/payments/methods/shop"
);
return data;
};
@@ -1,10 +1,10 @@
import { useMutation, useQuery } from "@tanstack/react-query";
import * as api from "../service/Service";
export const useGetFood = (id: string) => {
export const useGetProduct = (id: string) => {
return useQuery({
queryKey: ["food", id],
queryFn: () => api.getFood(id),
queryKey: ["product", id],
queryFn: () => api.getProduct(id),
});
};
+37 -37
View File
@@ -9,7 +9,7 @@ import { ArrowLeft, Clock, Cup, Heart, TruckTick } from 'iconsax-react'
import Image from 'next/image'
import { useParams, useRouter } from 'next/navigation'
import React, { useEffect, useMemo, useState } from 'react'
import { useGetFood, useToggleFavorite } from './hooks/useFoodData'
import { useGetProduct, useToggleFavorite } from './hooks/useProductData'
import { useGetAbout } from '../about/hooks/useAboutData'
import { useGetProfile } from '../../(Profile)/profile/hooks/userProfileData'
import { toast } from '@/components/Toast'
@@ -17,40 +17,40 @@ import { getToken } from '@/lib/api/func'
type Props = object
function FoodPage({ }: Props) {
function ProductPage({ }: Props) {
const { id, name } = useParams();
const router = useRouter();
const { isSuccess } = useGetProfile();
const { data: food, isLoading } = useGetFood(id as string);
const { data: product, isLoading } = useGetProduct(id as string);
const { data: about } = useGetAbout();
const { items, addToCart, removeFromCart } = useCart();
const [isFavorite, setIsFavorite] = useState<boolean>(false);
const { mutate: toggleFavorite } = useToggleFavorite();
const foodId = food?.data?.id || id as string;
const productId = product?.data?.id || id as string;
const quantity = useMemo(() => {
const item = items?.[foodId];
const item = items?.[productId];
if (item && typeof item === 'object' && 'quantity' in item) {
return item.quantity || 0;
}
return 0;
}, [items, foodId]);
}, [items, productId]);
const handleAddToCart = () => {
if (foodId) {
addToCart(foodId);
if (productId) {
addToCart(productId);
}
};
const handleRemoveFromCart = () => {
if (foodId) {
removeFromCart(foodId);
if (productId) {
removeFromCart(productId);
}
};
const handleToggleFavorite = async () => {
if (!foodId) return;
if (!productId) return;
const token = await getToken();
if (!token || !isSuccess) {
@@ -62,7 +62,7 @@ function FoodPage({ }: Props) {
const previousFavorite = isFavorite;
setIsFavorite(!isFavorite);
toggleFavorite(foodId, {
toggleFavorite(productId, {
onSuccess: () => {
// تایید تغییر در صورت موفقیت
},
@@ -75,12 +75,12 @@ function FoodPage({ }: Props) {
};
useEffect(() => {
if (food?.data?.isFavorite) {
if (product?.data?.isFavorite) {
setIsFavorite(true);
} else {
setIsFavorite(false);
}
}, [food?.data?.isFavorite]);
}, [product?.data?.isFavorite]);
if (isLoading) {
return (
@@ -90,7 +90,7 @@ function FoodPage({ }: Props) {
);
}
if (!food?.data) {
if (!product?.data) {
return (
<div className='flex items-center justify-center min-h-[400px]'>
<p className='text-disabled-text'>کالا یافت نشد</p>
@@ -98,27 +98,27 @@ function FoodPage({ }: Props) {
);
}
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]
: typeof foodData.images[0] === 'object' && foodData.images[0] !== null && 'url' in foodData.images[0]
? (foodData.images[0] as { url: string }).url
const productData = product.data;
const productName = productData.title || productData.name || productData.foodName || productData.productName || '';
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 = 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 || '';
const categoryName = productData.category?.title;
const prepareTime = productData.prepareTime || 0;
const price = productData.price || 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') || foodData.category?.id;
const categoryParam = urlParams.get('category') || productData.category?.id;
if (categoryParam) {
router.push(`/${name}?category=${categoryParam}`);
} else {
@@ -131,8 +131,8 @@ function FoodPage({ }: Props) {
<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={foodImage}
alt={foodName}
src={productImage}
alt={productName}
height={100}
width={100}
unoptimized
@@ -151,7 +151,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="w-full inline-flex justify-between items-center">
<h5 className="text-base font-bold">
{foodName}
{productName}
</h5>
<button
onClick={handleToggleFavorite}
@@ -186,7 +186,7 @@ function FoodPage({ }: Props) {
<div>
{ef(
Math.round(
foodData?.price *
productData?.price *
(Number(about.data.score.purchaseScore) / Number(about.data.score.purchaseAmount))
).toLocaleString('en-US')
)}
@@ -201,7 +201,7 @@ function FoodPage({ }: Props) {
<div className='flex items-center gap-2 mt-2'>
<TruckTick size={14} className='stroke-disabled-text' />
<span className='text-disabled-text'>
{foodData.pickupServe ? 'ارسال با پیک' : '-'}
{productData.pickupServe ? 'ارسال با پیک' : '-'}
</span>
</div>
</div>
@@ -259,4 +259,4 @@ function FoodPage({ }: Props) {
)
}
export default FoodPage
export default ProductPage
@@ -1,12 +1,12 @@
import { api } from "@/config/axios";
import { FoodResponse } from "../../types/Types";
import { ProductResponse } from "../../types/Types";
export const getFood = async (id: string): Promise<FoodResponse> => {
const { data } = await api.get<FoodResponse>(`/public/foods/${id}`);
export const getProduct = async (id: string): Promise<ProductResponse> => {
const { data } = await api.get<ProductResponse>(`/public/products/${id}`);
return data;
};
export const toggleFavorite = async (foodId: string) => {
const { data } = await api.post(`/public/foods/favorite/${foodId}`);
export const toggleFavorite = async (productId: string) => {
const { data } = await api.post(`/public/products/favorite/${productId}`);
return data;
};
+25 -25
View File
@@ -27,7 +27,7 @@ function AboutPage() {
const [sorting, setSorting] = useQueryState('sortBy', { defaultValue: '0' });
const { state: sortingModal, toggle: toggleSortingModal, set: setSortingModal } = useToggle();
const restaurant = aboutData?.data;
const shop = aboutData?.data;
const schedules = schedulesData?.data || [];
const isLoading = aboutLoading || reviewsLoading || schedulesLoading;
@@ -76,7 +76,7 @@ function AboutPage() {
};
const firstTab = () => {
if (!restaurant) return null;
if (!shop) return null;
return (
<section aria-labelledby="about-title" className='py-4'>
@@ -84,19 +84,19 @@ function AboutPage() {
className="bg-container rounded-container shadow-container p-4">
<div className="flex justify-between items-center border-b-[1.5px] border-gray-200 pb-[25px]">
<div className="">
<h2 className='text-sm2 font-bold leading-5'>{restaurant.name}</h2>
{restaurant.establishedYear && (
<p className="text-sm2 leading-5 mt-4">تاسیس: {restaurant.establishedYear}</p>
<h2 className='text-sm2 font-bold leading-5'>{shop.name}</h2>
{shop.establishedYear && (
<p className="text-sm2 leading-5 mt-4">تاسیس: {shop.establishedYear}</p>
)}
{restaurant.tagNames && restaurant.tagNames.length > 0 && (
<p className="text-sm2 leading-5 mt-2">نوع محصولات: {restaurant.tagNames.join('، ')}</p>
{shop.tagNames && shop.tagNames.length > 0 && (
<p className="text-sm2 leading-5 mt-2">نوع محصولات: {shop.tagNames.join('، ')}</p>
)}
</div>
{restaurant.logo && (
{shop.logo && (
<div className="rounded-normal overflow-clip">
<Image
alt='logo'
src={restaurant.logo}
src={shop.logo}
width={88}
height={88}
unoptimized
@@ -105,13 +105,13 @@ function AboutPage() {
</div>
)}
</div>
{restaurant.description && (
{shop.description && (
<div className="mt-[23px]">
<h3 className="text-sm2 font-bold leading-5">درباره مجموعه</h3>
<p className="text-sm2 leading-5 mt-3 border-spacing-48" style={{ wordSpacing: '0.01em' }}>
{restaurant.description}
{shop.description}
</p>
{restaurant.images && restaurant.images.length > 0 && (
{shop.images && shop.images.length > 0 && (
<div className='inline-flex gap-2 mt-[23px] items-center'>
<Gallery size={20} className='stroke-disabled-text' />
<span className='text-sm2 text-disabled-text font-medium pt-0.5'>عکس های فروشگاه</span>
@@ -121,28 +121,28 @@ function AboutPage() {
)}
</section>
{(restaurant.phone || restaurant.telegram || restaurant.whatsapp || restaurant.instagram) && (
{(shop.phone || shop.telegram || shop.whatsapp || shop.instagram) && (
<section
className="bg-container rounded-container shadow-container pt-3 pb-3.5 px-[16px] mt-4 grid grid-cols-3 items-center">
<h2 className='text-sm2 font-medium leading-5'>ارتباط</h2>
<div className='col-span-1 text-center flex justify-center gap-4'>
{restaurant.phone &&
<a href={`tel://${restaurant.phone}`} className='bg-[#EAEDF5] dark:bg-neutral-700 p-2 rounded-normal'>
{shop.phone &&
<a href={`tel://${shop.phone}`} className='bg-[#EAEDF5] dark:bg-neutral-700 p-2 rounded-normal'>
<CallCalling className='stroke-foreground' size={24} />
</a>
}
{restaurant.telegram &&
<a href={`https://t.me/${restaurant.telegram}`} className='bg-[#EAEDF5] dark:bg-neutral-700 p-2 rounded-normal'>
{shop.telegram &&
<a href={`https://t.me/${shop.telegram}`} className='bg-[#EAEDF5] dark:bg-neutral-700 p-2 rounded-normal'>
<TelegramIcon className='stroke-foreground' width={24} height={24} />
</a>
}
{restaurant.whatsapp &&
<a href={`https://whatsapp.com/?phone=${restaurant.whatsapp}`} className='bg-[#EAEDF5] dark:bg-neutral-700 p-2 rounded-normal'>
{shop.whatsapp &&
<a href={`https://whatsapp.com/?phone=${shop.whatsapp}`} className='bg-[#EAEDF5] dark:bg-neutral-700 p-2 rounded-normal'>
<Whatsapp className='stroke-foreground' size={24} />
</a>
}
{restaurant.instagram &&
<a href={`https://instagram.com/${restaurant.instagram}`} className='bg-[#EAEDF5] dark:bg-neutral-700 p-2 rounded-normal'>
{shop.instagram &&
<a href={`https://instagram.com/${shop.instagram}`} className='bg-[#EAEDF5] dark:bg-neutral-700 p-2 rounded-normal'>
<Instagram className='stroke-foreground' size={24} />
</a>
}
@@ -150,11 +150,11 @@ function AboutPage() {
</section>
)}
{restaurant.address && (
{shop.address && (
<section className="bg-container rounded-container shadow-container px-4 pt-6 pb-6 mt-4">
<h2 className='text-sm2 font-medium leading-5'>آدرس</h2>
<p className='text-sm2 mt-[9px] leading-5'>{restaurant.address}</p>
{restaurant.latitude && restaurant.longitude && (
<p className='text-sm2 mt-[9px] leading-5'>{shop.address}</p>
{shop.latitude && shop.longitude && (
<div className='inline-flex gap-2 mt-[23px] items-center '>
<Location size={20} className='stroke-disabled-text' />
<span className='text-sm2 text-disabled-text '>موقعیت روی نقشه</span>
@@ -285,7 +285,7 @@ function AboutPage() {
rating={review.rating}
date={formatDate(review.createdAt)}
text={review.comment}
tags={[review.food.title]}
tags={[review.food?.title ?? '']}
positivePoints={review.positivePoints}
negativePoints={review.negativePoints}
/>
@@ -6,13 +6,13 @@ import {
} from "../types/Types";
export const getAbout = async (name: string): Promise<AboutResponse> => {
const { data } = await api.get<AboutResponse>(`/public/restaurants/${name}`);
const { data } = await api.get<AboutResponse>(`/public/shops/${name}`);
return data;
};
export const getReviews = async (name: string): Promise<ReviewsResponse> => {
const { data } = await api.get<ReviewsResponse>(
`/public/reviews/restuarant/${name}`
`/public/reviews/shop/${name}`
);
return data;
};
@@ -21,7 +21,7 @@ export const getSchedules = async (
name: string
): Promise<SchedulesResponse> => {
const { data } = await api.get<SchedulesResponse>(
`/public/schedules/restaurant/${name}`
`/public/schedules/shop/${name}`
);
return data;
};
+5 -4
View File
@@ -16,7 +16,7 @@ export interface Score {
marriageDateScore: string;
}
export interface Restaurant {
export interface Shop {
id: string;
createdAt: string;
updatedAt: string;
@@ -47,7 +47,7 @@ export interface Restaurant {
plan: 'base' | 'premium';
}
export interface ReviewFood {
export interface ReviewProduct {
id: string;
createdAt: string;
updatedAt: string;
@@ -101,7 +101,8 @@ export interface Review {
updatedAt: string;
deletedAt: string | null;
order: string;
food: ReviewFood;
/** API returns "food" key */
food: ReviewProduct;
user: ReviewUser;
comment: string;
rating: number;
@@ -122,6 +123,6 @@ export interface Schedule {
restId: string;
}
export type AboutResponse = BaseResponse<Restaurant>;
export type AboutResponse = BaseResponse<Shop>;
export type ReviewsResponse = BaseResponse<Review[]>;
export type SchedulesResponse = BaseResponse<Schedule[]>;
+2 -2
View File
@@ -2,11 +2,11 @@ import { useQuery } from "@tanstack/react-query";
import * as api from "../service/MenuService";
import { useParams } from "next/navigation";
export const useGetFoods = () => {
export const useGetProducts = () => {
const { name } = useParams<{ name: string }>();
return useQuery({
queryKey: ["menu"],
queryFn: () => api.getFoods(name),
queryFn: () => api.getProducts(name),
enabled: !!name,
});
};
+13 -13
View File
@@ -15,18 +15,18 @@ import CategoryScroll from "@/app/[name]/(Main)/components/CategoryScroll";
import MenuFilterDrawer from "@/app/[name]/(Main)/components/MenuFilterDrawer";
import MenuSortingDrawer from "@/app/[name]/(Main)/components/MenuSortingDrawer";
import MenuSkeleton from "@/app/[name]/(Main)/components/MenuSkeleton";
import { useGetCategories, useGetFoods } from "./hooks/useMenuData";
import type { Food, Category } from "./types/Types";
import { useGetCategories, useGetProducts } from "./hooks/useMenuData";
import type { Product, Category } from "./types/Types";
const sortings = ["PopularityDescending", "RateDescending", "PriceAscending", "PriceDescending"] as const;
const MenuIndex = () => {
const { data: foodsData, isLoading: foodsLoading } = useGetFoods();
const { data: productsData, isLoading: productsLoading } = useGetProducts();
const { data: categoriesData, isLoading: categoriesLoading } = useGetCategories();
const foods = useMemo<Food[]>(() => foodsData?.data || [], [foodsData?.data]);
const products = useMemo<Product[]>(() => productsData?.data || [], [productsData?.data]);
const categories = useMemo<Category[]>(() => categoriesData?.data || [], [categoriesData?.data]);
const isLoading = foodsLoading || categoriesLoading;
const isLoading = productsLoading || categoriesLoading;
const { t: tCommon } = useTranslation('common');
const { t: tMenu } = useTranslation('menu', { keyPrefix: "Menu" });
const { state: filterModal, toggle: toggleFilterModal } = useToggle();
@@ -104,14 +104,14 @@ const MenuIndex = () => {
}, [setSelectedDeliveryId]);
const filteredReceiptItems = useMemo(() => {
if (!foods.length) return [];
if (!products.length) return [];
const lowerSearch = search.toLowerCase();
const lowerIngredients = selectedIngredients ? selectedIngredients.toLowerCase() : "";
const selectedCatId = selectedCategory === '0' ? null : selectedCategory;
const filtered = foods.filter((item) => {
const filtered = products.filter((item) => {
const matchesCategory = !selectedCatId || item.category?.id === selectedCatId;
const itemName = item.name || item.title || item.foodName || "";
const itemName = item.name || item.title || item.foodName || item.productName || "";
const matchesSearch =
!search || itemName.toLowerCase().includes(lowerSearch);
@@ -151,7 +151,7 @@ const MenuIndex = () => {
return 0;
}
});
}, [selectedCategory, search, selectedIngredients, selectedDeliveryId, foods, sorting]);
}, [selectedCategory, search, selectedIngredients, selectedDeliveryId, products, sorting]);
if (isLoading) {
return <MenuSkeleton />;
@@ -207,12 +207,12 @@ const MenuIndex = () => {
<VerticalScrollView className="mt-5! overflow-y-auto h-full">
{filteredReceiptItems.length === 0 ? (
<div className="text-center text-foreground/60 py-8">
{foodsData ? 'کالایی یافت نشد' : 'در حال بارگذاری...'}
{productsData ? 'کالایی یافت نشد' : 'در حال بارگذاری...'}
</div>
) : (
filteredReceiptItems.map((food) => (
<MenuItemRenderer key={food.id}>
<MenuItem food={food} />
filteredReceiptItems.map((product) => (
<MenuItemRenderer key={product.id}>
<MenuItem product={product} />
</MenuItemRenderer>
))
)}
+5 -5
View File
@@ -1,13 +1,13 @@
import { api } from "@/config/axios";
import {
CategoriesResponse,
FoodsResponse,
ProductsResponse,
NotificationsCountResponse,
} from "../types/Types";
export const getFoods = async (slug: string): Promise<FoodsResponse> => {
const response = await api.get<FoodsResponse>(
`/public/foods/restaurant/${slug}`
export const getProducts = async (slug: string): Promise<ProductsResponse> => {
const response = await api.get<ProductsResponse>(
`/public/products/shop/${slug}`
);
return response.data;
};
@@ -16,7 +16,7 @@ export const getCategories = async (
slug: string
): Promise<CategoriesResponse> => {
const response = await api.get<CategoriesResponse>(
`/public/categories/restaurant/${slug}`
`/public/categories/shop/${slug}`
);
return response.data;
};
+8 -7
View File
@@ -18,9 +18,9 @@ export interface Category {
export type CategoriesResponse = BaseResponse<Category[]>;
export type FoodImage = string | { url: string; alt?: string | null };
export type ProductImage = string | { url: string; alt?: string | null };
export interface RestaurantRef {
export interface ShopRef {
id: string;
}
@@ -30,12 +30,12 @@ export interface InventoryRef {
export type MealType = "breakfast" | "lunch" | "dinner" | "snack";
export interface Food {
export interface Product {
id: string;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
restaurant: RestaurantRef;
restaurant: ShopRef;
category: Category;
inventory: InventoryRef;
title: string;
@@ -56,9 +56,10 @@ export interface Food {
reviews: unknown[];
favorites: unknown[];
isFavorite: boolean;
// فیلدهای قدیمی برای سازگاری با سایر بخش‌های کد
// فیلدهای قدیمی برای سازگاری با API
name?: string;
foodName?: string;
productName?: string;
description?: string;
image?: string | null;
points?: number | null;
@@ -74,8 +75,8 @@ export interface Food {
[key: string]: unknown;
}
export type FoodsResponse = BaseResponse<Food[]>;
export type FoodResponse = BaseResponse<Food>;
export type ProductsResponse = BaseResponse<Product[]>;
export type ProductResponse = BaseResponse<Product>;
export interface NotificationsCountData {
count: number;
+34 -34
View File
@@ -8,7 +8,7 @@ import MenuItem from '@/components/listview/MenuItem'
import MenuItemRenderer from '@/components/listview/MenuItemRenderer'
import VerticalScrollView from '@/components/listview/VerticalScrollView'
import type { Favorite } from './types/Types'
import type { Food } from '@/app/[name]/(Main)/types/Types'
import type { Product } from '@/app/[name]/(Main)/types/Types'
function FavoritePage() {
const router = useRouter()
@@ -18,40 +18,40 @@ function FavoritePage() {
return favoritesData?.data || []
}, [favoritesData?.data])
const foodItems = useMemo(() => {
const productItems = useMemo(() => {
return favorites.map((favorite: Favorite) => {
const favoriteFood = favorite.food
// تبدیل FavoriteFood به Food برای استفاده در MenuItem
const food: Food = {
id: favoriteFood.id,
createdAt: favoriteFood.createdAt,
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,
desc: favoriteFood.desc,
content: favoriteFood.content,
price: favoriteFood.price,
order: favoriteFood.order,
prepareTime: favoriteFood.prepareTime,
weekDays: favoriteFood.weekDays,
mealTypes: favoriteFood.mealTypes,
isActive: favoriteFood.isActive,
images: favoriteFood.images,
inPlaceServe: favoriteFood.inPlaceServe,
pickupServe: favoriteFood.pickupServe,
score: favoriteFood.score,
discount: favoriteFood.discount,
isSpecialOffer: favoriteFood.isSpecialOffer,
const favProduct = favorite.food
// تبدیل FavoriteProduct به Product برای استفاده در MenuItem
const product: Product = {
id: favProduct.id,
createdAt: favProduct.createdAt,
updatedAt: favProduct.updatedAt,
deletedAt: favProduct.deletedAt,
restaurant: favProduct.restaurant as unknown as Product['restaurant'],
category: favProduct.category as unknown as Product['category'],
inventory: {} as Product['inventory'],
title: favProduct.title,
desc: favProduct.desc,
content: favProduct.content,
price: favProduct.price,
order: favProduct.order,
prepareTime: favProduct.prepareTime,
weekDays: favProduct.weekDays,
mealTypes: favProduct.mealTypes,
isActive: favProduct.isActive,
images: favProduct.images,
inPlaceServe: favProduct.inPlaceServe,
pickupServe: favProduct.pickupServe,
score: favProduct.score,
discount: favProduct.discount,
isSpecialOffer: favProduct.isSpecialOffer,
reviews: [],
favorites: [],
isFavorite: true,
name: favoriteFood.title,
description: favoriteFood.desc,
name: favProduct.title,
description: favProduct.desc,
}
return food
return product
})
}, [favorites])
@@ -74,7 +74,7 @@ function FavoritePage() {
<span className='h-4 w-4 animate-spin rounded-full border border-dashed border-foreground border-t-transparent'></span>
<p>در حال دریافت علاقهمندیها...</p>
</div>
) : foodItems.length === 0 ? (
) : productItems.length === 0 ? (
<div className='flex flex-col items-center justify-center gap-4 h-full'>
<Image
src='/assets/images/favorite.png'
@@ -92,9 +92,9 @@ function FavoritePage() {
</div>
) : (
<VerticalScrollView className="overflow-y-auto h-full">
{foodItems.map((food) => (
<MenuItemRenderer key={food.id}>
<MenuItem food={food} />
{productItems.map((product) => (
<MenuItemRenderer key={product.id}>
<MenuItem product={product} />
</MenuItemRenderer>
))}
</VerticalScrollView>
@@ -2,6 +2,6 @@ import { api } from "@/config/axios";
import { FavoritesResponse } from "../types/Types";
export const getFavorites = async (): Promise<FavoritesResponse> => {
const { data } = await api.get<FavoritesResponse>("/public/foods/favorite");
const { data } = await api.get<FavoritesResponse>("/public/products/favorite");
return data;
};
@@ -2,7 +2,7 @@ import { BaseResponse } from "@/app/[name]/(Main)/types/Types";
export type MealType = "breakfast" | "lunch" | "dinner" | "snack";
export interface FavoriteFood {
export interface FavoriteProduct {
id: string;
createdAt: string;
updatedAt: string;
@@ -32,7 +32,8 @@ export interface Favorite {
updatedAt: string;
deletedAt: string | null;
user: string;
food: FavoriteFood;
/** API returns "food" key */
food: FavoriteProduct;
}
export type FavoritesResponse = BaseResponse<Favorite[]>;
@@ -40,7 +40,7 @@ function OrderTrackingPage() {
const [mapInstance, setMapInstance] = useState<L.Map | null>(null);
const [isSearchOpen, setIsSearchOpen] = useState(false);
const restaurant = aboutData?.data;
const shop = aboutData?.data;
const [mapCenter, setMapCenter] = useState<[number, number]>(() => [
Number(params?.get('lat')) || 35.6892,
@@ -60,10 +60,10 @@ function OrderTrackingPage() {
useEffect(() => {
if (editMode && address) {
setMapCenter([address.latitude, address.longitude]);
} else if (restaurant?.latitude && restaurant?.longitude) {
setMapCenter([restaurant.latitude, restaurant.longitude]);
} else if (shop?.latitude && shop?.longitude) {
setMapCenter([shop.latitude, shop.longitude]);
}
}, [editMode, address, restaurant]);
}, [editMode, address, shop]);
useEffect(() => {
if (editMode && address && !initialPositionSet) {
@@ -87,24 +87,24 @@ function OrderTrackingPage() {
const iconHtml = `
<div style="display: flex; flex-direction: column; align-items: center; text-align: center;">
<img src="/icons/restaurant-marker.svg" alt="موقعیت فروشگاه" style="width: 32px; height: 40px; display: block;" />
<img src="/icons/shop-marker.svg" alt="موقعیت فروشگاه" style="width: 32px; height: 40px; display: block;" />
</div>
`;
const restaurantPosition: [number, number] | null =
restaurant?.latitude && restaurant?.longitude
? [restaurant.latitude, restaurant.longitude]
const shopPosition: [number, number] | null =
shop?.latitude && shop?.longitude
? [shop.latitude, shop.longitude]
: null;
const markersToSet: MarkerData[] = [];
if (restaurantPosition) {
if (shopPosition) {
markersToSet.push({
position: restaurantPosition,
position: shopPosition,
title: 'موقعیت فروشگاه',
icon: L.divIcon({
html: iconHtml,
className: 'custom-restaurant-marker',
className: 'custom-shop-marker',
iconSize: [40, 40],
iconAnchor: [20, 40],
popupAnchor: [0, -40],
@@ -116,7 +116,7 @@ function OrderTrackingPage() {
};
initializeMarkers();
}, [restaurant]);
}, [shop]);
const handlePositionSelect = (position: [number, number]) => {
setSelectedPosition((prev) => {
@@ -237,13 +237,13 @@ function OrderTrackingPage() {
);
}
if (!restaurant) {
if (!shop) {
return null
}
const hasServiceArea = restaurant?.serviceArea &&
restaurant.serviceArea.coordinates &&
restaurant.serviceArea.coordinates.length > 0;
const hasServiceArea = shop?.serviceArea &&
shop.serviceArea.coordinates &&
shop.serviceArea.coordinates.length > 0;
return (
<div className="fixed inset-0 bg-gray-50">
@@ -252,7 +252,7 @@ function OrderTrackingPage() {
initialPosition={mapCenter}
zoom={14}
markers={markers}
serviceArea={restaurant?.serviceArea || null}
serviceArea={shop?.serviceArea || null}
onPositionSelect={handlePositionSelect}
onZoomChange={handleZoomChange}
onClick={handleMapClick}
+9 -9
View File
@@ -1,7 +1,7 @@
import PreferenceWrapper from '@/components/wrapper/PreferenceWrapper'
import React from 'react'
import type { Metadata, Viewport } from 'next'
import { getRestaurant } from './lib/getRestaurant'
import { getShop } from './lib/getShop'
import { notFound } from 'next/navigation'
import CartChecker from '@/components/CartChecker'
import ActiveChecker from '@/components/ActiveChecker'
@@ -18,10 +18,10 @@ export async function generateMetadata({
}): Promise<Metadata> {
const { name } = await params
try {
const restaurant = await getRestaurant(name)
const shop = await getShop(name)
const title = restaurant.seoTitle || restaurant.name
const logo = restaurant.logo
const title = shop.seoTitle || shop.name
const logo = shop.logo
const defaultIcon = "/icons/web-app-manifest-192x192.png"
@@ -38,7 +38,7 @@ export async function generateMetadata({
return {
title,
description: restaurant.seoDescription || restaurant.description || `منوی ${restaurant.name}`,
description: shop.seoDescription || shop.description || `فروشگاه ${shop.name}`,
manifest: `/${name}/manifest.webmanifest`,
icons: {
icon: [
@@ -75,10 +75,10 @@ export async function generateViewport({
}): Promise<Viewport> {
try {
const { name } = await params
const restaurant = await getRestaurant(name)
const shop = await getShop(name)
return {
themeColor: restaurant.menuColor || '#F4F5F9'
themeColor: shop.menuColor || '#F4F5F9'
}
} catch {
return {
@@ -96,14 +96,14 @@ export default async function Layout({
}): Promise<React.ReactNode> {
try {
const { name } = await params
await getRestaurant(name)
await getShop(name)
return <>
<PreferenceWrapper>{children}</PreferenceWrapper>
<CartChecker />
<ActiveChecker />
</>
} catch (error) {
if (error instanceof Error && error.message === 'RESTAURANT_NOT_FOUND') {
if (error instanceof Error && error.message === 'SHOP_NOT_FOUND') {
notFound()
}
throw error
@@ -1,10 +1,10 @@
import { API_BASE_URL } from "@/config/const";
import { AboutResponse, Restaurant } from "../(Main)/about/types/Types";
import { AboutResponse, Shop } from "../(Main)/about/types/Types";
import axios from "axios";
export async function getRestaurant(slug: string): Promise<Restaurant> {
export async function getShop(slug: string): Promise<Shop> {
try {
const response = await axios(`${API_BASE_URL}/public/restaurants/${slug}`, {
const response = await axios(`${API_BASE_URL}/public/shops/${slug}`, {
// cache: "no-store",
headers: {
@@ -17,24 +17,24 @@ export async function getRestaurant(slug: string): Promise<Restaurant> {
if (!response.data) {
if (response.status === 404) {
throw new Error("RESTAURANT_NOT_FOUND");
throw new Error("SHOP_NOT_FOUND");
}
throw new Error(`Failed to fetch restaurant: ${response.status}`);
throw new Error(`Failed to fetch shop: ${response.status}`);
}
const data: AboutResponse = await response.data;
if (!data.success || !data.data) {
throw new Error("RESTAURANT_NOT_FOUND");
throw new Error("SHOP_NOT_FOUND");
}
return data.data;
} catch (error) {
console.log("error", error);
if (error instanceof Error && error.message === "RESTAURANT_NOT_FOUND") {
if (error instanceof Error && error.message === "SHOP_NOT_FOUND") {
throw error;
}
throw new Error(`Failed to fetch restaurant data: ${error}`);
throw new Error(`Failed to fetch shop data: ${error}`);
}
}
+9 -9
View File
@@ -1,5 +1,5 @@
import { NextResponse } from "next/server";
import { getRestaurant } from "../lib/getRestaurant";
import { getShop } from "../lib/getShop";
export const dynamic = "force-dynamic";
export const revalidate = 0;
@@ -10,10 +10,10 @@ export async function GET(
) {
try {
const { name } = await params;
const restaurant = await getRestaurant(name);
const shop = await getShop(name);
const themeColor = restaurant.menuColor || "#F4F5F9";
const logo = restaurant.logo;
const themeColor = shop.menuColor || "#F4F5F9";
const logo = shop.logo;
const icons = [];
if (logo && logo.trim() !== "") {
@@ -53,9 +53,9 @@ export async function GET(
}
const manifest = {
name: restaurant.name,
short_name: restaurant.name,
description: restaurant.description || `منوی ${restaurant.name}`,
name: shop.name,
short_name: shop.name,
description: shop.description || `فروشگاه ${shop.name}`,
start_url: `/${name}`,
scope: `/${name}`,
id: `/${name}`,
@@ -79,8 +79,8 @@ export async function GET(
},
});
} catch (error) {
if (error instanceof Error && error.message === "RESTAURANT_NOT_FOUND") {
return new NextResponse("Restaurant not found", { status: 404 });
if (error instanceof Error && error.message === "SHOP_NOT_FOUND") {
return new NextResponse("Shop not found", { status: 404 });
}
return new NextResponse("Internal Server Error", { status: 500 });
+35 -35
View File
@@ -8,44 +8,44 @@ import PlusIcon from "@/components/icons/PlusIcon";
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";
import type { Product, ProductImage } from "@/app/[name]/(Main)/types/Types";
interface MenuItemProps {
food: Food;
product: Product;
}
const MenuItem = ({ food }: MenuItemProps) => {
const MenuItem = ({ product }: MenuItemProps) => {
const { items, addToCart, removeFromCart } = useCart();
const params = useParams<{ name: string }>();
const name = params?.name || '';
const quantity = useMemo(() => {
const item = items?.[food.id];
const item = items?.[product.id];
if (item && typeof item === 'object' && 'quantity' in item) {
return item.quantity || 0;
}
return 0;
}, [items, food.id]);
}, [items, product.id]);
const fallbackImage = "/assets/images/no-image.png";
const resolvedImage = useMemo(() => {
if (food.image) return food.image;
if (Array.isArray(food.images) && food.images.length > 0) {
const [firstImage] = food.images;
if (product.image) return product.image;
if (Array.isArray(product.images) && product.images.length > 0) {
const [firstImage] = product.images;
if (typeof firstImage === "string") return firstImage;
const imageObj = firstImage as FoodImage;
const imageObj = firstImage as ProductImage;
return (typeof imageObj === "object" && imageObj?.url) || fallbackImage;
}
return fallbackImage;
}, [food.image, food.images]);
}, [product.image, product.images]);
const foodName = useMemo(
() => food.name || food.title || food.foodName || 'بدون نام',
[food.name, food.title, food.foodName]
const productName = useMemo(
() => product.name || product.title || product.foodName || product.productName || 'بدون نام',
[product.name, product.title, product.foodName, product.productName]
);
const foodContent = useMemo(() => {
const content = food.content;
const productContent = useMemo(() => {
const content = product.content;
if (!content) return '';
if (Array.isArray(content)) {
@@ -53,22 +53,22 @@ const MenuItem = ({ food }: MenuItemProps) => {
}
return '';
}, [food.content]);
}, [product.content]);
const foodDescription = useMemo(() => {
const desc = food.description || food.desc;
const productDescription = useMemo(() => {
const desc = product.description || product.desc;
if (!desc || typeof desc !== 'string') return '';
return desc.replace(/,/g, '،');
}, [food.description, food.desc]);
}, [product.description, product.desc]);
const finalPrice = useMemo(() => {
const basePrice = food.price || 0;
const discount = food.discount || 0;
const basePrice = product.price || 0;
const discount = product.discount || 0;
if (discount > 0) {
return Math.max(0, basePrice - discount);
}
return basePrice;
}, [food.price, food.discount]);
}, [product.price, product.discount]);
const formattedPrice = useMemo(
() => (finalPrice ? finalPrice.toLocaleString('fa-IR') : '0'),
@@ -76,45 +76,45 @@ const MenuItem = ({ food }: MenuItemProps) => {
);
const formattedOriginalPrice = useMemo(
() => (food.price ? food.price.toLocaleString('fa-IR') : '0'),
[food.price]
() => (product.price ? product.price.toLocaleString('fa-IR') : '0'),
[product.price]
);
const hasDiscount = useMemo(
() => (food.discount || 0) > 0,
[food.discount]
() => (product.discount || 0) > 0,
[product.discount]
);
const handleAddToCart = () => {
addToCart(food.id);
addToCart(product.id);
};
const handleRemoveFromCart = () => {
removeFromCart(food.id);
removeFromCart(product.id);
};
const foodDetailUrl = `/${name}/${food.id}?category=${food.category.id}`;
const productDetailUrl = `/${name}/${product.id}?category=${product.category.id}`;
return (
<div className="flex gap-4 w-full h-full items-center">
<Link href={foodDetailUrl} className="cursor-pointer">
<Link href={productDetailUrl} className="cursor-pointer">
<img
className="rounded-xl bg-[#F2F2F9] min-w-28 w-28 object-cover"
src={resolvedImage}
height={112}
width={112}
alt={foodName}
alt={productName}
/>
</Link>
<div className="w-full inline-flex flex-col justify-between min-w-0">
<Link href={foodDetailUrl} className="cursor-pointer min-w-0">
<Link href={productDetailUrl} className="cursor-pointer min-w-0">
<div className="min-w-0">
<div className="text-sm2 font-normal text-black dark:text-white wrap-break-word">
{foodName}
{productName}
</div>
{(foodContent || foodDescription) && (
{(productContent || productDescription) && (
<div className="text-[#7F7F7F] line-clamp-2 text-xs leading-5 font-normal mt-2 wrap-break-word overflow-hidden">
{foodContent || foodDescription}
{productContent || productDescription}
</div>
)}
</div>
+7 -7
View File
@@ -77,31 +77,31 @@
opacity: 0.8;
}
/* Custom restaurant marker styles */
.map-google-style .custom-restaurant-marker {
/* Custom shop marker styles */
.map-google-style .custom-shop-marker {
background: transparent !important;
border: none !important;
}
.map-google-style .custom-restaurant-marker img {
.map-google-style .custom-shop-marker img {
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2));
}
/* Restaurant popup smaller size */
.map-google-style .restaurant-popup .leaflet-popup-content-wrapper {
/* Shop popup smaller size */
.map-google-style .shop-popup .leaflet-popup-content-wrapper {
padding: 8px 25px 8px 12px;
position: relative;
min-height: 32px;
}
.map-google-style .restaurant-popup .leaflet-popup-content {
.map-google-style .shop-popup .leaflet-popup-content {
margin: 0;
font-size: 12px;
line-height: 1.4;
text-align: right;
}
.map-google-style .restaurant-popup .leaflet-popup-close-button {
.map-google-style .shop-popup .leaflet-popup-close-button {
position: absolute !important;
top: 4px !important;
right: 4px !important;
+2 -2
View File
@@ -86,7 +86,7 @@ function MarkerUpdater({ markers }: { markers?: CustomMapProps['markers'] }) {
return null;
}
// Component to auto-open popup for restaurant marker
// Component to auto-open popup for shop marker
function MarkerWithAutoOpen({ position, icon, title }: { position: [number, number]; icon?: L.Icon | L.DivIcon; title: string }) {
const markerRef = React.useRef<L.Marker>(null);
@@ -98,7 +98,7 @@ function MarkerWithAutoOpen({ position, icon, title }: { position: [number, numb
return (
<Marker ref={markerRef} position={position} icon={icon}>
<Popup className="restaurant-popup">
<Popup className="shop-popup">
<div className="text-xs font-medium">{title}</div>
</Popup>
</Marker>
+5 -5
View File
@@ -6,13 +6,13 @@ import Image from 'next/image';
interface SplashScreenProps {
logo?: string;
restaurantName?: string;
shopName?: string;
duration?: number;
}
export default function SplashScreen({
logo,
restaurantName,
shopName,
duration = 2000
}: SplashScreenProps) {
const [isVisible, setIsVisible] = useState(true);
@@ -83,7 +83,7 @@ export default function SplashScreen({
<div>
<Image
src={logo}
alt={restaurantName || 'Restaurant Logo'}
alt={shopName || 'Shop Logo'}
width={200}
height={100}
priority
@@ -113,14 +113,14 @@ export default function SplashScreen({
</motion.div>
)}
{/*
{restaurantName && (
{shopName && (
<motion.h1
initial={{ y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: 0.3, duration: 0.5 }}
className="text-3xl font-bold text-foreground text-center"
>
{restaurantName}
{shopName}
</motion.h1>
)} */}
+5 -5
View File
@@ -48,8 +48,8 @@ function PreferenceWrapper({ children }: Props) {
}
// بارگذاری لوگو و نام
const savedLogo = localStorage.getItem('restaurant-logo');
const savedName = localStorage.getItem('restaurant-name');
const savedLogo = localStorage.getItem('shop-logo');
const savedName = localStorage.getItem('shop-name');
if (savedLogo) setCachedLogo(savedLogo);
if (savedName) setCachedName(savedName);
@@ -65,10 +65,10 @@ function PreferenceWrapper({ children }: Props) {
// ذخیره لوگو و نام برای اسپلش
if (aboutData.data.logo) {
localStorage.setItem('restaurant-logo', aboutData.data.logo);
localStorage.setItem('shop-logo', aboutData.data.logo);
}
if (aboutData.data.name) {
localStorage.setItem('restaurant-name', aboutData.data.name);
localStorage.setItem('shop-name', aboutData.data.name);
}
applyPrimaryColor(aboutData.data.menuColor);
@@ -102,7 +102,7 @@ function PreferenceWrapper({ children }: Props) {
{showSplash && (
<SplashScreen
logo={splashLogo}
restaurantName={splashName}
shopName={splashName}
duration={2500}
/>
)}
+3 -2
View File
@@ -1,5 +1,6 @@
// export const API_BASE_URL = "https://dmenuplus-api.dev.danakcorp.com";
export const API_BASE_URL =
"https://dmenuplus-api-production.dev.danakcorp.com";
// export const API_BASE_URL =
// "https://dmenuplus-api-production.dev.danakcorp.com";
export const API_BASE_URL = "http://192.168.99.209:4000";
export const TOKEN_NAME = "dmenu-t";
export const REFRESH_TOKEN_NAME = "dmenu-rt";