Files
menulio/apps/web/src/components/PublicMenuClient.tsx
T

808 lines
35 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useMemo, useState, useEffect } from "react";
import type { ThemeConfig } from "@menulio/shared";
import { THEME_PRESETS, resolveThemePreset, type ThemePreset } from "@/lib/theme";
export interface MenuItem {
id: string;
name: string;
description: string | null;
price: number;
image_url: string | null;
is_active: boolean;
}
export interface MenuCategory {
id: string;
name: string;
description: string | null;
is_active: boolean;
menu_items: MenuItem[];
}
export interface RestaurantData {
id: string;
name: string;
slug: string;
logo_url: string | null;
phone?: string | null;
address?: string | null;
}
interface PublicMenuClientProps {
restaurant: RestaurantData;
categories: MenuCategory[];
initialThemeKey?: string;
customThemeConfig?: Partial<ThemeConfig>;
allowThemeSwitching?: boolean;
}
export function PublicMenuClient({
restaurant,
categories,
initialThemeKey = "elegant",
customThemeConfig,
allowThemeSwitching = true,
}: PublicMenuClientProps) {
const [selectedThemeKey, setSelectedThemeKey] = useState<string>(initialThemeKey);
const [searchQuery, setSearchQuery] = useState("");
const [activeCategoryId, setActiveCategoryId] = useState<string>(categories[0]?.id ?? "");
const [selectedItem, setSelectedItem] = useState<MenuItem | null>(null);
const [showInfoModal, setShowInfoModal] = useState(false);
const [showThemePicker, setShowThemePicker] = useState(false);
const [copiedLink, setCopiedLink] = useState(false);
const [showBackToTop, setShowBackToTop] = useState(false);
const [tableNumber, setTableNumber] = useState<string | null>(null);
const [serviceActionToast, setServiceActionToast] = useState<string | null>(null);
const theme: ThemePreset = useMemo(() => {
return resolveThemePreset(selectedThemeKey, customThemeConfig);
}, [selectedThemeKey, customThemeConfig]);
// Extract table number from URL (?table=X)
useEffect(() => {
if (typeof window !== "undefined") {
const params = new URLSearchParams(window.location.search);
const table = params.get("table") || params.get("masa");
if (table) setTableNumber(table);
}
}, []);
// Track scroll position for active category & back-to-top button
useEffect(() => {
const handleScroll = () => {
setShowBackToTop(window.scrollY > 300);
const categoryElements = categories.map((c) => ({
id: c.id,
el: document.getElementById(`category-${c.id}`),
}));
const scrollPos = window.scrollY + 140;
for (let i = categoryElements.length - 1; i >= 0; i--) {
const item = categoryElements[i];
if (item && item.el && item.el.offsetTop <= scrollPos) {
setActiveCategoryId(item.id);
break;
}
}
};
window.addEventListener("scroll", handleScroll, { passive: true });
return () => window.removeEventListener("scroll", handleScroll);
}, [categories]);
// Filter categories and items based on search
const filteredCategories = useMemo(() => {
const q = searchQuery.trim().toLowerCase();
if (!q) return categories;
return categories
.map((cat) => ({
...cat,
menu_items: cat.menu_items.filter(
(item) =>
item.is_active &&
(item.name.toLowerCase().includes(q) || (item.description && item.description.toLowerCase().includes(q))),
),
}))
.filter((cat) => cat.menu_items.length > 0);
}, [categories, searchQuery]);
const totalItemsCount = useMemo(() => {
return categories.reduce((acc, cat) => acc + cat.menu_items.filter((i) => i.is_active).length, 0);
}, [categories]);
const scrollToCategory = (categoryId: string) => {
setActiveCategoryId(categoryId);
const el = document.getElementById(`category-${categoryId}`);
if (el) {
const yOffset = -85;
const y = el.getBoundingClientRect().top + window.pageYOffset + yOffset;
window.scrollTo({ top: y, behavior: "smooth" });
}
};
const handleShare = async () => {
const url = window.location.href;
if (navigator.share) {
try {
await navigator.share({
title: `${restaurant.name} - Dijital Menü`,
text: `${restaurant.name} dijital menüsünü inceleyin!`,
url,
});
return;
} catch {
// fallback
}
}
await navigator.clipboard.writeText(url);
setCopiedLink(true);
setTimeout(() => setCopiedLink(false), 2500);
};
const triggerServiceAction = (msg: string) => {
setServiceActionToast(msg);
setTimeout(() => setServiceActionToast(null), 3500);
};
const fontClass =
theme.fontFamily === "serif" ? "font-serif" : theme.fontFamily === "heading" ? "font-heading" : "font-sans";
return (
<div
className={`min-h-screen ${fontClass} transition-colors duration-300`}
style={{
backgroundColor: theme.config.background,
color: theme.textPrimary,
}}
>
{/* Toast Notification */}
{serviceActionToast && (
<div className="fixed top-5 left-1/2 -translate-x-1/2 z-50 animate-bounce-in max-w-sm w-full px-4">
<div className="bg-stone-900 text-white px-5 py-3.5 rounded-2xl shadow-2xl flex items-center gap-3 border border-stone-700">
<span className="text-xl">🔔</span>
<p className="text-sm font-medium flex-1">{serviceActionToast}</p>
</div>
</div>
)}
{/* Main Container - Optimized for Mobile First & Desktop Centered */}
<div className="w-full max-w-2xl mx-auto min-h-screen flex flex-col shadow-2xl relative">
{/* Header Hero Banner */}
<header
style={{ background: theme.headerGradient }}
className="relative text-white px-4 sm:px-6 pt-8 sm:pt-10 pb-7 sm:pb-8 rounded-b-3xl shadow-lg overflow-hidden"
>
{/* Subtle Ambient Glow */}
<div
className="absolute top-0 right-0 w-64 h-64 rounded-full blur-3xl opacity-20 pointer-events-none"
style={{ backgroundColor: theme.config.primaryColor }}
/>
{/* Top Bar: Table & Action Buttons */}
<div className="flex items-center justify-between mb-4 sm:mb-5 relative z-10">
{tableNumber ? (
<div className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-white/10 backdrop-blur-md border border-white/15 text-xs font-semibold tracking-wide">
<span className="w-2 h-2 rounded-full bg-emerald-400 animate-pulse" />
Masa {tableNumber}
</div>
) : (
<div className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-white/10 backdrop-blur-md border border-white/15 text-xs font-medium tracking-wide">
<span></span> Dijital QR Menü
</div>
)}
<div className="flex items-center gap-1.5 sm:gap-2">
{/* Restaurant Info Trigger */}
{(restaurant.phone || restaurant.address) && (
<button
type="button"
onClick={() => setShowInfoModal(true)}
className="w-8 h-8 sm:w-9 sm:h-9 rounded-full bg-white/10 hover:bg-white/20 active:scale-95 transition-all flex items-center justify-center border border-white/15 backdrop-blur-md"
aria-label="Restoran Bilgisi"
>
<svg className="w-3.5 h-3.5 sm:w-4 sm:h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</button>
)}
{/* Share Trigger */}
<button
type="button"
onClick={handleShare}
className="w-8 h-8 sm:w-9 sm:h-9 rounded-full bg-white/10 hover:bg-white/20 active:scale-95 transition-all flex items-center justify-center border border-white/15 backdrop-blur-md"
aria-label="Menüyü Paylaş"
>
{copiedLink ? (
<svg className="w-3.5 h-3.5 sm:w-4 sm:h-4 text-emerald-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
</svg>
) : (
<svg className="w-3.5 h-3.5 sm:w-4 sm:h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z" />
</svg>
)}
</button>
{/* Live Theme Switcher Trigger */}
{allowThemeSwitching && (
<button
type="button"
onClick={() => setShowThemePicker(!showThemePicker)}
className="px-2.5 py-1 sm:py-1.5 rounded-full bg-white/10 hover:bg-white/20 active:scale-95 transition-all flex items-center gap-1.5 border border-white/15 backdrop-blur-md text-[11px] sm:text-xs font-semibold"
aria-label="Tema Değiştir"
>
<span className="w-2 h-2 sm:w-2.5 sm:h-2.5 rounded-full" style={{ backgroundColor: theme.config.primaryColor }} />
<span>Tema</span>
</button>
)}
</div>
</div>
{/* Restaurant Identity */}
<div className="flex items-center gap-3 sm:gap-4 relative z-10">
{restaurant.logo_url ? (
<img
src={restaurant.logo_url}
alt={restaurant.name}
className="w-14 h-14 sm:w-16 sm:h-16 rounded-2xl object-cover border-2 shadow-md flex-shrink-0"
style={{ borderColor: theme.config.primaryColor }}
/>
) : (
<div
className="w-14 h-14 sm:w-16 sm:h-16 rounded-2xl flex items-center justify-center font-bold text-xl sm:text-2xl flex-shrink-0 shadow-inner border border-white/20"
style={{
background: "rgba(255, 255, 255, 0.12)",
color: theme.config.primaryColor,
}}
>
{restaurant.name.charAt(0).toUpperCase()}
</div>
)}
<div className="flex-1 min-w-0">
<h1 className="text-xl sm:text-2xl font-bold tracking-tight leading-snug break-words">
{restaurant.name}
</h1>
<p className="text-[11px] sm:text-xs text-white/70 mt-0.5 flex flex-wrap items-center gap-1.5">
<span>{totalItemsCount} Özel Lezzet</span>
<span></span>
<span className="capitalize">{theme.name} Şablonu</span>
</p>
</div>
</div>
{/* Live Search Bar */}
<div className="mt-6 relative z-10">
<div className="relative flex items-center">
<input
type="text"
placeholder="Yemek, içecek veya tatlı ara..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-11 pr-10 py-3 rounded-2xl text-stone-900 bg-white/95 placeholder-stone-400 text-sm focus:outline-none focus:ring-2 transition-all shadow-lg"
style={{
outlineColor: theme.config.primaryColor,
}}
/>
<svg
className="w-5 h-5 absolute left-3.5 text-stone-400 pointer-events-none"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
{searchQuery && (
<button
type="button"
onClick={() => setSearchQuery("")}
className="absolute right-3.5 w-5 h-5 rounded-full bg-stone-200 text-stone-600 flex items-center justify-center text-xs"
>
</button>
)}
</div>
</div>
</header>
{/* Live Theme Switcher Drawer */}
{showThemePicker && allowThemeSwitching && (
<div className="bg-stone-900 text-white px-5 py-4 border-b border-stone-800 animate-slide-up">
<div className="flex items-center justify-between mb-3">
<p className="text-xs font-bold uppercase tracking-wider text-stone-400">
Canlı Tema Önizleme (6 Şablon)
</p>
<button
type="button"
onClick={() => setShowThemePicker(false)}
className="text-stone-400 hover:text-white text-xs"
>
Kapat
</button>
</div>
<div className="grid grid-cols-3 sm:grid-cols-6 gap-2">
{Object.values(THEME_PRESETS).map((preset) => {
const isSelected = selectedThemeKey === preset.key;
return (
<button
key={preset.key}
type="button"
onClick={() => setSelectedThemeKey(preset.key)}
className={`flex flex-col items-center gap-1.5 p-2 rounded-xl border text-center transition-all ${
isSelected
? "bg-white/15 border-white shadow-md scale-105"
: "bg-white/5 border-white/10 opacity-70 hover:opacity-100"
}`}
>
<span
className="w-5 h-5 rounded-full border border-white/30"
style={{ backgroundColor: preset.config.primaryColor }}
/>
<span className="text-[10px] font-medium leading-tight truncate w-full">
{preset.name.split(" ")[0]}
</span>
</button>
);
})}
</div>
</div>
)}
{/* Sticky Category Navigation Bar */}
{categories.length > 0 && !searchQuery && (
<nav
className="sticky top-0 z-40 px-4 py-3 backdrop-blur-xl border-b shadow-sm transition-all"
style={{
backgroundColor: `${theme.config.background}F2`,
borderColor: theme.cardBorder,
}}
>
<div className="flex items-center gap-2 overflow-x-auto no-scrollbar scroll-smooth py-0.5">
{categories.map((cat) => {
const isActive = activeCategoryId === cat.id;
return (
<button
key={cat.id}
type="button"
onClick={() => scrollToCategory(cat.id)}
className={`px-4 py-2 rounded-full text-xs font-bold whitespace-nowrap transition-all flex items-center gap-1.5 ${
isActive
? "text-white shadow-md scale-105"
: "hover:bg-black/5 active:scale-95"
}`}
style={
isActive
? { backgroundColor: theme.config.primaryColor }
: {
backgroundColor: theme.accentBg,
color: theme.textSecondary,
}
}
>
<span>{cat.name}</span>
<span
className={`text-[10px] px-1.5 py-0.2 rounded-full ${
isActive ? "bg-black/20 text-white" : "bg-black/5"
}`}
>
{cat.menu_items.length}
</span>
</button>
);
})}
</div>
</nav>
)}
{/* Menu Content Area */}
<main className="flex-1 px-4 py-6 space-y-8">
{filteredCategories.length === 0 ? (
<div className="text-center py-16 px-4">
<div className="w-16 h-16 rounded-full bg-stone-100 dark:bg-stone-800 flex items-center justify-center text-3xl mx-auto mb-4">
🔍
</div>
<h3 className="text-base font-bold text-stone-800 dark:text-stone-200">
Aramanıza Uygun Lezzet Bulunamadı
</h3>
<p className="text-xs text-stone-500 mt-1 max-w-xs mx-auto">
"{searchQuery}" için sonuç yok. Lütfen farklı bir arama kelimesi deneyin.
</p>
<button
type="button"
onClick={() => setSearchQuery("")}
className="mt-4 px-4 py-2 rounded-xl text-xs font-bold text-white shadow"
style={{ backgroundColor: theme.config.primaryColor }}
>
Tüm Menüyü Göster
</button>
</div>
) : (
filteredCategories.map((category) => (
<section
key={category.id}
id={`category-${category.id}`}
className="scroll-mt-28 space-y-3.5"
>
{/* Sticky Category Heading Banner */}
<div
className="sticky top-[58px] z-20 flex items-center justify-between py-2.5 px-3 -mx-3 rounded-2xl backdrop-blur-xl border-b transition-all shadow-sm"
style={{
backgroundColor: `${theme.config.background}F2`,
borderColor: theme.cardBorder,
}}
>
<div>
<h2
className="text-base sm:text-lg font-extrabold tracking-tight"
style={{ color: theme.config.primaryColor }}
>
{category.name}
</h2>
{category.description && (
<p className="text-xs mt-0.5 line-clamp-1" style={{ color: theme.textSecondary }}>
{category.description}
</p>
)}
</div>
<span
className="text-[11px] font-bold px-2.5 py-0.5 rounded-full shadow-sm"
style={{ backgroundColor: theme.badgeBg, color: theme.badgeText }}
>
{category.menu_items.length} Ürün
</span>
</div>
{/* Items Container: Grid / Card / List Layout based on Theme */}
{/* Items Container: Grid / Card / List Layout based on Theme */}
<div
className={
theme.key === "artisan"
? "grid grid-cols-2 gap-3 sm:gap-4"
: theme.config.productLayout === "list"
? "space-y-3"
: "grid grid-cols-1 gap-3.5"
}
>
{category.menu_items.map((item) => {
if (theme.key === "artisan") {
return (
<article
key={item.id}
onClick={() => setSelectedItem(item)}
className="group relative rounded-3xl p-3.5 transition-all duration-300 cursor-pointer border flex flex-col justify-between hover:scale-[1.02] active:scale-[0.98] shadow-md"
style={{
backgroundColor: theme.cardBg,
borderColor: theme.cardBorder,
}}
>
{/* Image Container with 3D Dish presentation */}
<div className="relative w-full aspect-square rounded-2xl overflow-hidden bg-stone-900/60 mb-3 flex items-center justify-center border border-white/5">
{item.image_url ? (
<img
src={item.image_url}
alt={item.name}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
loading="lazy"
/>
) : (
<div className="text-3xl opacity-60"></div>
)}
<span
className="absolute top-2 right-2 px-2 py-0.5 rounded-full text-[9px] font-extrabold uppercase tracking-wide shadow-md"
style={{ backgroundColor: theme.config.primaryColor, color: "#121316" }}
>
ÖZEL
</span>
</div>
{/* Info */}
<div className="flex-1 flex flex-col justify-between">
<div>
<h3 className="font-bold text-xs sm:text-sm text-white tracking-tight leading-snug line-clamp-1 group-hover:text-amber-400 transition-colors">
{item.name}
</h3>
{item.description ? (
<p className="text-[11px] text-stone-400 mt-1 line-clamp-2 leading-relaxed">
{item.description}
</p>
) : null}
</div>
<div className="mt-3 pt-2.5 border-t border-white/5 flex items-center justify-between">
<span
className="text-sm sm:text-base font-black tracking-tight"
style={{ color: theme.config.primaryColor }}
>
{item.price.toFixed(0)}
</span>
<span
className="w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold text-stone-950 shadow-sm transition-transform group-hover:scale-110"
style={{ backgroundColor: theme.config.primaryColor }}
>
+
</span>
</div>
</div>
</article>
);
}
return (
<article
key={item.id}
onClick={() => setSelectedItem(item)}
className={`group relative rounded-2xl p-4 transition-all duration-200 cursor-pointer border hover:shadow-lg active:scale-[0.99] flex gap-3.5 items-center ${
theme.config.productLayout === "list" ? "justify-between" : ""
}`}
style={{
backgroundColor: theme.cardBg,
borderColor: theme.cardBorder,
}}
>
{/* Item Details */}
<div className="flex-1 min-w-0 pr-1">
<div className="flex items-start justify-between gap-2">
<h3 className="font-bold text-sm tracking-tight leading-snug group-hover:text-amber-600 transition-colors">
{item.name}
</h3>
</div>
{item.description && (
<p
className="text-xs mt-1 leading-relaxed line-clamp-2"
style={{ color: theme.textSecondary }}
>
{item.description}
</p>
)}
<div className="mt-2.5 flex items-center justify-between">
<span
className="text-base font-extrabold tracking-tight"
style={{ color: theme.config.primaryColor }}
>
{item.price.toFixed(0)}
</span>
<span className="text-[10px] font-bold px-2 py-0.5 rounded-md bg-black/5 dark:bg-white/10 group-hover:bg-amber-100 dark:group-hover:bg-amber-900/40 transition-colors">
İncele
</span>
</div>
</div>
{/* Item Thumbnail Image */}
{item.image_url && (
<div className="relative w-20 h-20 rounded-xl overflow-hidden flex-shrink-0 bg-stone-100 dark:bg-stone-800 shadow-sm">
<img
src={item.image_url}
alt={item.name}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
loading="lazy"
/>
</div>
)}
</article>
);
})}
</div>
</section>
))
)}
</main>
{/* Footer */}
<footer
className="px-6 py-10 text-center border-t mt-auto text-xs space-y-4"
style={{
borderColor: theme.cardBorder,
color: theme.textSecondary,
}}
>
<div className="flex items-center justify-center gap-2 font-semibold">
<span>Powered by</span>
<span className="text-stone-900 dark:text-white font-black tracking-wider uppercase">
MENULIO
</span>
</div>
<p className="text-[11px] opacity-70">
© {new Date().getFullYear()} {restaurant.name}. Fiyatlara tüm vergiler dahildir.
</p>
</footer>
{/* Floating Quick Action Bar (Garson Çağır / Hesap İste / Başa Dön) */}
<aside aria-label="Masa Servis İşlemleri" className="fixed bottom-5 left-1/2 -translate-x-1/2 z-40 max-w-sm w-full px-4 flex items-center justify-between gap-2 pointer-events-none">
<div className="flex items-center gap-2 pointer-events-auto shadow-2xl rounded-full p-1 bg-stone-900/90 backdrop-blur-lg border border-stone-700 text-white">
<button
type="button"
onClick={() =>
triggerServiceAction(
tableNumber
? `Masa ${tableNumber} için Garson Çağrıldı! Garsonunuz en kısa sürede masanızda olacaktır.`
: "Garson çağrıldı! Garsonunuz hemen masanızda olacaktır.",
)
}
className="px-3.5 py-2 rounded-full hover:bg-white/15 active:scale-95 transition-all text-xs font-bold flex items-center gap-1.5"
>
<span>👋</span> Garson Çağır
</button>
<button
type="button"
onClick={() =>
triggerServiceAction(
tableNumber
? `Masa ${tableNumber} için Hesap İsteği iletildi!`
: "Hesap isteği iletildi!",
)
}
className="px-3.5 py-2 rounded-full hover:bg-white/15 active:scale-95 transition-all text-xs font-bold flex items-center gap-1.5"
>
<span>💳</span> Hesap İste
</button>
</div>
{showBackToTop && (
<button
type="button"
onClick={() => window.scrollTo({ top: 0, behavior: "smooth" })}
className="w-11 h-11 rounded-full bg-stone-900 text-white shadow-2xl flex items-center justify-center pointer-events-auto active:scale-90 transition-all border border-stone-700"
aria-label="Başa Dön"
>
</button>
)}
</aside>
</div>
{/* Item Detail Modal */}
{selectedItem && (
<div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center p-0 sm:p-4 bg-black/70 backdrop-blur-sm animate-fade-in">
<div
className="w-full max-w-lg rounded-t-3xl sm:rounded-3xl overflow-hidden shadow-2xl animate-slide-up flex flex-col max-h-[85vh]"
style={{ backgroundColor: theme.cardBg, color: theme.textPrimary }}
>
{/* Modal Image */}
{selectedItem.image_url ? (
<div className="relative h-56 w-full bg-stone-900">
<img
src={selectedItem.image_url}
alt={selectedItem.name}
className="w-full h-full object-cover"
/>
<button
type="button"
onClick={() => setSelectedItem(null)}
className="absolute top-4 right-4 w-9 h-9 rounded-full bg-black/60 text-white flex items-center justify-center backdrop-blur-md text-sm hover:bg-black/80"
>
</button>
</div>
) : (
<div className="p-4 flex justify-end">
<button
type="button"
onClick={() => setSelectedItem(null)}
className="w-8 h-8 rounded-full bg-stone-100 dark:bg-stone-800 text-stone-600 dark:text-stone-300 flex items-center justify-center text-sm"
>
</button>
</div>
)}
{/* Modal Body */}
<div className="p-6 overflow-y-auto space-y-4">
<div className="flex items-start justify-between gap-4">
<h3 className="text-xl font-bold tracking-tight leading-snug">
{selectedItem.name}
</h3>
<span
className="text-xl font-black whitespace-nowrap"
style={{ color: theme.config.primaryColor }}
>
{selectedItem.price.toFixed(0)}
</span>
</div>
{selectedItem.description && (
<p className="text-sm leading-relaxed" style={{ color: theme.textSecondary }}>
{selectedItem.description}
</p>
)}
{/* Quality Badges */}
<div className="pt-2 flex flex-wrap gap-2 text-xs">
<span className="px-3 py-1 rounded-full bg-emerald-50 text-emerald-700 font-semibold border border-emerald-200">
🌱 Taze & Günlük
</span>
<span className="px-3 py-1 rounded-full bg-amber-50 text-amber-700 font-semibold border border-amber-200">
Şefin İmzası
</span>
</div>
</div>
{/* Modal Footer */}
<div className="p-4 border-t" style={{ borderColor: theme.cardBorder }}>
<button
type="button"
onClick={() => {
setSelectedItem(null);
triggerServiceAction(`"${selectedItem.name}" sipariş tercihleriniz garsona iletildi!`);
}}
className="w-full py-3.5 rounded-2xl font-bold text-white shadow-lg active:scale-98 transition-all flex items-center justify-center gap-2 text-sm"
style={{ backgroundColor: theme.config.primaryColor }}
>
<span></span> Garsona Sipariş Olarak Bildir
</button>
</div>
</div>
</div>
)}
{/* Restaurant Info & Contact Modal */}
{showInfoModal && (
<div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center p-0 sm:p-4 bg-black/70 backdrop-blur-sm animate-fade-in">
<div
className="w-full max-w-md rounded-t-3xl sm:rounded-3xl overflow-hidden shadow-2xl p-6 space-y-5 animate-slide-up"
style={{ backgroundColor: theme.cardBg, color: theme.textPrimary }}
>
<div className="flex items-center justify-between">
<h3 className="text-lg font-bold">Restoran Bilgileri</h3>
<button
type="button"
onClick={() => setShowInfoModal(false)}
className="w-8 h-8 rounded-full bg-stone-100 dark:bg-stone-800 text-stone-600 dark:text-stone-300 flex items-center justify-center text-sm"
>
</button>
</div>
<div className="space-y-4 text-sm">
{restaurant.phone && (
<div className="flex items-center gap-3 p-3 rounded-xl bg-stone-50 dark:bg-stone-800/50">
<span className="text-lg">📞</span>
<div className="flex-1">
<p className="text-xs text-stone-400">Telefon</p>
<a href={`tel:${restaurant.phone}`} className="font-semibold hover:underline">
{restaurant.phone}
</a>
</div>
</div>
)}
{restaurant.address && (
<div className="flex items-center gap-3 p-3 rounded-xl bg-stone-50 dark:bg-stone-800/50">
<span className="text-lg">📍</span>
<div className="flex-1">
<p className="text-xs text-stone-400">Adres</p>
<p className="font-medium text-xs">{restaurant.address}</p>
</div>
</div>
)}
<div className="flex items-center gap-3 p-3 rounded-xl bg-stone-50 dark:bg-stone-800/50">
<span className="text-lg">🕒</span>
<div className="flex-1">
<p className="text-xs text-stone-400">Çalışma Saatleri</p>
<p className="font-semibold text-xs">Hergün 10:00 - 00:00</p>
</div>
</div>
</div>
<button
type="button"
onClick={() => setShowInfoModal(false)}
className="w-full py-3 rounded-xl font-bold bg-stone-900 text-white text-sm"
>
Kapat
</button>
</div>
</div>
)}
</div>
);
}