import ImageWithFallback from "components/ImageWithFallback"; import { ItemDescriptionIcons } from "components/ItemDescriptionIcons/ItemDescriptionIcons"; import ProText from "components/ProText"; import { useAppSelector } from "redux/hooks"; import { default_image } from "utils/constants"; // import PageTransition from "components/PageTransition/PageTransition"; import { Divider, Space } from "antd"; import ArabicPrice from "components/ArabicPrice"; import useBreakPoint from "hooks/useBreakPoint"; import ExtraGroupsContainer from "pages/product/components/ExtraGroupsContainer.tsx"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useParams } from "react-router-dom"; import { useGetMenuQuery } from "redux/api/others.ts"; import { Extra, Product } from "utils/types/appTypes"; import BackButton from "../menu/components/BackButton"; import ExtraComponent from "./components/Extra"; import ProductFooter from "./components/ProductFooter"; import Variants from "./components/Variants"; import styles from "./product.module.css"; export default function ProductDetailPage({ onClose, }: { onClose?: () => void; }) { const { productId } = useParams(); const { isRTL } = useAppSelector((state) => state.locale); const { restaurant } = useAppSelector((state) => state.order); const { isDesktop, isTablet } = useBreakPoint(); const [quantity, setQuantity] = useState(1); const [viewportHeight, setViewportHeight] = useState( typeof window !== "undefined" ? window.innerHeight : 0, ); const isBottomSheetView = useMemo(() => { return window.location.href.includes("menu"); }, []); const currenctProductId = useMemo(() => { // in case the prduct viewing from the bottom sheet, the product id is not passed in the url return productId || localStorage.getItem("productId"); }, [localStorage.getItem("productId")]); // Update viewport height when browser UI changes (mobile address bar, etc.) useEffect(() => { const updateViewportHeight = () => { setViewportHeight(window.innerHeight); }; // Set initial height updateViewportHeight(); // Update on resize and orientation change window.addEventListener("resize", updateViewportHeight); window.addEventListener("orientationchange", updateViewportHeight); // Also listen to visual viewport changes for mobile browsers if (window.visualViewport) { window.visualViewport.addEventListener("resize", updateViewportHeight); } return () => { window.removeEventListener("resize", updateViewportHeight); window.removeEventListener("orientationchange", updateViewportHeight); if (window.visualViewport) { window.visualViewport.removeEventListener( "resize", updateViewportHeight, ); } }; }, []); // Get menu data const { data: menuData } = useGetMenuQuery(restaurant?.restautantId, { skip: !restaurant?.restautantId, }); // Find product from menu data const product: Product = useMemo(() => { // we still need to support the old way of passing the product id in local storage // because in desktop we open the product dialog from the menu list // and the product id is not passed in the url const productIdLocalStorage = localStorage.getItem("productId"); if (!menuData?.products || (!currenctProductId && !productIdLocalStorage)) return null; // Find the product with matching IDs return menuData.products.find( (p: Product) => p.id.toString() === (currenctProductId || productIdLocalStorage), ); }, [menuData, currenctProductId]); // State for variant selections const [selectedVariants, setSelectedVariants] = useState< Record >({}); // State for selected extras const [selectedExtras, setSelectedExtras] = useState([]); // State for selected extras by group const [selectedExtrasByGroup, setSelectedExtrasByGroup] = useState< Record> >([]); // Determine variant levels based on options array length const variantLevels = useMemo(() => { if (!product?.variants || product.variants.length === 0) return []; const maxOptionsLength = Math.max( ...product.variants.map((v) => v.options.length), ); const levels: Array<{ level: number; optionKey: string; optionKeyAR: string; availableValues: string[]; }> = []; for (let i = 0; i < maxOptionsLength; i++) { const optionKey = product.variants[0]?.options[i]?.option || ""; const optionKeyAR = product.variants[0]?.optionsAR?.[i]?.option || ""; if (optionKey) { const availableValues = [ ...new Set( product.variants .filter((v) => v.options[i]?.option === optionKey) .map((v) => v.options[i]?.value) .filter(Boolean), ), ]; levels.push({ level: i, // Use the actual array index as level number optionKey, optionKeyAR, availableValues, }); } } return levels; }, [product?.variants]); // Get the final selected variant ID (the variant that matches all current selections) const getFinalSelectedVariant = useCallback(() => { if (!product?.variants || Object.keys(selectedVariants).length === 0) return undefined; // Find the variant that matches all current selections // Convert to string only if defined, otherwise return empty string return product.variants.find((variant) => { return Object.entries(selectedVariants).every(([level, value]) => { const levelNum = parseInt(level); return variant.options[levelNum]?.value === value; }); }); }, [product?.variants, selectedVariants]); const getExtras = useCallback(() => { /* const finalSelectedVariant = getFinalSelectedVariant(); if (!finalSelectedVariant) return []; */ /* const selectedVariant = product?.variants?.find( (variant) => variant.id === finalSelectedVariant.id, ); */ //don't show v if (product?.variants && product.variants.length > 0) return []; return product?.extras || []; }, [product?.variants, getFinalSelectedVariant]); // Validation function to check if all required selections are made const validateRequiredSelections = () => { // Check if all variant levels are selected const allVariantsSelected = variantLevels.every( (level) => selectedVariants[level.level] !== undefined, ); // Check if all required extra groups are satisfied const allRequiredExtraGroupsSatisfied = product?.theExtrasGroups.every( (group) => { if (group.force_limit_selection === 1) { const selectedCount = selectedExtrasByGroup[group.id]?.length || 0; return selectedCount === group.limit; } return true; // Optional groups are always satisfied }, ); return allVariantsSelected && allRequiredExtraGroupsSatisfied; }; const isValid = validateRequiredSelections(); // Check if product has any customization options const hasCustomizationOptions = useMemo(() => { const hasVariants = product?.variants?.length > 0 && variantLevels.length > 0; const hasExtras = getExtras()?.length > 0; const hasExtraGroups = product?.theExtrasGroups?.length > 0; return hasVariants || hasExtras || hasExtraGroups; }, [ product?.variants, product?.theExtrasGroups, variantLevels.length, getExtras, ]); if (!product) { return (
Product not found
); } console.log(product.theExtrasGroups); return (
0 ? `${viewportHeight - 195}px` : "calc(100dvh - 195px)", overflow: "auto", scrollbarWidth: "none", }} > {!isDesktop && !isBottomSheetView && (
)}
{/* Left Column - Product Info & Image */}
{isRTL ? product?.nameOther : product?.name} {product?.description && ( {isRTL ? product?.descriptionAR : product?.description} )}
{!hasCustomizationOptions && ( )}
{isDesktop && ( setQuantity(quantity)} /> )}
{/* Right Column - Variants, Extras, and Extra Groups */} {hasCustomizationOptions && (
{product?.variants?.length > 0 && variantLevels.length > 0 && ( )} {product.theExtrasGroups.length > 0 && ( )} {getExtras()?.length > 0 && ( )}
)}
{!isDesktop && ( setQuantity(quantity)} onClose={onClose} /> )}
); }