feat: redesign web menu layout, add header cover banner & dynamic header layout, refine mobile inputs and theme selector

This commit is contained in:
AyrisAI
2026-08-20 15:07:13 +03:00
parent 6fb91c94ff
commit 0836fc8539
17 changed files with 311 additions and 553 deletions
+103 -108
View File
@@ -26,6 +26,7 @@ export interface RestaurantData {
name: string;
slug: string;
logo_url: string | null;
cover_url?: string | null;
phone?: string | null;
address?: string | null;
}
@@ -43,7 +44,7 @@ export function PublicMenuClient({
categories,
initialThemeKey = "elegant",
customThemeConfig,
allowThemeSwitching = true,
allowThemeSwitching = false,
}: PublicMenuClientProps) {
const [selectedThemeKey, setSelectedThemeKey] = useState<string>(initialThemeKey);
const [searchQuery, setSearchQuery] = useState("");
@@ -176,26 +177,38 @@ export function PublicMenuClient({
{/* 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"
style={{ background: restaurant.cover_url ? "none" : 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 min-h-[200px]"
>
{/* Background Cover Image if available */}
{restaurant.cover_url && (
<>
<img
src={restaurant.cover_url}
alt={restaurant.name}
className="absolute inset-0 w-full h-full object-cover z-0"
/>
<div className="absolute inset-0 bg-gradient-to-b from-black/65 via-black/45 to-black/85 z-0 backdrop-blur-[1px]" />
</>
)}
{/* 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 }}
/>
{!restaurant.cover_url && (
<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">
<div className="flex items-center justify-between mb-2 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">
<div className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-black/40 backdrop-blur-md border border-white/20 text-xs font-semibold tracking-wide text-white">
<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 />
)}
<div className="flex items-center gap-1.5 sm:gap-2">
@@ -230,54 +243,84 @@ export function PublicMenuClient({
</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>
)}
{/* Restaurant Identity - Dynamic Header Layout (Centered or Left) */}
{theme.config.headerLayout === "left" ? (
/* Left-Aligned Header Layout */
<div className="flex items-center gap-3.5 sm:gap-4 relative z-10 pt-1 pb-1">
{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">
<div className="flex-1 min-w-0">
<h1 className="text-xl sm:text-2xl font-bold tracking-tight leading-snug break-words text-white">
{restaurant.name}
</h1>
<p className="text-[11px] sm:text-xs text-white/80 mt-0.5 flex items-center gap-1.5 font-medium">
<span>{totalItemsCount} Özel Lezzet</span>
</p>
</div>
</div>
) : (
/* Centered Header Layout */
<div className="flex flex-col items-center text-center relative z-10 pt-1 pb-2">
{/* Centered Logo Avatar */}
<div className="relative group">
{restaurant.logo_url ? (
<img
src={restaurant.logo_url}
alt={restaurant.name}
className="w-18 h-18 sm:w-20 sm:h-20 rounded-full object-cover border-2 shadow-2xl transition-transform duration-300 group-hover:scale-105"
style={{ borderColor: theme.config.primaryColor }}
/>
) : (
<div
className="w-18 h-18 sm:w-20 sm:h-20 rounded-full flex items-center justify-center font-black text-2xl sm:text-3xl shadow-2xl border-2 backdrop-blur-md"
style={{
background: "rgba(255, 255, 255, 0.15)",
borderColor: theme.config.primaryColor,
color: "#FFFFFF",
}}
>
{restaurant.name.charAt(0).toUpperCase()}
</div>
)}
{/* Decorative Ambient Glow Ring */}
<div
className="absolute -inset-1 rounded-full blur-md opacity-30 pointer-events-none"
style={{ backgroundColor: theme.config.primaryColor }}
/>
</div>
{/* Centered Restaurant Title */}
<h1 className="text-2xl sm:text-3xl font-extrabold tracking-tight leading-snug mt-3 text-white drop-shadow-md max-w-md">
{restaurant.name}
</h1>
<p className="text-[11px] sm:text-xs text-white/70 mt-0.5 flex flex-wrap items-center gap-1.5">
{/* Centered Subtitle Badge Pill */}
<div className="mt-2 inline-flex items-center gap-1.5 px-3.5 py-1 rounded-full bg-black/35 backdrop-blur-md border border-white/15 text-xs font-medium text-white/90 shadow-sm">
<span className="w-1.5 h-1.5 rounded-full bg-amber-400 animate-pulse" />
<span>{totalItemsCount} Özel Lezzet</span>
<span></span>
<span className="capitalize">{theme.name} Şablonu</span>
</p>
</div>
</div>
</div>
)}
{/* Live Search Bar */}
<div className="mt-6 relative z-10">
@@ -810,12 +853,6 @@ export function PublicMenuClient({
>
{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>
@@ -905,48 +942,19 @@ export function PublicMenuClient({
</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 && (
{/* Floating Back to Top Button */}
{showBackToTop && (
<aside aria-label="Başa Dön" className="fixed bottom-5 right-5 z-40">
<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"
className="w-11 h-11 rounded-full bg-stone-900 text-white shadow-2xl flex items-center justify-center active:scale-90 transition-all border border-stone-700 font-bold"
aria-label="Başa Dön"
>
</button>
)}
</aside>
</aside>
)}
</div>
{/* Item Detail Modal */}
@@ -1003,30 +1011,17 @@ export function PublicMenuClient({
{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!`);
}}
onClick={() => setSelectedItem(null)}
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
Kapat
</button>
</div>
</div>
+64 -178
View File
@@ -1,9 +1,8 @@
"use client";
import { useState } from "react";
import Link from "next/link";
import { PublicMenuClient, type MenuCategory, type RestaurantData } from "@/components/PublicMenuClient";
import { THEME_PRESETS, type ThemePreset } from "@/lib/theme";
import { THEME_PRESETS } from "@/lib/theme";
interface TemplateGalleryClientProps {
restaurant: RestaurantData;
@@ -14,199 +13,86 @@ interface TemplateGalleryClientProps {
export function TemplateGalleryClient({
restaurant,
categories,
initialThemeKey = "elegant",
}: TemplateGalleryClientProps) {
const [selectedThemeKey, setSelectedThemeKey] = useState<string>(initialThemeKey);
const [viewMode, setViewMode] = useState<"phone" | "fullscreen">("phone");
const currentPreset: ThemePreset = THEME_PRESETS[selectedThemeKey] || THEME_PRESETS.elegant!;
const presetsList = Object.values(THEME_PRESETS);
return (
<div className="min-h-screen bg-stone-950 text-stone-100 flex flex-col font-sans">
{/* Top Navbar */}
<header className="border-b border-stone-800/80 bg-stone-900/90 backdrop-blur-xl sticky top-0 z-50 px-4 sm:px-6 py-3.5">
<div className="max-w-7xl mx-auto flex flex-col md:flex-row items-center justify-between gap-3">
<div className="flex items-center justify-between w-full md:w-auto">
<div className="flex items-center gap-3">
<Link href="/" className="font-black text-xl tracking-tight text-amber-400">
MENULIO
</Link>
<span className="text-stone-600 hidden sm:inline">/</span>
<span className="text-xs font-semibold text-stone-400 hidden sm:inline">Şablon Galerisi</span>
</div>
{/* View Mode Toggle (Visible on desktop/tablet) */}
<div className="flex items-center bg-stone-800/80 p-1 rounded-xl border border-stone-700 md:hidden">
<button
type="button"
onClick={() => setViewMode("phone")}
className={`px-2.5 py-1 rounded-lg text-xs font-bold transition-all ${
viewMode === "phone" ? "bg-amber-400 text-stone-950 shadow" : "text-stone-400"
}`}
>
📱 Mobil
</button>
<button
type="button"
onClick={() => setViewMode("fullscreen")}
className={`px-2.5 py-1 rounded-lg text-xs font-bold transition-all ${
viewMode === "fullscreen" ? "bg-amber-400 text-stone-950 shadow" : "text-stone-400"
}`}
>
🖥 Tam
</button>
</div>
</div>
{/* 5 Theme Pills - Smooth Horizontal Scroll */}
<div className="flex items-center gap-2 overflow-x-auto w-full md:w-auto pb-1 md:pb-0 no-scrollbar">
{Object.values(THEME_PRESETS).map((preset) => {
const isActive = selectedThemeKey === preset.key;
return (
<button
key={preset.key}
type="button"
onClick={() => setSelectedThemeKey(preset.key)}
className={`px-3.5 py-1.5 rounded-xl text-xs font-bold transition-all flex items-center gap-2 whitespace-nowrap border flex-shrink-0 ${
isActive
? "bg-white text-stone-950 border-white shadow-lg scale-105"
: "bg-stone-800/90 text-stone-300 border-stone-700 hover:border-stone-500 hover:text-white"
}`}
>
<span
className="w-2.5 h-2.5 rounded-full"
style={{ backgroundColor: preset.config.primaryColor }}
/>
<span>{preset.name}</span>
</button>
);
})}
</div>
<div className="hidden md:flex items-center gap-3">
{/* View Mode Toggle (Desktop) */}
<div className="flex items-center bg-stone-800/80 p-1 rounded-xl border border-stone-700">
<button
type="button"
onClick={() => setViewMode("phone")}
className={`px-3 py-1 rounded-lg text-xs font-bold transition-all ${
viewMode === "phone" ? "bg-amber-400 text-stone-950 shadow" : "text-stone-400 hover:text-white"
}`}
>
📱 Telefon
</button>
<button
type="button"
onClick={() => setViewMode("fullscreen")}
className={`px-3 py-1 rounded-lg text-xs font-bold transition-all ${
viewMode === "fullscreen" ? "bg-amber-400 text-stone-950 shadow" : "text-stone-400 hover:text-white"
}`}
>
🖥 Tam Ekran
</button>
</div>
<Link
href="/"
className="text-xs font-bold px-4 py-2 rounded-xl bg-amber-500 hover:bg-amber-400 text-stone-950 transition-all shadow-md shadow-amber-500/20"
>
Uygulamayı İndir
<div className="max-w-7xl mx-auto flex items-center justify-between">
<div className="flex items-center gap-3">
<Link href="/" className="font-black text-xl tracking-tight text-amber-400">
MENULIO
</Link>
<span className="text-stone-600 hidden sm:inline">/</span>
<span className="text-xs font-semibold text-stone-400 hidden sm:inline">
Şablon Galerisi
</span>
</div>
<Link
href="/"
className="text-xs font-bold px-4 py-2 rounded-xl bg-amber-500 hover:bg-amber-400 text-stone-950 transition-all shadow-md shadow-amber-500/20"
>
Ana Sayfa
</Link>
</div>
</header>
{/* Main Content Area */}
<main className="flex-1 w-full flex flex-col items-center justify-center p-0 sm:p-6 lg:p-8">
{viewMode === "fullscreen" ? (
/* Fullscreen Fluid View */
<div className="w-full flex-1 min-h-[85vh]">
<PublicMenuClient
restaurant={restaurant}
categories={categories}
initialThemeKey={selectedThemeKey}
allowThemeSwitching={true}
/>
</div>
) : (
/* Responsive Device Simulator / Side-by-Side on Desktop */
<div className="w-full max-w-6xl mx-auto grid grid-cols-1 lg:grid-cols-12 gap-8 items-center justify-center py-4">
{/* Left Info Panel (Visible on Desktop) */}
<div className="hidden lg:flex lg:col-span-5 flex-col space-y-6 pr-4">
<div className="space-y-2">
<span className="text-amber-400 text-xs font-bold uppercase tracking-wider">
ŞABLON DETAYI
</span>
<h2 className="text-3xl font-extrabold text-white">
{currentPreset.name}
</h2>
<p className="text-sm text-stone-400 leading-relaxed">
{currentPreset.key === "elegant" && "Fine dining restoranlar, şarap evleri ve lüks steakhouse mekanlar için altın & krem tonlarında yüksek prestijli tasarım."}
{currentPreset.key === "modern" && "Kafeler, burgerciler ve dinamik mekanlar için canlı mavi safir tonları ve hızlı arama odaklı ızgara düzeni."}
{currentPreset.key === "dark" && "Gece kulüpleri, kokteyl barlar ve lounge mekanlar için obsidian siyahı ve kehribar parıltılı lüks mod."}
{currentPreset.key === "minimal" && "Butik kahveciler, fırınlar ve üçüncü nesil mekanlar için ferah ve monokromatik Nordic mizanpaj."}
{currentPreset.key === "classic" && "Geleneksel brasserie'ler, meyhaneler ve trattoria'lar için Toskana bordo ve sıcak rustik doku."}
</p>
{/* Main Content Area - Stacked Vertically */}
<main className="flex-1 w-full max-w-4xl mx-auto py-10 px-4 space-y-16">
{presetsList.map((preset) => (
<section
key={preset.key}
className="flex flex-col items-center gap-8 border-b border-stone-800/80 pb-16 last:border-0"
>
{/* Theme Info */}
<div className="text-center max-w-xl space-y-3">
<div className="inline-flex items-center gap-2.5 px-3.5 py-1.5 rounded-full bg-stone-900 border border-stone-800 text-xs font-bold text-stone-300">
<span
className="w-2.5 h-2.5 rounded-full"
style={{ backgroundColor: preset.config.primaryColor }}
/>
<span className="uppercase tracking-wider text-[11px]">{preset.key} Şablonu</span>
</div>
{/* Theme Specs */}
<div className="p-5 rounded-2xl bg-stone-900 border border-stone-800 space-y-3.5 text-xs">
<div className="flex items-center justify-between">
<span className="text-stone-400">Vurgu Rengi</span>
<div className="flex items-center gap-2">
<span
className="w-4 h-4 rounded-full border border-white/20"
style={{ backgroundColor: currentPreset.config.primaryColor }}
/>
<span className="font-mono text-stone-200">{currentPreset.config.primaryColor}</span>
</div>
</div>
<h2 className="text-2xl sm:text-3xl font-extrabold text-white">
{preset.name}
</h2>
<div className="flex items-center justify-between">
<span className="text-stone-400">Tipografi Ailesi</span>
<span className="font-semibold text-stone-200 uppercase">{currentPreset.fontFamily}</span>
</div>
<p className="text-xs sm:text-sm text-stone-400 leading-relaxed">
Tipografi: <span className="uppercase text-stone-200 font-semibold">{preset.fontFamily}</span> Mizanpaj: <span className="uppercase text-stone-200 font-semibold">{preset.config.productLayout}</span>
</p>
<div className="flex items-center justify-between">
<span className="text-stone-400">Ürün Mizanpajı</span>
<span className="font-semibold text-stone-200 uppercase">{currentPreset.config.productLayout}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-stone-400">Kategori Gezintisi</span>
<span className="font-semibold text-stone-200 uppercase">{currentPreset.config.categoryLayout}</span>
</div>
</div>
{/* Direct Demo Link */}
<Link
href={`/demo?theme=${selectedThemeKey}`}
target="_blank"
className="w-full py-3.5 rounded-xl font-bold text-center bg-stone-800 hover:bg-stone-700 text-stone-200 border border-stone-700 transition-all text-xs flex items-center justify-center gap-2"
>
<span></span> Yeni Sekmede Canlı Menüyü
</Link>
</div>
{/* Right Phone Mockup Container */}
<div className="lg:col-span-7 flex justify-center w-full">
<div className="w-full max-w-[420px] rounded-[36px] sm:rounded-[44px] overflow-hidden shadow-2xl border-0 sm:border-[8px] border-stone-800 bg-stone-900 relative">
{/* Dynamic Island (Desktop only) */}
<div className="hidden sm:block w-24 h-4 bg-stone-800 rounded-full mx-auto mt-2 mb-1" />
{/* Simulated Screen Body */}
<div className="overflow-y-auto max-h-[85vh] sm:max-h-[780px] rounded-none sm:rounded-[32px] no-scrollbar">
<PublicMenuClient
restaurant={restaurant}
categories={categories}
initialThemeKey={selectedThemeKey}
allowThemeSwitching={true}
/>
</div>
<div className="pt-2">
<Link
href={`/demo?theme=${preset.key}`}
target="_blank"
className="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl bg-stone-900 hover:bg-stone-800 text-stone-200 border border-stone-700/80 text-xs font-bold transition-all shadow-md"
>
<span></span> Canlı Demoyu Sekmede
</Link>
</div>
</div>
</div>
)}
{/* Phone Mockup Preview */}
<div className="w-full max-w-[420px] rounded-[36px] sm:rounded-[44px] overflow-hidden shadow-2xl border-0 sm:border-[8px] border-stone-800 bg-stone-900 relative">
{/* Dynamic Island */}
<div className="hidden sm:block w-24 h-4 bg-stone-800 rounded-full mx-auto mt-2.5 mb-1" />
{/* Screen Body */}
<div className="overflow-y-auto max-h-[720px] rounded-none sm:rounded-[32px] no-scrollbar">
<PublicMenuClient
restaurant={restaurant}
categories={categories}
initialThemeKey={preset.key}
allowThemeSwitching={false}
/>
</div>
</div>
</section>
))}
</main>
</div>
);