feat: admin panel CRUD, Cloudinary upload, logo, user management

This commit is contained in:
mstfyldz
2026-06-05 17:38:01 +03:00
parent 53eeeee474
commit 5e4e2f0db9
43 changed files with 3917 additions and 281 deletions
+2
View File
@@ -39,3 +39,5 @@ yarn-error.log*
# typescript # typescript
*.tsbuildinfo *.tsbuildinfo
next-env.d.ts next-env.d.ts
/app/generated/prisma
+4
View File
@@ -20,6 +20,10 @@ COPY . .
# Environment variables must be present at build time for Next.js # Environment variables must be present at build time for Next.js
ENV NEXT_TELEMETRY_DISABLED=1 ENV NEXT_TELEMETRY_DISABLED=1
# Generate Prisma client before building
COPY prisma ./prisma
RUN npx prisma generate
RUN npm run build RUN npm run build
# 4. Runner # 4. Runner
+266
View File
@@ -0,0 +1,266 @@
"use client";
import { useEffect, useState, useRef } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { MenuCategory, MenuItem as MenuItemType } from "@/data/menu";
import { CategoryNav } from "@/components/CategoryNav";
import { MenuItem } from "@/components/MenuItem";
export const CATEGORY_ICONS: Record<string, string> = {
"kahvaltiliklar": "🌅",
"kaseler": "🥗",
"burger-ve-sandvic": "🍔",
"pizzalar": "🍕",
"makarnalar": "🍝",
"baslangiclar": "🧆",
"salatalar": "🥬",
"ana-yemekler": "🍽️",
"tatlilar": "🍮",
"i̇mza-kokteyller": "🍹",
"klasik-kokteyller": "🍸",
"biralar": "🍺",
"shotlar": "🥃",
"sise-ve-kadeh-alkollu": "🥂",
"saraplar": "🍷",
"cerezler": "🥜",
"alkolsuz-i̇cecekler": "🧃",
"sicak-kahveler": "☕",
"soguk-kahveler": "🧊",
"ekstralar": "✨",
};
export default function MenuClient({ initialCategories }: { initialCategories: MenuCategory[] }) {
const [activeCategoryId, setActiveCategoryId] = useState<string>(
initialCategories[0]?.id || ""
);
const [selectedItem, setSelectedItem] = useState<MenuItemType | null>(null);
const sectionRefs = useRef<(HTMLElement | null)[]>([]);
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
const hit = entries.find((e) => e.isIntersecting);
if (hit) setActiveCategoryId(hit.target.id);
},
{ root: null, rootMargin: "-100px 0px -62% 0px", threshold: 0 }
);
sectionRefs.current.forEach((ref) => ref && observer.observe(ref));
return () => observer.disconnect();
}, []);
useEffect(() => {
if (selectedItem) {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "unset";
}
return () => {
document.body.style.overflow = "unset";
};
}, [selectedItem]);
return (
<div className="min-h-screen" style={{ background: "var(--cream)" }}>
{/* ── Hero ─────────────────────────────────────── */}
<header className="relative overflow-hidden bg-stone-900">
{/* Background Image */}
<div
className="absolute inset-0 z-0 opacity-80"
style={{
backgroundImage: "url('/header-bg.jpg')",
backgroundSize: "cover",
backgroundPosition: "center"
}}
/>
{/* Dark overlay to make text readable */}
<div className="absolute inset-0 z-0 bg-gradient-to-b from-black/60 via-black/40 to-black/80" />
{/* hero content */}
<div className="relative z-10 flex flex-col items-center text-center px-6 pt-16 pb-20">
<motion.div
initial={{ opacity: 0, y: 24 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.9, ease: [0.16, 1, 0.3, 1] }}
className="flex flex-col items-center"
>
<p
className="text-[10px] tracking-[0.38em] uppercase mb-8 font-sans font-medium"
style={{ color: "rgba(255,220,160,1)" }}
>
Akyaka · Muğla
</p>
{/* Logo */}
<img
src="/logo.png"
alt="Moy Beach"
className="w-64 md:w-80 object-contain drop-shadow-lg"
style={{ filter: 'brightness(0) invert(1)' }}
/>
<div
className="flex items-center gap-3 my-5"
style={{ color: "rgba(255,190,100,0.35)" }}
>
<div className="h-px w-14" style={{ background: "currentColor" }} />
<span className="text-base"></span>
<div className="h-px w-14" style={{ background: "currentColor" }} />
</div>
<p
className="text-xs font-sans tracking-wide"
style={{ color: "rgba(255,218,168,0.5)" }}
>
Akyaka&apos;nın en keyifli menüsü
</p>
</motion.div>
</div>
{/* wave transition */}
<div className="absolute bottom-0 left-0 right-0 translate-y-px">
<svg
viewBox="0 0 1440 52"
preserveAspectRatio="none"
className="w-full h-[52px]"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0 52L80 44C160 36 320 20 480 18C640 16 800 28 960 32C1120 36 1280 32 1360 30L1440 28V52H1360C1280 52 1120 52 960 52C800 52 640 52 480 52C320 52 160 52 80 52H0Z"
fill="#FAF8F3"
/>
</svg>
</div>
</header>
{/* ── Category Nav ─────────────────────────────── */}
<CategoryNav
categories={initialCategories}
activeCategoryId={activeCategoryId}
icons={CATEGORY_ICONS}
/>
{/* ── Menu Sections ────────────────────────────── */}
<main className="pb-28">
{initialCategories.map((category, index) => (
<motion.section
key={category.id}
id={category.id}
ref={(el) => { sectionRefs.current[index] = el; }}
className="pt-10 px-4 max-w-lg mx-auto scroll-mt-[62px]"
initial={{ opacity: 0, y: 14 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-50px" }}
transition={{ duration: 0.5, ease: [0.16, 1, 0.3, 1] }}
>
{/* section header */}
<div
className="flex items-center gap-2.5 mb-4 pb-3 border-b"
style={{ borderColor: "var(--sand-border)" }}
>
<span className="text-xl leading-none select-none">
{CATEGORY_ICONS[category.id] ?? "🍽️"}
</span>
<h2
className="font-display text-[22px] leading-tight font-semibold italic"
style={{ color: "var(--stone-ink)" }}
>
{category.title}
</h2>
</div>
<div>
{category.items.map((item, idx) => (
<MenuItem key={idx} item={item} onClick={() => setSelectedItem(item)} />
))}
</div>
</motion.section>
))}
</main>
{/* ── Footer ───────────────────────────────────── */}
<footer
className="py-12 text-center"
style={{ background: "var(--hero-from)" }}
>
<div className="flex flex-col items-center gap-1">
{/* Footer Logo */}
<img
src="/logo.png"
alt="Moy Beach"
className="w-36 object-contain opacity-70 mb-2"
style={{ filter: 'brightness(0) invert(1)' }}
/>
<p
className="text-[10px] font-sans tracking-[0.3em] uppercase mt-1"
style={{ color: "rgba(255,200,130,0.35)" }}
>
Akyaka, Muğla
</p>
<div
className="flex items-center gap-3 my-4"
style={{ color: "rgba(160,100,50,0.4)" }}
>
<div className="h-px w-10" style={{ background: "currentColor" }} />
<span className="text-xs"></span>
<div className="h-px w-10" style={{ background: "currentColor" }} />
</div>
<p
className="text-[11px] font-sans"
style={{ color: "rgba(120,90,60,0.5)" }}
>
© 2026 Moy Beach. Tüm hakları saklıdır.
</p>
</div>
</footer>
{/* ── Modal ────────────────────────────────────── */}
<AnimatePresence>
{selectedItem && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/40 backdrop-blur-sm"
onClick={() => setSelectedItem(null)}
>
<motion.div
initial={{ y: 50, opacity: 0, scale: 0.95 }}
animate={{ y: 0, opacity: 1, scale: 1 }}
exit={{ y: 20, opacity: 0, scale: 0.95 }}
className="bg-white rounded-2xl overflow-hidden w-full max-w-sm shadow-xl"
onClick={(e) => e.stopPropagation()}
>
{selectedItem.image && (
<div className="w-full h-56 relative">
<img src={selectedItem.image} alt={selectedItem.name} className="w-full h-full object-cover" />
<button
className="absolute top-3 right-3 bg-black/50 text-white w-8 h-8 rounded-full flex items-center justify-center backdrop-blur-md transition-colors hover:bg-black/70"
onClick={() => setSelectedItem(null)}
>
</button>
</div>
)}
<div className="p-5">
<div className="flex justify-between items-start gap-4 mb-2">
<h3 className="text-xl font-semibold font-sans" style={{ color: "var(--stone-ink)" }}>
{selectedItem.name}
</h3>
<span className="shrink-0 text-lg font-semibold font-sans tabular-nums" style={{ color: "var(--amber)" }}>
{selectedItem.price}
</span>
</div>
<p className="text-[14px] leading-relaxed font-sans" style={{ color: "var(--stone-muted)" }}>
{selectedItem.description}
</p>
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}
@@ -0,0 +1,86 @@
'use client'
import { useActionState } from 'react'
import { createCategory, updateCategory } from './actions'
import Link from 'next/link'
type Category = {
id: number
name_tr: string
order_num: number
status: number | null
}
export default function CategoryForm({ category }: { category?: Category }) {
const isEditing = !!category
// Use appropriate action based on editing state
const actionToUse = isEditing
? updateCategory.bind(null, category.id)
: createCategory
const [state, formAction, isPending] = useActionState(actionToUse, undefined)
return (
<form action={formAction} className="space-y-6">
{state?.error && (
<div className="bg-red-50 text-red-700 p-3 rounded-lg text-sm">
{state.error}
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Kategori Adı (TR) *
</label>
<input
type="text"
name="name_tr"
defaultValue={category?.name_tr || ''}
required
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-colors"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Sıra Numarası
</label>
<input
type="number"
name="order_num"
defaultValue={category?.order_num || 0}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-colors"
/>
</div>
</div>
<div className="flex items-center gap-2">
<input
type="checkbox"
name="status"
id="status"
defaultChecked={category ? category.status === 1 : true}
className="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
/>
<label htmlFor="status" className="text-sm font-medium text-gray-700">
Aktif
</label>
</div>
<div className="flex justify-end gap-3 pt-4 border-t">
<Link href="/admin/categories" className="px-6 py-2 border border-gray-300 text-gray-700 rounded-lg font-medium hover:bg-gray-50 transition-colors">
İptal
</Link>
<button
type="submit"
disabled={isPending}
className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-6 rounded-lg transition-colors disabled:opacity-70"
>
{isPending ? 'Kaydediliyor...' : 'Kaydet'}
</button>
</div>
</form>
)
}
@@ -0,0 +1,23 @@
'use client'
import { Trash2 } from 'lucide-react'
import { deleteCategory } from './actions'
export default function DeleteButton({ id }: { id: number }) {
return (
<button
onClick={async () => {
if (confirm('Bu kategoriyi silmek istediğinize emin misiniz?')) {
const res = await deleteCategory(id)
if (res?.error) {
alert(res.error)
}
}
}}
className="p-2 text-red-600 hover:bg-red-50 rounded-lg transition-colors"
title="Sil"
>
<Trash2 size={18} />
</button>
)
}
@@ -0,0 +1,34 @@
import prisma from '@/lib/prisma'
import CategoryForm from '../../CategoryForm'
import { notFound } from 'next/navigation'
export const metadata = {
title: 'Kategori Düzenle - Admin',
}
export default async function EditCategoryPage({ params }: { params: Promise<{ id: string }> }) {
const resolvedParams = await params
const id = parseInt(resolvedParams.id, 10)
if (isNaN(id)) {
notFound()
}
const category = await prisma.categories.findUnique({
where: { id }
})
if (!category) {
notFound()
}
return (
<div className="space-y-6">
<h1 className="text-2xl font-bold text-gray-900">Kategori Düzenle</h1>
<div className="bg-white p-6 rounded-xl shadow-sm border border-gray-100 max-w-2xl">
<CategoryForm category={category} />
</div>
</div>
)
}
@@ -0,0 +1,90 @@
'use server'
import prisma from '@/lib/prisma'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
export async function createCategory(_prevState: unknown, formData: FormData) {
const name_tr = formData.get('name_tr') as string
const name = formData.get('name') as string || name_tr
const order_num = parseInt(formData.get('order_num') as string, 10) || 0
const status = formData.get('status') === 'on' ? 1 : 0
if (!name_tr) {
return { error: 'Lütfen kategori adını doldurun.' }
}
try {
await prisma.categories.create({
data: {
name,
name_tr,
slug: name_tr.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
order_num,
status,
}
})
} catch (error) {
console.error('Create category error:', error)
return { error: 'Kategori eklenirken hata oluştu.' }
}
revalidatePath('/admin/categories')
revalidatePath('/')
redirect('/admin/categories')
}
export async function updateCategory(id: number, _prevState: unknown, formData: FormData) {
const name_tr = formData.get('name_tr') as string
const name = formData.get('name') as string || name_tr
const order_num = parseInt(formData.get('order_num') as string, 10) || 0
const status = formData.get('status') === 'on' ? 1 : 0
if (!name_tr) {
return { error: 'Lütfen kategori adını doldurun.' }
}
try {
await prisma.categories.update({
where: { id },
data: {
name,
name_tr,
slug: name_tr.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
order_num,
status,
}
})
} catch (error) {
console.error('Update category error:', error)
return { error: 'Kategori güncellenirken hata oluştu.' }
}
revalidatePath('/admin/categories')
revalidatePath('/')
redirect('/admin/categories')
}
export async function deleteCategory(id: number) {
try {
// Check if category has products
const productsCount = await prisma.products.count({
where: { category_id: id }
})
if (productsCount > 0) {
return { error: 'Bu kategoriye ait ürünler olduğu için silinemez. Önce ürünleri silin veya başka kategoriye taşıyın.' }
}
await prisma.categories.delete({
where: { id }
})
revalidatePath('/admin/categories')
revalidatePath('/')
return { success: true }
} catch (error) {
console.error('Delete category error:', error)
return { error: 'Silme işlemi başarısız oldu.' }
}
}
@@ -0,0 +1,17 @@
import CategoryForm from '../CategoryForm'
export const metadata = {
title: 'Yeni Kategori Ekle - Admin',
}
export default function NewCategoryPage() {
return (
<div className="space-y-6">
<h1 className="text-2xl font-bold text-gray-900">Yeni Kategori Ekle</h1>
<div className="bg-white p-6 rounded-xl shadow-sm border border-gray-100 max-w-2xl">
<CategoryForm />
</div>
</div>
)
}
+81
View File
@@ -0,0 +1,81 @@
import prisma from '@/lib/prisma'
import { Pencil, Plus } from 'lucide-react'
import Link from 'next/link'
import DeleteButton from './DeleteButton'
export const metadata = {
title: 'Kategoriler - Admin',
}
export default async function CategoriesPage() {
const categories = await prisma.categories.findMany({
orderBy: { order_num: 'asc' },
include: {
_count: {
select: { products: true }
}
}
})
return (
<div className="space-y-6">
<div className="flex justify-between items-center">
<h1 className="text-2xl font-bold text-gray-900">Kategoriler</h1>
<Link href="/admin/categories/new" className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg font-medium flex items-center gap-2 transition-colors">
<Plus size={18} /> Yeni Kategori Ekle
</Link>
</div>
<div className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse">
<thead>
<tr className="bg-gray-50 border-b border-gray-100 text-sm font-semibold text-gray-600">
<th className="py-4 px-6">Kategori Adı</th>
<th className="py-4 px-6 text-center">Sıra</th>
<th className="py-4 px-6 text-center">Ürün Sayısı</th>
<th className="py-4 px-6 text-center">Durum</th>
<th className="py-4 px-6 text-right">İşlemler</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-100">
{categories.map((category) => (
<tr key={category.id} className="hover:bg-gray-50 transition-colors">
<td className="py-4 px-6 font-medium text-gray-900">{category.name_tr}</td>
<td className="py-4 px-6 text-center text-gray-600">{category.order_num}</td>
<td className="py-4 px-6 text-center">
<span className="bg-blue-50 text-blue-700 py-1 px-3 rounded-full text-xs font-medium">
{category._count.products} Ürün
</span>
</td>
<td className="py-4 px-6 text-center">
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
category.status === 1 ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'
}`}>
{category.status === 1 ? 'Aktif' : 'Pasif'}
</span>
</td>
<td className="py-4 px-6 text-right">
<div className="flex justify-end gap-2">
<Link href={`/admin/categories/${category.id}/edit`} className="p-2 text-blue-600 hover:bg-blue-50 rounded-lg transition-colors" title="Düzenle">
<Pencil size={18} />
</Link>
<DeleteButton id={category.id} />
</div>
</td>
</tr>
))}
{categories.length === 0 && (
<tr>
<td colSpan={5} className="py-8 text-center text-gray-500">
Henüz kategori bulunmuyor.
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
</div>
)
}
+62
View File
@@ -0,0 +1,62 @@
import Link from 'next/link'
import { LayoutDashboard, LogOut, Package, Settings, Tags, Users2 } from 'lucide-react'
import { logout } from '@/lib/auth'
import { redirect } from 'next/navigation'
export default function AdminLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="flex h-screen bg-gray-100 overflow-hidden">
{/* Sidebar */}
<aside className="w-64 bg-white shadow-md flex flex-col hidden md:flex">
<div className="p-4 border-b">
<h2 className="text-xl font-bold text-gray-800">Admin Panel</h2>
</div>
<nav className="flex-1 p-4 space-y-2">
<Link href="/admin" className="flex items-center gap-3 px-3 py-2 text-gray-700 rounded-lg hover:bg-gray-100 transition-colors">
<LayoutDashboard size={20} />
<span>Dashboard</span>
</Link>
<Link href="/admin/categories" className="flex items-center gap-3 px-3 py-2 text-gray-700 rounded-lg hover:bg-gray-100 transition-colors">
<Tags size={20} />
<span>Kategoriler</span>
</Link>
<Link href="/admin/products" className="flex items-center gap-3 px-3 py-2 text-gray-700 rounded-lg hover:bg-gray-100 transition-colors">
<Package size={20} />
<span>Ürünler</span>
</Link>
<Link href="/admin/settings" className="flex items-center gap-3 px-3 py-2 text-gray-700 rounded-lg hover:bg-gray-100 transition-colors">
<Settings size={20} />
<span>Ayarlar</span>
</Link>
<Link href="/admin/users" className="flex items-center gap-3 px-3 py-2 text-gray-700 rounded-lg hover:bg-gray-100 transition-colors">
<Users2 size={20} />
<span>Kullanıcılar</span>
</Link>
</nav>
<div className="p-4 border-t">
<form action={async () => {
'use server'
await logout()
redirect('/admin/login')
}}>
<button className="flex items-center gap-3 w-full px-3 py-2 text-red-600 rounded-lg hover:bg-red-50 transition-colors">
<LogOut size={20} />
<span>Çıkış Yap</span>
</button>
</form>
</div>
</aside>
{/* Main Content */}
<main className="flex-1 overflow-y-auto bg-gray-50 p-6 md:p-8">
{children}
</main>
{/* Mobile Bottom Nav (Optional, simpler for now just a top bar) */}
</div>
)
}
+72
View File
@@ -0,0 +1,72 @@
import prisma from '@/lib/prisma'
import { Package, Tags, Eye, TrendingUp } from 'lucide-react'
import Link from 'next/link'
export const metadata = {
title: 'Admin Dashboard - Moy Beach',
}
export default async function AdminDashboard() {
const [totalCategories, totalProducts, activeProducts] = await Promise.all([
prisma.categories.count(),
prisma.products.count(),
prisma.products.count({ where: { status: 1 } }),
])
return (
<div className="space-y-6">
<h1 className="text-2xl font-bold text-gray-900">Dashboard</h1>
{/* Stats Cards */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="bg-white p-6 rounded-xl shadow-sm border border-gray-100 flex items-center gap-4">
<div className="p-3 bg-blue-100 text-blue-600 rounded-lg">
<Tags size={24} />
</div>
<div>
<p className="text-sm text-gray-500 font-medium">Kategori Sayısı</p>
<p className="text-2xl font-bold text-gray-900">{totalCategories}</p>
</div>
</div>
<div className="bg-white p-6 rounded-xl shadow-sm border border-gray-100 flex items-center gap-4">
<div className="p-3 bg-indigo-100 text-indigo-600 rounded-lg">
<Package size={24} />
</div>
<div>
<p className="text-sm text-gray-500 font-medium">Toplam Ürün</p>
<p className="text-2xl font-bold text-gray-900">{totalProducts}</p>
</div>
</div>
<div className="bg-white p-6 rounded-xl shadow-sm border border-gray-100 flex items-center gap-4">
<div className="p-3 bg-green-100 text-green-600 rounded-lg">
<TrendingUp size={24} />
</div>
<div>
<p className="text-sm text-gray-500 font-medium">Aktif Ürünler</p>
<p className="text-2xl font-bold text-gray-900">{activeProducts}</p>
</div>
</div>
</div>
{/* Quick Actions */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="bg-white p-6 rounded-xl shadow-sm border border-gray-100">
<h2 className="text-lg font-semibold mb-4">Hızlı Kısayollar</h2>
<div className="flex flex-col gap-3">
<Link href="/admin/products" className="text-blue-600 hover:text-blue-800 font-medium flex items-center gap-2">
<Package size={18} /> Ürünleri Yönet
</Link>
<Link href="/admin/categories" className="text-blue-600 hover:text-blue-800 font-medium flex items-center gap-2">
<Tags size={18} /> Kategorileri Yönet
</Link>
<Link href="/" target="_blank" className="text-gray-600 hover:text-gray-800 font-medium flex items-center gap-2">
<Eye size={18} /> Canlı Menüyü Görüntüle
</Link>
</div>
</div>
</div>
</div>
)
}
@@ -0,0 +1,20 @@
'use client'
import { Trash2 } from 'lucide-react'
import { deleteProduct } from './actions'
export default function DeleteButton({ id }: { id: number }) {
return (
<button
onClick={async () => {
if (confirm('Bu ürünü silmek istediğinize emin misiniz?')) {
await deleteProduct(id)
}
}}
className="p-2 text-red-600 hover:bg-red-50 rounded-lg transition-colors"
title="Sil"
>
<Trash2 size={18} />
</button>
)
}
@@ -0,0 +1,210 @@
'use client'
import { useActionState, useState, useRef } from 'react'
import { createProduct, updateProduct } from './actions'
import Link from 'next/link'
import Image from 'next/image'
import { Upload, X, Loader2 } from 'lucide-react'
const DEFAULT_IMAGE = '/default-product.png'
type Category = {
id: number
name_tr: string
}
type Product = {
id: number
name_tr: string
price: number | string
category_id: number | null
description_tr: string | null
image_url: string | null
status: number | null
}
export default function ProductForm({ categories, product }: { categories: Category[], product?: Product }) {
const isEditing = !!product
const actionToUse = isEditing
? updateProduct.bind(null, product.id)
: createProduct
const [state, formAction, isPending] = useActionState(actionToUse, undefined)
const [imageUrl, setImageUrl] = useState<string>(product?.image_url || '')
const [uploading, setUploading] = useState(false)
const [uploadError, setUploadError] = useState('')
const fileInputRef = useRef<HTMLInputElement>(null)
const previewSrc = imageUrl || DEFAULT_IMAGE
async function handleFileChange(e: React.ChangeEvent<HTMLInputElement>) {
const file = e.target.files?.[0]
if (!file) return
if (file.size > 5 * 1024 * 1024) {
setUploadError("Dosya boyutu 5MB'ı geçemez.")
return
}
setUploadError('')
setUploading(true)
try {
const fd = new FormData()
fd.append('file', file)
const res = await fetch('/api/upload', { method: 'POST', body: fd })
const data = await res.json()
if (!res.ok) throw new Error(data.error || 'Yükleme başarısız')
setImageUrl(data.url)
} catch (err: any) {
setUploadError(err.message || 'Resim yüklenirken hata oluştu.')
} finally {
setUploading(false)
}
}
return (
<form action={formAction} className="space-y-6">
{state?.error && (
<div className="bg-red-50 text-red-700 p-3 rounded-lg text-sm">
{state.error}
</div>
)}
{/* Image Upload */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Ürün Resmi</label>
<div className="flex items-start gap-4">
{/* Preview */}
<div className="relative w-28 h-28 rounded-xl overflow-hidden border-2 border-gray-200 bg-gray-50 flex-shrink-0">
<Image
src={previewSrc}
alt="Ürün resmi önizleme"
fill
className="object-cover"
/>
{imageUrl && (
<button
type="button"
onClick={() => {
setImageUrl('')
if (fileInputRef.current) fileInputRef.current.value = ''
}}
className="absolute top-1 right-1 bg-red-500 text-white rounded-full p-0.5 hover:bg-red-600 transition-colors"
>
<X size={12} />
</button>
)}
</div>
{/* Upload button */}
<div className="flex flex-col gap-2 justify-center h-28">
<button
type="button"
onClick={() => fileInputRef.current?.click()}
disabled={uploading}
className="flex items-center gap-2 px-4 py-2 border-2 border-dashed border-gray-300 rounded-lg text-sm text-gray-600 hover:border-blue-400 hover:text-blue-600 transition-colors disabled:opacity-50"
>
{uploading
? <><Loader2 size={16} className="animate-spin" /> Yükleniyor...</>
: <><Upload size={16} /> Resim Yükle</>
}
</button>
<p className="text-xs text-gray-500">PNG, JPG, WEBP Maks. 5MB</p>
{uploadError && <p className="text-xs text-red-600">{uploadError}</p>}
{!imageUrl && (
<p className="text-xs text-gray-400 italic">Resim yüklenmezse varsayılan görsel kullanılır.</p>
)}
</div>
</div>
<input
ref={fileInputRef}
type="file"
accept="image/*"
className="hidden"
onChange={handleFileChange}
/>
{/* URL'yi form verisiyle gönder */}
<input type="hidden" name="image_url" value={imageUrl} />
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Ürün Adı (TR) *</label>
<input
type="text"
name="name_tr"
defaultValue={product?.name_tr || ''}
required
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-colors"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Kategori *</label>
<select
name="category_id"
defaultValue={product?.category_id?.toString() || ''}
required
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-colors"
>
<option value="" disabled>Seçiniz</option>
{categories.map(c => (
<option key={c.id} value={c.id}>{c.name_tr}</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Fiyat () *</label>
<input
type="number"
step="0.01"
name="price"
defaultValue={product ? product.price.toString() : ''}
required
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-colors"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Açıklama (TR)</label>
<textarea
name="description_tr"
defaultValue={product?.description_tr || ''}
rows={3}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-colors"
/>
</div>
<div className="flex items-center gap-2">
<input
type="checkbox"
name="status"
id="status"
defaultChecked={product ? product.status === 1 : true}
className="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
/>
<label htmlFor="status" className="text-sm font-medium text-gray-700">Aktif</label>
</div>
<div className="flex justify-end gap-3 pt-4 border-t">
<Link
href="/admin/products"
className="px-6 py-2 border border-gray-300 text-gray-700 rounded-lg font-medium hover:bg-gray-50 transition-colors"
>
İptal
</Link>
<button
type="submit"
disabled={isPending || uploading}
className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-6 rounded-lg transition-colors disabled:opacity-70"
>
{isPending ? 'Kaydediliyor...' : 'Kaydet'}
</button>
</div>
</form>
)
}
@@ -0,0 +1,46 @@
import prisma from '@/lib/prisma'
import ProductForm from '../../ProductForm'
import { notFound } from 'next/navigation'
export const metadata = {
title: 'Ürün Düzenle - Admin',
}
export default async function EditProductPage({ params }: { params: Promise<{ id: string }> }) {
const resolvedParams = await params
const id = parseInt(resolvedParams.id, 10)
if (isNaN(id)) {
notFound()
}
const [categories, product] = await Promise.all([
prisma.categories.findMany({
orderBy: { order_num: 'asc' },
select: { id: true, name_tr: true }
}),
prisma.products.findUnique({
where: { id }
})
])
if (!product) {
notFound()
}
return (
<div className="space-y-6">
<h1 className="text-2xl font-bold text-gray-900">Ürün Düzenle</h1>
<div className="bg-white p-6 rounded-xl shadow-sm border border-gray-100 max-w-3xl">
<ProductForm
categories={categories}
product={{
...product,
price: product.price.toString() // Convert Decimal to string
}}
/>
</div>
</div>
)
}
+92
View File
@@ -0,0 +1,92 @@
'use server'
import prisma from '@/lib/prisma'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
export async function createProduct(_prevState: unknown, formData: FormData) {
const name_tr = formData.get('name_tr') as string
const name = formData.get('name') as string || name_tr
const price = parseFloat(formData.get('price') as string)
const category_id = parseInt(formData.get('category_id') as string, 10)
const description_tr = formData.get('description_tr') as string || ''
const image_url = formData.get('image_url') as string || null
const status = formData.get('status') === 'on' ? 1 : 0
if (!name_tr || isNaN(price) || isNaN(category_id)) {
return { error: 'Lütfen zorunlu alanları doldurun.' }
}
try {
await prisma.products.create({
data: {
name,
name_tr,
slug: name_tr.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
price,
category_id,
description_tr,
image_url,
status,
}
})
} catch (error) {
console.error('Create product error:', error)
return { error: 'Ürün eklenirken hata oluştu.' }
}
revalidatePath('/admin/products')
revalidatePath('/')
redirect('/admin/products')
}
export async function updateProduct(id: number, _prevState: unknown, formData: FormData) {
const name_tr = formData.get('name_tr') as string
const name = formData.get('name') as string || name_tr
const price = parseFloat(formData.get('price') as string)
const category_id = parseInt(formData.get('category_id') as string, 10)
const description_tr = formData.get('description_tr') as string || ''
const image_url = formData.get('image_url') as string || null
const status = formData.get('status') === 'on' ? 1 : 0
if (!name_tr || isNaN(price) || isNaN(category_id)) {
return { error: 'Lütfen zorunlu alanları doldurun.' }
}
try {
await prisma.products.update({
where: { id },
data: {
name,
name_tr,
slug: name_tr.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
price,
category_id,
description_tr,
image_url,
status,
}
})
} catch (error) {
console.error('Update product error:', error)
return { error: 'Ürün güncellenirken hata oluştu.' }
}
revalidatePath('/admin/products')
revalidatePath('/')
redirect('/admin/products')
}
export async function deleteProduct(id: number) {
try {
await prisma.products.delete({
where: { id }
})
revalidatePath('/admin/products')
revalidatePath('/')
return { success: true }
} catch (error) {
console.error('Delete product error:', error)
return { error: 'Silme işlemi başarısız oldu.' }
}
}
@@ -0,0 +1,23 @@
import prisma from '@/lib/prisma'
import ProductForm from '../ProductForm'
export const metadata = {
title: 'Yeni Ürün Ekle - Admin',
}
export default async function NewProductPage() {
const categories = await prisma.categories.findMany({
orderBy: { order_num: 'asc' },
select: { id: true, name_tr: true }
})
return (
<div className="space-y-6">
<h1 className="text-2xl font-bold text-gray-900">Yeni Ürün Ekle</h1>
<div className="bg-white p-6 rounded-xl shadow-sm border border-gray-100 max-w-3xl">
<ProductForm categories={categories} />
</div>
</div>
)
}
+100
View File
@@ -0,0 +1,100 @@
import prisma from '@/lib/prisma'
import { Pencil, Plus, Trash2, Image as ImageIcon } from 'lucide-react'
import Image from 'next/image'
import Link from 'next/link'
import DeleteButton from './DeleteButton'
export const metadata = {
title: 'Ürün Yönetimi - Admin',
}
export default async function ProductsPage() {
const categories = await prisma.categories.findMany({
orderBy: { order_num: 'asc' },
include: {
products: {
orderBy: { id: 'asc' },
},
},
})
return (
<div className="space-y-6">
<div className="flex justify-between items-center">
<h1 className="text-2xl font-bold text-gray-900">Ürün Yönetimi</h1>
<Link href="/admin/products/new" className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg flex items-center gap-2 transition-colors">
<Plus size={20} />
<span>Yeni Ürün Ekle</span>
</Link>
</div>
<div className="space-y-8">
{categories.map((category) => (
<div key={category.id} className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
<div className="bg-gray-50 px-6 py-4 border-b border-gray-100 flex justify-between items-center">
<h2 className="text-lg font-bold text-gray-800">{category.name_tr}</h2>
<span className="text-sm text-gray-500 font-medium">{category.products.length} ürün</span>
</div>
{category.products.length > 0 ? (
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse">
<thead>
<tr className="border-b border-gray-100">
<th className="py-3 px-6 text-sm font-semibold text-gray-600 w-16">Resim</th>
<th className="py-3 px-6 text-sm font-semibold text-gray-600">İsim</th>
<th className="py-3 px-6 text-sm font-semibold text-gray-600">Fiyat</th>
<th className="py-3 px-6 text-sm font-semibold text-gray-600">Durum</th>
<th className="py-3 px-6 text-sm font-semibold text-gray-600 text-right">İşlemler</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-50">
{category.products.map((product) => (
<tr key={product.id} className="hover:bg-gray-50 transition-colors">
<td className="py-3 px-6">
{product.image_url ? (
<div className="relative w-12 h-12 rounded-lg overflow-hidden border border-gray-200">
<Image
src={product.image_url}
alt={product.name_tr}
fill
className="object-cover"
/>
</div>
) : (
<div className="w-12 h-12 rounded-lg bg-gray-100 flex items-center justify-center text-gray-400 border border-gray-200">
<ImageIcon size={20} />
</div>
)}
</td>
<td className="py-3 px-6 font-medium text-gray-900">{product.name_tr}</td>
<td className="py-3 px-6 text-gray-600">{product.price.toString()}</td>
<td className="py-3 px-6">
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${product.status === 1 ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}`}>
{product.status === 1 ? 'Aktif' : 'Pasif'}
</span>
</td>
<td className="py-3 px-6 text-right">
<div className="flex justify-end gap-2">
<Link href={`/admin/products/${product.id}/edit`} className="p-2 text-blue-600 hover:bg-blue-50 rounded-lg transition-colors" title="Düzenle">
<Pencil size={18} />
</Link>
<DeleteButton id={product.id} />
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
) : (
<div className="py-6 px-6 text-gray-500 text-sm">
Bu kategoride henüz ürün yok.
</div>
)}
</div>
))}
</div>
</div>
)
}
@@ -0,0 +1,158 @@
'use client'
import { useActionState, useState, useRef } from 'react'
import { saveSettings } from './actions'
import Image from 'next/image'
import { Upload, X, Loader2 } from 'lucide-react'
export default function SettingsForm({
defaultRestaurantName,
defaultLocation,
defaultLogoUrl,
}: {
defaultRestaurantName: string
defaultLocation: string
defaultLogoUrl: string
}) {
const [state, action, isPending] = useActionState(saveSettings, undefined)
const [logoUrl, setLogoUrl] = useState(defaultLogoUrl)
const [uploading, setUploading] = useState(false)
const [uploadError, setUploadError] = useState('')
const fileInputRef = useRef<HTMLInputElement>(null)
async function handleFileChange(e: React.ChangeEvent<HTMLInputElement>) {
const file = e.target.files?.[0]
if (!file) return
if (file.size > 5 * 1024 * 1024) {
setUploadError("Dosya boyutu 5MB'ı geçemez.")
return
}
setUploadError('')
setUploading(true)
try {
const fd = new FormData()
fd.append('file', file)
const res = await fetch('/api/upload', { method: 'POST', body: fd })
const data = await res.json()
if (!res.ok) throw new Error(data.error || 'Yükleme başarısız')
setLogoUrl(data.url)
} catch (err: any) {
setUploadError(err.message || 'Logo yüklenirken hata oluştu.')
} finally {
setUploading(false)
}
}
return (
<form action={action} className="space-y-6">
{state?.success && (
<div className="bg-green-50 text-green-700 p-3 rounded-lg text-sm">
{state.message}
</div>
)}
{state?.error && (
<div className="bg-red-50 text-red-700 p-3 rounded-lg text-sm">
{state.error}
</div>
)}
{/* Logo Upload */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Site Logosu
</label>
<div className="flex items-start gap-4">
{/* Preview */}
<div className="relative w-40 h-20 rounded-xl overflow-hidden border-2 border-gray-200 bg-stone-900 flex items-center justify-center flex-shrink-0">
{logoUrl ? (
<>
<Image
src={logoUrl}
alt="Logo önizleme"
fill
className="object-contain p-2"
/>
<button
type="button"
onClick={() => { setLogoUrl(''); if (fileInputRef.current) fileInputRef.current.value = '' }}
className="absolute top-1 right-1 bg-red-500 text-white rounded-full p-0.5 hover:bg-red-600 transition-colors"
>
<X size={12} />
</button>
</>
) : (
<span className="text-xs text-gray-400 text-center px-2">Logo yok<br />(varsayılan kullanılır)</span>
)}
</div>
{/* Upload button */}
<div className="flex flex-col gap-2 justify-center h-20">
<button
type="button"
onClick={() => fileInputRef.current?.click()}
disabled={uploading}
className="flex items-center gap-2 px-4 py-2 border-2 border-dashed border-gray-300 rounded-lg text-sm text-gray-600 hover:border-blue-400 hover:text-blue-600 transition-colors disabled:opacity-50"
>
{uploading
? <><Loader2 size={16} className="animate-spin" /> Yükleniyor...</>
: <><Upload size={16} /> Logo Yükle</>
}
</button>
<p className="text-xs text-gray-500">PNG, SVG, WEBP Maks. 5MB</p>
{uploadError && <p className="text-xs text-red-600">{uploadError}</p>}
</div>
</div>
<input
ref={fileInputRef}
type="file"
accept="image/*"
className="hidden"
onChange={handleFileChange}
/>
<input type="hidden" name="logo_url" value={logoUrl} />
</div>
<hr className="border-gray-100" />
{/* Restaurant Name */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Restoran / Mekan Adı
</label>
<input
type="text"
name="restaurant_name"
defaultValue={defaultRestaurantName}
required
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-colors"
/>
</div>
{/* Location */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Konum Bilgisi
</label>
<input
type="text"
name="location"
defaultValue={defaultLocation}
required
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-colors"
/>
</div>
<button
type="submit"
disabled={isPending || uploading}
className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-6 rounded-lg transition-colors disabled:opacity-70"
>
{isPending ? 'Kaydediliyor...' : 'Kaydet'}
</button>
</form>
)
}
+38
View File
@@ -0,0 +1,38 @@
'use server'
import prisma from '@/lib/prisma'
import { revalidatePath } from 'next/cache'
export async function saveSettings(_prevState: unknown, formData: FormData) {
const restaurant_name = formData.get('restaurant_name') as string
const location = formData.get('location') as string
const logo_url = formData.get('logo_url') as string
try {
await prisma.$transaction([
prisma.settings.upsert({
where: { key: 'restaurant_name' },
update: { value: restaurant_name },
create: { key: 'restaurant_name', value: restaurant_name },
}),
prisma.settings.upsert({
where: { key: 'location' },
update: { value: location },
create: { key: 'location', value: location },
}),
prisma.settings.upsert({
where: { key: 'logo_url' },
update: { value: logo_url || '' },
create: { key: 'logo_url', value: logo_url || '' },
}),
])
revalidatePath('/')
revalidatePath('/admin/settings')
return { success: true, message: 'Ayarlar başarıyla kaydedildi.' }
} catch (error) {
console.error('Settings save error:', error)
return { error: 'Ayarlar kaydedilirken bir hata oluştu.' }
}
}
+32
View File
@@ -0,0 +1,32 @@
import prisma from '@/lib/prisma'
import SettingsForm from './SettingsForm'
export const metadata = {
title: 'Genel Ayarlar - Admin',
}
export default async function SettingsPage() {
const settingsData = await prisma.settings.findMany()
const settings = settingsData.reduce((acc, curr) => {
acc[curr.key] = curr.value
return acc
}, {} as Record<string, string>)
const restaurantName = settings['restaurant_name'] || 'Moy Beach'
const location = settings['location'] || 'Akyaka · Muğla'
const logoUrl = settings['logo_url'] || ''
return (
<div className="space-y-6">
<h1 className="text-2xl font-bold text-gray-900">Genel Ayarlar</h1>
<div className="bg-white p-6 rounded-xl shadow-sm border border-gray-100 max-w-2xl">
<SettingsForm
defaultRestaurantName={restaurantName}
defaultLocation={location}
defaultLogoUrl={logoUrl}
/>
</div>
</div>
)
}
@@ -0,0 +1,74 @@
'use client'
import { useActionState } from 'react'
import { createUser } from './actions'
import { UserPlus } from 'lucide-react'
export default function CreateUserForm() {
const [state, formAction, isPending] = useActionState(createUser, undefined)
return (
<div className="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<div className="flex items-center gap-2 mb-4">
<UserPlus size={20} className="text-blue-600" />
<h2 className="text-lg font-semibold text-gray-800">Yeni Kullanıcı Ekle</h2>
</div>
{state?.success && (
<div className="bg-green-50 text-green-700 p-3 rounded-lg text-sm mb-4">
Kullanıcı başarıyla oluşturuldu.
</div>
)}
{state?.error && (
<div className="bg-red-50 text-red-700 p-3 rounded-lg text-sm mb-4">
{state.error}
</div>
)}
<form action={formAction} className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Kullanıcı Adı *</label>
<input
type="text"
name="username"
required
autoComplete="off"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none text-sm"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Şifre * <span className="text-gray-400 font-normal">(min. 6 karakter)</span></label>
<input
type="password"
name="password"
required
autoComplete="new-password"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none text-sm"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Rol</label>
<select
name="role"
defaultValue="admin"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none text-sm"
>
<option value="admin">Admin</option>
<option value="editor">Editor</option>
</select>
</div>
<div className="sm:col-span-3 flex justify-end">
<button
type="submit"
disabled={isPending}
className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-5 rounded-lg text-sm transition-colors disabled:opacity-70 flex items-center gap-2"
>
<UserPlus size={16} />
{isPending ? 'Oluşturuluyor...' : 'Kullanıcı Oluştur'}
</button>
</div>
</form>
</div>
)
}
+135
View File
@@ -0,0 +1,135 @@
'use client'
import { useActionState, useState } from 'react'
import { changePassword, deleteUser } from './actions'
import { KeyRound, Trash2, ChevronDown, ChevronUp, Shield } from 'lucide-react'
type User = {
id: number
username: string
role: string
created_at: Date
}
function ChangePasswordForm({ userId }: { userId: number }) {
const action = changePassword.bind(null, userId)
const [state, formAction, isPending] = useActionState(action, undefined)
return (
<form action={formAction} className="mt-3 p-4 bg-gray-50 rounded-lg border border-gray-200 space-y-3">
{state?.success && (
<p className="text-xs text-green-700 bg-green-50 px-3 py-2 rounded-lg">{state.message}</p>
)}
{state?.error && (
<p className="text-xs text-red-700 bg-red-50 px-3 py-2 rounded-lg">{state.error}</p>
)}
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-xs font-medium text-gray-600 mb-1">Yeni Şifre</label>
<input
type="password"
name="new_password"
required
autoComplete="new-password"
placeholder="Min. 6 karakter"
className="w-full px-3 py-1.5 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 outline-none"
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-600 mb-1">Şifre Tekrar</label>
<input
type="password"
name="confirm_password"
required
autoComplete="new-password"
placeholder="Tekrar giriniz"
className="w-full px-3 py-1.5 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 outline-none"
/>
</div>
</div>
<div className="flex justify-end">
<button
type="submit"
disabled={isPending}
className="bg-amber-500 hover:bg-amber-600 text-white text-sm font-medium py-1.5 px-4 rounded-lg transition-colors disabled:opacity-70 flex items-center gap-1.5"
>
<KeyRound size={14} />
{isPending ? 'Güncelleniyor...' : 'Şifreyi Güncelle'}
</button>
</div>
</form>
)
}
function UserRow({ user }: { user: User }) {
const [expanded, setExpanded] = useState(false)
async function handleDelete() {
if (!confirm(`"${user.username}" kullanıcısını silmek istediğinize emin misiniz?`)) return
const res = await deleteUser(user.id)
if (res?.error) alert(res.error)
}
return (
<div className="border border-gray-100 rounded-xl overflow-hidden">
<div className="flex items-center justify-between px-5 py-4 hover:bg-gray-50 transition-colors">
<div className="flex items-center gap-3">
<div className="w-9 h-9 rounded-full bg-blue-100 flex items-center justify-center text-blue-700 font-semibold text-sm uppercase">
{user.username[0]}
</div>
<div>
<p className="font-medium text-gray-900 text-sm">{user.username}</p>
<p className="text-xs text-gray-400">
{new Date(user.created_at).toLocaleDateString('tr-TR', { day: 'numeric', month: 'long', year: 'numeric' })}
</p>
</div>
</div>
<div className="flex items-center gap-2">
<span className={`inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-medium ${
user.role === 'admin' ? 'bg-purple-100 text-purple-700' : 'bg-gray-100 text-gray-600'
}`}>
<Shield size={10} />
{user.role}
</span>
<button
onClick={() => setExpanded(v => !v)}
className="flex items-center gap-1.5 px-3 py-1.5 text-xs text-amber-600 bg-amber-50 hover:bg-amber-100 rounded-lg transition-colors font-medium"
>
<KeyRound size={13} />
Şifre
{expanded ? <ChevronUp size={13} /> : <ChevronDown size={13} />}
</button>
<button
onClick={handleDelete}
className="p-2 text-red-500 hover:bg-red-50 rounded-lg transition-colors"
title="Kullanıcıyı Sil"
>
<Trash2 size={16} />
</button>
</div>
</div>
{expanded && <ChangePasswordForm userId={user.id} />}
</div>
)
}
export default function UserList({ users }: { users: User[] }) {
return (
<div className="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<h2 className="text-lg font-semibold text-gray-800 mb-4">Mevcut Kullanıcılar ({users.length})</h2>
{users.length === 0 ? (
<p className="text-sm text-gray-500 text-center py-6">Henüz kullanıcı yok.</p>
) : (
<div className="space-y-3">
{users.map(user => (
<UserRow key={user.id} user={user} />
))}
</div>
)}
</div>
)
}
+71
View File
@@ -0,0 +1,71 @@
'use server'
import prisma from '@/lib/prisma'
import bcrypt from 'bcryptjs'
import { revalidatePath } from 'next/cache'
export async function createUser(_prevState: unknown, formData: FormData) {
const username = (formData.get('username') as string)?.trim()
const password = formData.get('password') as string
const role = (formData.get('role') as string) || 'admin'
if (!username || !password) {
return { error: 'Kullanıcı adı ve şifre zorunludur.' }
}
if (password.length < 6) {
return { error: 'Şifre en az 6 karakter olmalıdır.' }
}
try {
const existing = await prisma.users.findUnique({ where: { username } })
if (existing) {
return { error: 'Bu kullanıcı adı zaten kullanılıyor.' }
}
const password_hash = await bcrypt.hash(password, 12)
await prisma.users.create({ data: { username, password_hash, role } })
revalidatePath('/admin/users')
return { success: true }
} catch (error) {
console.error('Create user error:', error)
return { error: 'Kullanıcı oluşturulurken hata oluştu.' }
}
}
export async function changePassword(userId: number, _prevState: unknown, formData: FormData) {
const newPassword = formData.get('new_password') as string
const confirmPassword = formData.get('confirm_password') as string
if (!newPassword || newPassword.length < 6) {
return { error: 'Şifre en az 6 karakter olmalıdır.' }
}
if (newPassword !== confirmPassword) {
return { error: 'Şifreler eşleşmiyor.' }
}
try {
const password_hash = await bcrypt.hash(newPassword, 12)
await prisma.users.update({ where: { id: userId }, data: { password_hash } })
revalidatePath('/admin/users')
return { success: true, message: 'Şifre güncellendi.' }
} catch (error) {
console.error('Change password error:', error)
return { error: 'Şifre güncellenirken hata oluştu.' }
}
}
export async function deleteUser(userId: number) {
try {
// Count total users — don't allow deleting the last admin
const count = await prisma.users.count()
if (count <= 1) {
return { error: 'Son yönetici kullanıcı silinemez.' }
}
await prisma.users.delete({ where: { id: userId } })
revalidatePath('/admin/users')
return { success: true }
} catch (error) {
console.error('Delete user error:', error)
return { error: 'Kullanıcı silinirken hata oluştu.' }
}
}
+27
View File
@@ -0,0 +1,27 @@
import prisma from '@/lib/prisma'
import CreateUserForm from './CreateUserForm'
import UserList from './UserList'
export const metadata = {
title: 'Kullanıcı Yönetimi - Admin',
}
export default async function UsersPage() {
const users = await prisma.users.findMany({
orderBy: { created_at: 'asc' },
select: {
id: true,
username: true,
role: true,
created_at: true,
}
})
return (
<div className="space-y-6">
<h1 className="text-2xl font-bold text-gray-900">Kullanıcı Yönetimi</h1>
<CreateUserForm />
<UserList users={users} />
</div>
)
}
+52
View File
@@ -0,0 +1,52 @@
'use client'
import { useActionState } from 'react'
import { authenticate } from './actions'
export default function LoginForm() {
const [state, action, isPending] = useActionState(authenticate, undefined)
return (
<form action={action} className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Kullanıcı Adı
</label>
<input
name="username"
type="text"
required
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-colors"
placeholder="admin"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Şifre
</label>
<input
name="password"
type="password"
required
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-colors"
placeholder="••••••••"
/>
</div>
{state?.error && (
<div className="text-red-500 text-sm bg-red-50 p-3 rounded-lg">
{state.error}
</div>
)}
<button
type="submit"
disabled={isPending}
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-lg transition-colors disabled:opacity-70"
>
{isPending ? 'Giriş Yapılıyor...' : 'Giriş Yap'}
</button>
</form>
)
}
+40
View File
@@ -0,0 +1,40 @@
'use server'
import { login } from '@/lib/auth'
import prisma from '@/lib/prisma'
import bcrypt from 'bcryptjs'
import { redirect } from 'next/navigation'
export async function authenticate(prevState: any, formData: FormData) {
const username = formData.get('username') as string
const password = formData.get('password') as string
if (!username || !password) {
return { error: 'Lütfen tüm alanları doldurun.' }
}
try {
// Check user in database
const user = await prisma.users.findUnique({
where: { username }
})
if (!user) {
return { error: 'Kullanıcı adı veya şifre hatalı.' }
}
const passwordsMatch = await bcrypt.compare(password, user.password_hash)
if (!passwordsMatch) {
return { error: 'Kullanıcı adı veya şifre hatalı.' }
}
await login(username)
} catch (error) {
console.error('Login error:', error)
return { error: 'Bir hata oluştu, lütfen tekrar deneyin.' }
}
// Redirect to admin dashboard after successful login
redirect('/admin')
}
+26
View File
@@ -0,0 +1,26 @@
import { redirect } from 'next/navigation'
import { getSession } from '@/lib/auth'
import LoginForm from './LoginForm'
export const metadata = {
title: 'Admin Login - Moy Beach',
}
export default async function LoginPage() {
const session = await getSession()
if (session) {
redirect('/admin')
}
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="max-w-md w-full p-8 bg-white rounded-xl shadow-lg">
<div className="text-center mb-8">
<h1 className="text-2xl font-bold text-gray-900">Admin Girişi</h1>
<p className="text-gray-500 mt-2">Yönetim paneline erişmek için giriş yapın</p>
</div>
<LoginForm />
</div>
</div>
)
}
+32
View File
@@ -0,0 +1,32 @@
import { NextRequest, NextResponse } from 'next/server'
import cloudinary from '@/lib/cloudinary'
export async function POST(request: NextRequest) {
try {
const formData = await request.formData()
const file = formData.get('file') as File
if (!file) {
return NextResponse.json({ error: 'Dosya bulunamadı.' }, { status: 400 })
}
// Convert file to buffer
const arrayBuffer = await file.arrayBuffer()
const buffer = Buffer.from(arrayBuffer)
const base64 = `data:${file.type};base64,${buffer.toString('base64')}`
// Upload to Cloudinary
const result = await cloudinary.uploader.upload(base64, {
folder: 'moy-qr/products',
transformation: [
{ width: 800, height: 800, crop: 'limit' },
{ quality: 'auto', fetch_format: 'auto' }
]
})
return NextResponse.json({ url: result.secure_url })
} catch (error) {
console.error('Upload error:', error)
return NextResponse.json({ error: 'Yükleme başarısız oldu.' }, { status: 500 })
}
}
+46 -264
View File
@@ -1,271 +1,53 @@
"use client"; import prisma from "@/lib/prisma";
import MenuClient from "./MenuClient";
import { MenuCategory } from "@/data/menu";
import { useEffect, useState, useRef } from "react"; async function getMenuData(): Promise<MenuCategory[]> {
import { motion, AnimatePresence } from "framer-motion"; const dbCategories = await prisma.categories.findMany({
import { menuData, MenuItem as MenuItemType } from "@/data/menu"; where: { status: 1 },
import { CategoryNav } from "@/components/CategoryNav"; orderBy: { order_num: "asc" },
import { MenuItem } from "@/components/MenuItem"; });
export const CATEGORY_ICONS: Record<string, string> = { const dbProducts = await prisma.products.findMany({
"kahvaltiliklar": "🌅", where: { status: 1 },
"kaseler": "🥗", orderBy: [{ category_id: "asc" }, { id: "asc" }],
"burger-ve-sandvic": "🍔", });
"pizzalar": "🍕",
"makarnalar": "🍝", return dbCategories.map((cat) => {
"baslangiclar": "🧆", const categoryProducts = dbProducts.filter((p) => p.category_id === cat.id);
"salatalar": "🥬", return {
"ana-yemekler": "🍽️", id: cat.slug,
"tatlilar": "🍮", title: cat.name_tr,
"i̇mza-kokteyller": "🍹", items: categoryProducts.map((p) => ({
"klasik-kokteyller": "🍸", id: p.id.toString(),
"biralar": "🍺", name: p.name_tr,
"shotlar": "🥃", description: p.description_tr,
"sise-ve-kadeh-alkollu": "🥂", price: p.price.toString() + "",
"saraplar": "🍷", image: p.image_url || '/default-product.png',
"cerezler": "🥜", })),
"alkolsuz-i̇cecekler": "🧃",
"sicak-kahveler": "☕",
"soguk-kahveler": "🧊",
"ekstralar": "✨",
}; };
});
export default function MenuPage() {
const [activeCategoryId, setActiveCategoryId] = useState<string>(
menuData[0]?.id || ""
);
const [selectedItem, setSelectedItem] = useState<MenuItemType | null>(null);
const sectionRefs = useRef<(HTMLElement | null)[]>([]);
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
const hit = entries.find((e) => e.isIntersecting);
if (hit) setActiveCategoryId(hit.target.id);
},
{ root: null, rootMargin: "-100px 0px -62% 0px", threshold: 0 }
);
sectionRefs.current.forEach((ref) => ref && observer.observe(ref));
return () => observer.disconnect();
}, []);
useEffect(() => {
if (selectedItem) {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "unset";
} }
return () => {
document.body.style.overflow = "unset";
};
}, [selectedItem]);
return ( async function getSiteSettings() {
<div className="min-h-screen" style={{ background: "var(--cream)" }}> const settingsData = await prisma.settings.findMany()
const settings = settingsData.reduce((acc, curr) => {
acc[curr.key] = curr.value
return acc
}, {} as Record<string, string>)
{/* ── Hero ─────────────────────────────────────── */} return {
<header className="relative overflow-hidden bg-stone-900"> logoUrl: settings['logo_url'] || '/logo.png',
{/* Background Image */} location: settings['location'] || 'Akyaka · Muğla',
<div restaurantName: settings['restaurant_name'] || 'Moy Beach',
className="absolute inset-0 z-0 opacity-80" }
style={{ }
backgroundImage: "url('/header-bg.jpg')",
backgroundSize: "cover", export default async function Page() {
backgroundPosition: "center" const [menuData, siteSettings] = await Promise.all([
}} getMenuData(),
/> getSiteSettings(),
]);
{/* Dark overlay to make text readable */}
<div className="absolute inset-0 z-0 bg-gradient-to-b from-black/60 via-black/40 to-black/80" /> return <MenuClient initialCategories={menuData} siteSettings={siteSettings} />;
{/* hero content */}
<div className="relative z-10 flex flex-col items-center text-center px-6 pt-16 pb-20">
<motion.div
initial={{ opacity: 0, y: 24 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.9, ease: [0.16, 1, 0.3, 1] }}
className="flex flex-col items-center"
>
<p
className="text-[10px] tracking-[0.38em] uppercase mb-8 font-sans font-medium"
style={{ color: "rgba(255,220,160,1)" }}
>
Akyaka · Muğla
</p>
<h1
className="font-display text-[82px] font-light leading-none tracking-tight"
style={{ color: "#FFFAF2" }}
>
Moy
</h1>
<h2
className="font-display text-[28px] font-light tracking-[0.22em] uppercase"
style={{ color: "rgba(255,200,120,0.85)", marginTop: "-2px" }}
>
Beach
</h2>
<div
className="flex items-center gap-3 my-5"
style={{ color: "rgba(255,190,100,0.35)" }}
>
<div className="h-px w-14" style={{ background: "currentColor" }} />
<span className="text-base"></span>
<div className="h-px w-14" style={{ background: "currentColor" }} />
</div>
<p
className="text-xs font-sans tracking-wide"
style={{ color: "rgba(255,218,168,0.5)" }}
>
Akyaka&apos;nın en keyifli menüsü
</p>
</motion.div>
</div>
{/* wave transition */}
<div className="absolute bottom-0 left-0 right-0 translate-y-px">
<svg
viewBox="0 0 1440 52"
preserveAspectRatio="none"
className="w-full h-[52px]"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0 52L80 44C160 36 320 20 480 18C640 16 800 28 960 32C1120 36 1280 32 1360 30L1440 28V52H1360C1280 52 1120 52 960 52C800 52 640 52 480 52C320 52 160 52 80 52H0Z"
fill="#FAF8F3"
/>
</svg>
</div>
</header>
{/* ── Category Nav ─────────────────────────────── */}
<CategoryNav
categories={menuData}
activeCategoryId={activeCategoryId}
icons={CATEGORY_ICONS}
/>
{/* ── Menu Sections ────────────────────────────── */}
<main className="pb-28">
{menuData.map((category, index) => (
<motion.section
key={category.id}
id={category.id}
ref={(el) => { sectionRefs.current[index] = el; }}
className="pt-10 px-4 max-w-lg mx-auto scroll-mt-[62px]"
initial={{ opacity: 0, y: 14 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-50px" }}
transition={{ duration: 0.5, ease: [0.16, 1, 0.3, 1] }}
>
{/* section header */}
<div
className="flex items-center gap-2.5 mb-4 pb-3 border-b"
style={{ borderColor: "var(--sand-border)" }}
>
<span className="text-xl leading-none select-none">
{CATEGORY_ICONS[category.id] ?? "🍽️"}
</span>
<h2
className="font-display text-[22px] leading-tight font-semibold italic"
style={{ color: "var(--stone-ink)" }}
>
{category.title}
</h2>
</div>
<div>
{category.items.map((item, idx) => (
<MenuItem key={idx} item={item} onClick={() => setSelectedItem(item)} />
))}
</div>
</motion.section>
))}
</main>
{/* ── Footer ───────────────────────────────────── */}
<footer
className="py-12 text-center"
style={{ background: "var(--hero-from)" }}
>
<div className="flex flex-col items-center gap-1">
<span
className="font-display text-3xl font-light italic"
style={{ color: "rgba(255,220,160,0.85)" }}
>
Moy Beach
</span>
<p
className="text-[10px] font-sans tracking-[0.3em] uppercase mt-1"
style={{ color: "rgba(255,200,130,0.35)" }}
>
Akyaka, Muğla
</p>
<div
className="flex items-center gap-3 my-4"
style={{ color: "rgba(160,100,50,0.4)" }}
>
<div className="h-px w-10" style={{ background: "currentColor" }} />
<span className="text-xs"></span>
<div className="h-px w-10" style={{ background: "currentColor" }} />
</div>
<p
className="text-[11px] font-sans"
style={{ color: "rgba(120,90,60,0.5)" }}
>
© 2026 Moy Beach. Tüm hakları saklıdır.
</p>
</div>
</footer>
{/* ── Modal ────────────────────────────────────── */}
<AnimatePresence>
{selectedItem && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/40 backdrop-blur-sm"
onClick={() => setSelectedItem(null)}
>
<motion.div
initial={{ y: 50, opacity: 0, scale: 0.95 }}
animate={{ y: 0, opacity: 1, scale: 1 }}
exit={{ y: 20, opacity: 0, scale: 0.95 }}
className="bg-white rounded-2xl overflow-hidden w-full max-w-sm shadow-xl"
onClick={(e) => e.stopPropagation()}
>
{selectedItem.image && (
<div className="w-full h-56 relative">
<img src={selectedItem.image} alt={selectedItem.name} className="w-full h-full object-cover" />
<button
className="absolute top-3 right-3 bg-black/50 text-white w-8 h-8 rounded-full flex items-center justify-center backdrop-blur-md transition-colors hover:bg-black/70"
onClick={() => setSelectedItem(null)}
>
</button>
</div>
)}
<div className="p-5">
<div className="flex justify-between items-start gap-4 mb-2">
<h3 className="text-xl font-semibold font-sans" style={{ color: "var(--stone-ink)" }}>
{selectedItem.name}
</h3>
<span className="shrink-0 text-lg font-semibold font-sans tabular-nums" style={{ color: "var(--amber)" }}>
{selectedItem.price}
</span>
</div>
<p className="text-[14px] leading-relaxed font-sans" style={{ color: "var(--stone-muted)" }}>
{selectedItem.description}
</p>
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</div>
);
} }
+79
View File
@@ -0,0 +1,79 @@
import { SignJWT, jwtVerify } from 'jose'
import { cookies } from 'next/headers'
import { NextRequest, NextResponse } from 'next/server'
const secretKey = process.env.JWT_SECRET || 'fallback-secret-key-do-not-use-in-prod'
const key = new TextEncoder().encode(secretKey)
export async function encrypt(payload: any) {
return await new SignJWT(payload)
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('24h')
.sign(key)
}
export async function decrypt(input: string): Promise<any> {
const { payload } = await jwtVerify(input, key, {
algorithms: ['HS256'],
})
return payload
}
export async function login(username: string) {
// Create the session
const expires = new Date(Date.now() + 24 * 60 * 60 * 1000)
const session = await encrypt({ username, expires })
// Save the session in a cookie
const cookieStore = await cookies()
cookieStore.set('session', session, {
expires,
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/',
})
}
export async function logout() {
const cookieStore = await cookies()
cookieStore.set('session', '', {
expires: new Date(0),
path: '/',
})
}
export async function getSession() {
const cookieStore = await cookies()
const session = cookieStore.get('session')?.value
if (!session) return null
try {
return await decrypt(session)
} catch (error) {
return null
}
}
export async function updateSession(request: NextRequest) {
const session = request.cookies.get('session')?.value
if (!session) return null
try {
const parsed = await decrypt(session)
parsed.expires = new Date(Date.now() + 24 * 60 * 60 * 1000)
const res = NextResponse.next()
res.cookies.set({
name: 'session',
value: await encrypt(parsed),
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/',
expires: parsed.expires,
})
return res
} catch (error) {
return null
}
}
+9
View File
@@ -0,0 +1,9 @@
import { v2 as cloudinary } from 'cloudinary'
cloudinary.config({
cloud_name: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET,
})
export default cloudinary
+19
View File
@@ -0,0 +1,19 @@
import { PrismaClient } from '@prisma/client'
import { Pool } from 'pg'
import { PrismaPg } from '@prisma/adapter-pg'
const prismaClientSingleton = () => {
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
const adapter = new PrismaPg(pool)
return new PrismaClient({ adapter })
}
declare const globalThis: {
prismaGlobal: ReturnType<typeof prismaClientSingleton>;
} & typeof global;
const prisma = globalThis.prismaGlobal ?? prismaClientSingleton()
export default prisma
if (process.env.NODE_ENV !== 'production') globalThis.prismaGlobal = prisma
+12
View File
@@ -2,6 +2,18 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
output: "standalone", output: "standalone",
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'images.unsplash.com',
},
{
protocol: 'https',
hostname: 'res.cloudinary.com',
},
],
},
/* other config options here */ /* other config options here */
}; };
+1349 -15
View File
File diff suppressed because it is too large Load Diff
+10
View File
@@ -9,19 +9,29 @@
"lint": "eslint" "lint": "eslint"
}, },
"dependencies": { "dependencies": {
"@prisma/adapter-pg": "^7.8.0",
"@prisma/client": "^7.8.0",
"bcryptjs": "^3.0.3",
"cloudinary": "^2.10.0",
"framer-motion": "^12.40.0", "framer-motion": "^12.40.0",
"jose": "^6.2.3",
"lucide-react": "^1.17.0", "lucide-react": "^1.17.0",
"next": "16.2.7", "next": "16.2.7",
"next-cloudinary": "^6.17.5",
"pg": "^8.21.0",
"react": "19.2.4", "react": "19.2.4",
"react-dom": "19.2.4" "react-dom": "19.2.4"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
"@types/bcryptjs": "^2.4.6",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
"dotenv": "^17.4.2",
"eslint": "^9", "eslint": "^9",
"eslint-config-next": "16.2.7", "eslint-config-next": "16.2.7",
"prisma": "^7.8.0",
"tailwindcss": "^4", "tailwindcss": "^4",
"typescript": "^5" "typescript": "^5"
} }
+14
View File
@@ -0,0 +1,14 @@
// This file was generated by Prisma, and assumes you have installed the following:
// npm install --save-dev prisma dotenv
import "dotenv/config";
import { defineConfig } from "prisma/config";
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
},
datasource: {
url: process.env["DATABASE_URL"],
},
});
+53
View File
@@ -0,0 +1,53 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
}
model categories {
id Int @id @default(autoincrement())
name String @db.VarChar(100)
name_tr String @db.Text
slug String @db.VarChar(100)
order_num Int? @default(0)
status Int? @default(1) @db.SmallInt
created_at DateTime @default(now()) @db.Timestamp(0)
products products[]
}
model products {
id Int @id @default(autoincrement())
category_id Int?
category categories? @relation(fields: [category_id], references: [id])
name String @db.VarChar(255)
name_tr String @db.VarChar(255)
slug String @db.VarChar(255)
short_description String? @db.VarChar(255)
description String? @db.Text
description_tr String @db.Text
price Decimal @db.Decimal(10, 2)
image_url String? @db.VarChar(255)
status Int? @default(1) @db.SmallInt
created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @default(now()) @updatedAt @db.Timestamp(0)
}
model users {
id Int @id @default(autoincrement())
username String @unique @db.VarChar(50)
password_hash String @db.VarChar(255)
role String @default("admin") @db.VarChar(50)
created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @default(now()) @updatedAt @db.Timestamp(0)
}
model settings {
key String @id @db.VarChar(100)
value String @db.Text
updated_at DateTime @default(now()) @updatedAt @db.Timestamp(0)
}
+38
View File
@@ -0,0 +1,38 @@
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { updateSession, decrypt } from './lib/auth'
export async function proxy(request: NextRequest) {
// Always update session expiration
let response = await updateSession(request)
const isAuthPage = request.nextUrl.pathname.startsWith('/admin/login')
const isAdminPage = request.nextUrl.pathname.startsWith('/admin')
// Check if session exists and is valid
const sessionValue = request.cookies.get('session')?.value
let session = null
if (sessionValue) {
try {
session = await decrypt(sessionValue)
} catch(e) {
session = null
}
}
// If trying to access admin pages (except login) without a valid session
if (isAdminPage && !isAuthPage && !session) {
return NextResponse.redirect(new URL('/admin/login', request.url))
}
// If trying to access login page with a valid session
if (isAuthPage && session) {
return NextResponse.redirect(new URL('/admin', request.url))
}
return response || NextResponse.next()
}
export const config = {
matcher: ['/admin/:path*'],
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

+39
View File
@@ -0,0 +1,39 @@
import { Client } from 'pg';
import bcrypt from 'bcryptjs';
import dotenv from 'dotenv';
dotenv.config();
const client = new Client({
connectionString: process.env.DATABASE_URL,
});
async function main() {
await client.connect();
const username = 'admin';
const password = 'password123'; // Users should change this!
const passwordHash = await bcrypt.hash(password, 10);
try {
// Check if user already exists
const res = await client.query('SELECT id FROM users WHERE username = $1', [username]);
if (res.rowCount && res.rowCount > 0) {
console.log('Admin user already exists.');
} else {
await client.query(
'INSERT INTO users (username, password_hash, role, created_at, updated_at) VALUES ($1, $2, $3, NOW(), NOW())',
[username, passwordHash, 'admin']
);
console.log('Admin user created successfully.');
console.log(`Username: ${username}`);
console.log(`Password: ${password}`);
}
} catch (error) {
console.error('Error creating admin user:', error);
} finally {
await client.end();
}
}
main();
+89
View File
@@ -0,0 +1,89 @@
-- phpMyAdmin SQL Dump
-- version 5.2.2
-- https://www.phpmyadmin.net/
--
-- Anamakine: localhost:3306
-- Üretim Zamanı: 04 Haz 2026, 22:13:32
-- Sunucu sürümü: 11.4.12-MariaDB
-- PHP Sürümü: 8.4.21
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
--
-- Veritabanı: `moybeach_menu`
--
-- --------------------------------------------------------
--
-- Tablo için tablo yapısı `categories`
--
CREATE TABLE `categories` (
`id` int(11) NOT NULL,
`name` varchar(100) NOT NULL,
`name_tr` text NOT NULL,
`slug` varchar(100) NOT NULL,
`order_num` int(11) DEFAULT 0,
`status` tinyint(1) DEFAULT 1,
`created_at` timestamp NOT NULL DEFAULT current_timestamp()
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_turkish_ci;
--
-- Tablo döküm verisi `categories`
--
INSERT INTO `categories` (`id`, `name`, `name_tr`, `slug`, `order_num`, `status`, `created_at`) VALUES
(1, 'BREAKFAST', 'KAHVALTI', 'breakfast', 1, 1, '2026-05-22 12:08:58'),
(2, 'BRUNCH', 'BRUNCH', 'brunch', 2, 1, '2026-05-22 12:09:15'),
(3, 'SANDWİCHES', 'SANDVİÇLER', 'sandwİches', 3, 1, '2026-05-22 12:09:48'),
(4, 'PIZZA', 'PİZZA', 'pizza', 4, 1, '2026-05-22 12:10:04'),
(5, 'PASTA', 'MAKARNA', 'pasta', 5, 1, '2026-05-22 12:10:55'),
(6, 'SNACK & STARTERS', 'ATIŞTIRMALIKLAR & BAŞLANGIÇLAR', 'snack-&-starters', 6, 1, '2026-05-22 12:11:24'),
(7, 'SALADS', 'SALATALAR', 'salads', 7, 1, '2026-05-22 12:11:40'),
(8, 'MAIN COURSES', 'ANA YEMEKLER', 'main-courses', 8, 1, '2026-05-22 12:11:53'),
(19, 'HOT & COLD DRINKS', 'Sıcak ve Soğuk İçecekler', 'hot-&-cold-drinks', 19, 1, '2026-05-24 12:19:46'),
(48, 'DESSERTS', 'TATLILAR', 'desserts', 9, 1, '2026-05-22 13:01:50'),
(49, 'COCKTAILS', 'KOKTEYLLER', 'cocktails', 10, 1, '2026-05-22 13:01:59'),
(50, 'CLASSIC COCKTAILS', 'KLASİK KOKTEYL', 'classic-cocktails', 11, 1, '2026-05-22 13:01:59'),
(51, 'BEERS', 'BİRALAR', 'beers', 12, 1, '2026-05-22 13:01:59'),
(52, 'SHOTS', 'SHOTLAR', 'shots', 13, 1, '2026-05-22 13:01:59'),
(53, 'BOTTLES', 'ŞİŞELER', 'bottles', 14, 1, '2026-05-22 13:01:59'),
(54, 'SOFT DRINKS', 'SOFT İÇECEKLER', 'soft-drinks', 15, 1, '2026-05-22 13:01:59'),
(55, 'BY THE GLASS', 'KADEHLER', 'by-the-glass', 16, 1, '2026-05-22 13:01:59'),
(56, 'WINES', 'ŞARAPLAR', 'wines', 17, 1, '2026-05-22 13:01:59'),
(57, 'NUTS & SNACKS', 'ÇEREZLER', 'nuts-snacks', 18, 1, '2026-05-22 13:01:59'),
(59, 'HOOKAH', 'NARGİLE', 'hookah', 20, 1, '2026-05-29 13:34:43');
--
-- Dökümü yapılmış tablolar için indeksler
--
--
-- Tablo için indeksler `categories`
--
ALTER TABLE `categories`
ADD PRIMARY KEY (`id`);
--
-- Dökümü yapılmış tablolar için AUTO_INCREMENT değeri
--
--
-- Tablo için AUTO_INCREMENT değeri `categories`
--
ALTER TABLE `categories`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=60;
COMMIT;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+245
View File
@@ -0,0 +1,245 @@
-- phpMyAdmin SQL Dump
-- version 5.2.2
-- https://www.phpmyadmin.net/
--
-- Anamakine: localhost:3306
-- Üretim Zamanı: 04 Haz 2026, 22:13:25
-- Sunucu sürümü: 11.4.12-MariaDB
-- PHP Sürümü: 8.4.21
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
--
-- Veritabanı: `moybeach_menu`
--
-- --------------------------------------------------------
--
-- Tablo için tablo yapısı `products`
--
CREATE TABLE `products` (
`id` int(11) NOT NULL,
`category_id` int(11) DEFAULT NULL,
`name` varchar(255) NOT NULL,
`name_tr` varchar(255) NOT NULL,
`slug` varchar(255) NOT NULL,
`short_description` varchar(255) DEFAULT NULL,
`description` text DEFAULT NULL,
`description_tr` text NOT NULL,
`price` decimal(10,2) NOT NULL,
`image_url` varchar(255) DEFAULT NULL,
`status` tinyint(1) DEFAULT 1,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_turkish_ci;
--
-- Tablo döküm verisi `products`
--
INSERT INTO `products` (`id`, `category_id`, `name`, `name_tr`, `slug`, `short_description`, `description`, `description_tr`, `price`, `image_url`, `status`, `created_at`, `updated_at`) VALUES
(1, 1, 'Breakfast Plate', 'Kahvaltı Tabağı', 'breakfast-plate', 'A rich breakfast platter prepared with boiled eggs, black and green olives, honey, clotted cream, fresh avocado, tomatoes and cucumbers, cheese, and beef rosé.', 'A rich breakfast platter prepared with boiled eggs, black and green olives, honey, clotted cream, fresh avocado, tomatoes and cucumbers, cheese, and beef rosé.', 'Haşlanmış yumurta, siyah ve yeşil zeytin, bal, kaymak, taze avokado, domates ve salatalık ile hazırlanan zengin kahvaltı tabağı, peynir, dana rosebeef.', 850.00, '', 1, '2026-05-22 12:21:16', '2026-05-22 13:01:50'),
(2, 1, 'Eggs with Braised Meat', 'Kavurmalı Yumurta', 'eggs-with-braised-meat', 'Butter-cooked eggs paired with tender beef kavurma.', 'Butter-cooked eggs meet tender, slow-braised beef kavurma for a hearty and satisfying breakfast.', 'Tereyağında pişirilen yumurtaların, yumuşak dokulu dana kavurma ile buluştuğu doyurucu kahvaltılık.', 800.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(3, 1, 'Menemen', 'Menemen', 'menemen', 'Traditional Turkish egg scramble with tomatoes and peppers.', 'Traditional Turkish menemen made with ripe tomatoes, green peppers, and eggs, cooked in olive oil.', 'Olgun domates, yeşil biber ve yumurta ile hazırlanan geleneksel Türk menemeni.', 600.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(4, 1, 'Eggs with Turkish Sausage', 'Sucuklu Yumurta', 'eggs-with-turkish-sausage', 'Classic buttery eggs with lightly grilled Turkish sausage.', 'A classic combination of lightly grilled Turkish sausage (sucuk) slices served alongside buttery pan-cooked eggs.', 'Izgarada hafifçe kızartılmış sucuk dilimleri ve tereyağlı yumurta ile hazırlanan klasik lezzet.', 650.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(5, 1, 'Sunny Side Up', 'Göz Yumurta', 'sunny-side-up', 'Two eggs cooked in butter, simple and classic.', 'Two eggs gently cooked in butter and served in their classic, simple presentation.', 'Tereyağında pişirilmiş iki adet yumurta, sade ve klasik sunumuyla servis edilir.', 450.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(6, 1, 'Scrambled Eggs', 'Çırpılmış Yumurta', 'scrambled-eggs', 'Creamy scrambled eggs gently cooked in butter.', 'Soft and creamy scrambled eggs prepared in butter, served warm.', 'Tereyağında yumuşak kıvamda hazırlanan çırpılmış yumurta.', 450.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(7, 1, 'Vegetable & Cheese Omelette', 'Sebzeli ve Peynirli Omlet', 'vegetable-cheese-omelette', 'Three-egg omelette with seasonal vegetables or cheese.', 'A fluffy three-egg omelette served with your choice of seasonal vegetables or cheese.', 'Üç yumurta ile hazırlanan omlet; mevsim sebzeleri veya peynir seçeneğiyle servis edilir.', 550.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(8, 2, 'Roasted Quinoa Bowl', 'Kavrulmuş Kinoa Kasesi', 'roasted-quinoa-bowl', 'Quinoa with smoky sweet potato, edamame, corn, and lime.', 'Roasted quinoa paired with smoky sweet potato, edamame, baby corn, and Mexican beans, finished with capers, sesame seeds, and a squeeze of lime.', 'Kavrulmuş kinoa; köz aromalı tatlı patates, edamame, baby mısır ve Meksika fasulyesi ile buluşturulur. Kapari, susam ve misket limonu dokunuşlarıyla ferah bir denge kazanır.', 950.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(9, 2, 'Avocado Bowl', 'Avokado Kasesi', 'avocado-bowl', 'Fresh avocado with cucumber, cherry tomatoes, and tahini.', 'Fresh avocado cubes combined with cucumber, cherry tomatoes, and mint. Served with edamame, chickpeas, and cheese cubes in a light tahini dressing.', 'Taze avokado küpleri; salatalık, cherry domates ve nane ile buluşur. Edamame, nohut ve peynir küpleri tahin sos eşliğinde servis edilir.', 950.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(10, 2, 'Granola Bowl', 'Granola Kasesi', 'granola-bowl', 'Crunchy granola and seasonal fruits over fruit yogurt.', 'Crunchy granola and fresh seasonal fruits layered over creamy fruit yogurt — light, refreshing, and satisfying.', 'Meyveli yoğurt üzerine yerleştirilen çıtır granola ve mevsim meyveleriyle hazırlanan hafif ve ferah bowl.', 800.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(11, 2, 'Minty Watermelon Bowl', 'Naneli Karpuz Kasesi', 'minty-watermelon-bowl', 'Juicy watermelon with mint, basil, Ezine cheese, and balsamic glaze.', 'Juicy watermelon slices tossed with fresh mint, basil, and purslane. Balanced with Ezine cheese crumbles and a drizzle of balsamic glaze.', 'Sulu karpuz dilimleri; taze nane, fesleğen ve kaya koruğu ile harmanlanır. Ezine peyniri ve balzamik glaze ile dengelenir.', 800.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(12, 2, 'Eggs Benedict', 'Eggs Benedict', 'eggs-benedict', 'Poached egg on toasted bread with spinach, cured meat, and hollandaise.', 'Toasted bread topped with sautéed spinach, thin-sliced cured meat, and a perfectly poached egg, finished with classic hollandaise sauce.', 'Tereyağında kızartılmış ekmek üzerinde sotelenmiş ıspanak, ince dilim kuru et ve poşe yumurta; hollandaise sos ile tamamlanır.', 850.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(13, 2, 'Sushi Bowl', 'Sushi Kasesi', 'sushi-bowl', 'Smoked salmon, avocado, surimi with spicy mayo and sesame.', 'Smoked salmon, surimi, avocado, baby radish, cucumber, and marinated seaweed, finished with spicy mayo, spring onion, and toasted sesame.', 'Füme somon, surimi, avokado, baby turp, salatalık ve marine yosun ile buluşur. Spicy mayo, frenk soğanı ve kavrulmuş susam ile tamamlanır.', 1350.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(14, 3, 'Cheeseburger', 'Cheeseburger', 'cheeseburger', 'Grilled beef patty with cheddar, caramelized onions, and special sauce.', 'Grilled beef patty topped with melted cheddar cheese and caramelized onions, served in a soft burger bun with house special sauce.', 'Izgara hamburger köftesi, eriyen cheddar peyniri ve karamelize soğan; özel sos eşliğinde yumuşak burger ekmeğinde servis edilir.', 1100.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(15, 3, 'Sloppy Joe', 'Sloppy Joe', 'sloppy-joe', 'Slow-cooked minced beef in artisan bun with truffle mayonnaise.', 'Slow-cooked minced beef and vegetables piled into an artisan burger bun, served with aromatic truffle mayonnaise.', 'Yavaş pişirilmiş sebzeli kıyma ile hazırlanan Sloppy Joe, artisan burger ekmeğinde trüflü mayonez eşliğinde sunulur.', 1000.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(16, 3, 'Caprese Sandwich', 'Caprese Sandviç', 'caprese-sandwich', 'Burrata, pesto, ripe tomatoes and basil on olive-rosemary sourdough.', 'A light Italian-inspired sandwich with pesto sauce, creamy burrata, ripe tomatoes, and fresh basil leaves on olive and rosemary sourdough bread.', 'Pesto sos, buratta, olgun domates ve fesleğen yapraklarının zeytinli biberiyeli köy ekmeği ile buluştuğu hafif İtalyan sandviçi.', 1250.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(17, 3, 'Hot Dog', 'Hot Dog', 'hot-dog', 'Grilled sausage with cheddar, crispy onions, mustard, and sweet chili.', 'Grilled sausage served in a soft bun with cheddar cheese, crispy onions, mustard, and sweet chili sauce.', 'Izgara sosis; cheddar, kıtır soğan, hardal ve sweet chili sos ile sandviç ekmeği içerisinde servis edilir.', 1100.00, '', 1, '2026-05-22 13:01:50', '2026-06-01 14:35:48'),
(18, 3, 'Chili Hot Dog', 'Chili Hot Dog', 'chili-hot-dog', 'Flavor-packed hot dog with bolognese and mildly spicy chili sauce.', 'A richly flavored hot dog interpretation topped with slow-cooked bolognese mince and a mildly spicy chili sauce.', 'Bolonez kıyma ve hafif acılı chili sos ile hazırlanan yoğun aromalı hot dog yorumu.', 1200.00, '', 1, '2026-05-22 13:01:50', '2026-06-01 14:36:08'),
(19, 3, 'Tex-Mex Steak Stack', 'Tex-Mex Biftek Sandviç', 'tex-mex-steak-stack', 'Grilled steak slices with Tex-Mex flavors on sourdough.', 'Grilled steak slices layered with special sauces and bold Tex-Mex flavors, served on plain sourdough bread.', 'Izgara biftek dilimleri, özel soslar ve Tex-Mex aromalarıyla hazırlanan sade köy ekmeği.', 1400.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(20, 4, 'Margherita Pizza', 'Margherita Pizza', 'margherita-pizza', 'Classic thin-crust pizza with tomato sauce, mozzarella, and basil.', 'Classic Italian thin-crust pizza with tomato sauce, fresh mozzarella, and fragrant basil leaves.', 'İnce pizza tabanı üzerinde domates sosu, mozzarella ve taze fesleğen ile hazırlanan klasik İtalyan pizza.', 800.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(21, 4, 'Pepperoni Pizza', 'Sucuklu Pizza', 'pepperoni-pizza', 'Rich pizza topped with Turkish sausage over mozzarella and tomato.', 'Rich and aromatic pizza topped with Turkish sausage (sucuk) slices over classic tomato sauce and mozzarella cheese.', 'Domates sosu ve mozzarella peyniri üzerine yerleştirilen sucuk dilimleriyle hazırlanan yoğun aromalı pizza.', 1200.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(22, 4, 'Burrata & Artichoke Pesto Pizza', 'Burrata & Enginar Pestolu Pizza', 'burrata-&-artichoke-pesto-pizza', 'Thin crust with spinach pesto, feta, and creamy burrata.', 'Thin-crust pizza with spinach pesto sauce, crumbled feta cheese, and a generous serving of creamy burrata.', 'Enginar pesto sosu, feta peyniri ve kremamsı burrata ile hazırlanan ince taban pizza yorumu.', 1350.00, '', 1, '2026-05-22 13:01:50', '2026-06-01 14:39:17'),
(23, 4, 'Dry Beef & Artichoke Pizza', 'Kuru Et & Enginarlı Pizza', 'dry-beef-artichoke-pizza', 'Pizza with cured beef, artichoke hearts, and sun-dried tomatoes.', 'A flavorful pizza topped with thinly sliced cured beef, artichoke hearts, and sun-dried tomatoes.', 'Kuru et, enginar ve kurutulmuş domates ile hazırlanan pizza.', 1350.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(24, 4, 'Quattro Formaggi Pizza', 'Dört Peynirli Pizza', 'quattro-formaggi-pizza', 'Four-cheese pizza: mozzarella, roquefort, brie, and edam.', 'A richly flavored four-cheese pizza combining mozzarella, roquefort, brie, and edam for a deeply satisfying experience.', 'Mozzarella, rokfor, brie ve edam peynirlerinin buluştuğu yoğun peynir aromalı pizza.', 1200.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(25, 4, 'Mixed Pizza', 'Karışık Pizza', 'mixed-pizza', 'Hearty pizza with sucuk, peppers, mushrooms, corn, and black olives.', 'A hearty, loaded pizza topped with Turkish sausage, peppers, mushrooms, sweet corn, and black olives.', 'Sucuk, biber, mantar, mısır ve siyah zeytin ile hazırlanan doyurucu karışık pizza.', 1300.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(26, 4, 'Vegetarian Pizza', 'Vejetaryen Pizza', 'vegetarian-pizza', 'Grilled zucchini, peppers, mushrooms, red onion, and black olives.', 'A colorful vegetable pizza with grilled zucchini, mixed peppers, mushrooms, red onion, and black olives on a classic base.', 'Izgara kabak, renkli biberler, mantar, kırmızı soğan ve siyah zeytin ile hazırlanan sebzeli pizza.', 1000.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(27, 5, 'Lemon & Sage Capellini', 'Limon & Adaçaylı Capellini', 'lemon-sage-capellini', 'Thin capellini with fresh lemon juice and aromatic sage.', 'Delicate capellini pasta prepared with fresh lemon juice and aromatic sage, light and full of flavour.', 'İnce capellini makarnası; taze limon suyu ve aromatik adaçayı ile hazırlanır.', 900.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(28, 5, 'Creamy Artichoke & Gnocchi', 'Kremalı Enginar & Gnocchi', 'creamy-artichoke-&-gnocchi', 'Soft gnocchi with creamy pesto sauce, spinach, and parmesan.', 'Soft, pillowy gnocchi tossed in a creamy pesto sauce with fresh spinach and a generous finish of parmesan.', 'Kremalı pesto sos, taze enginar ve parmesan ile hazırlanan yumuşak dokulu makarna.', 900.00, '', 1, '2026-05-22 13:01:50', '2026-06-01 14:38:53'),
(29, 5, 'Linguine di Mare', 'Deniz Mahsullü Linguine', 'linguine-di-mare', 'Linguine with shrimp, crab, and aromatic seafood sauce.', 'Linguine tossed with shrimp, crab meat, and an aromatic seafood sauce, finished with a dusting of parmesan.', 'Karides, yengeç ve aromatik deniz mahsullü sos ile hazırlanan linguine makarna, parmesan ile tamamlanır.', 1350.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(30, 5, 'Spaghetti Bolognese', 'Spagetti Bolonez', 'spaghetti-bolognese', 'Classic spaghetti with slow-cooked Bolognese sauce.', 'Classic Italian spaghetti paired with a rich, slow-cooked Bolognese meat sauce and a sprinkle of parmesan.', 'Yavaş pişirilmiş bolonez sosun spaghetti ile buluştuğu klasik İtalyan lezzeti.', 1000.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(31, 5, 'Fettuccine Alfredo', 'Fettuccine Alfredo', 'fettuccine-alfredo', 'Creamy Alfredo sauce with chicken, mushrooms, and parmesan.', 'Fettuccine in a velvety Alfredo cream sauce enriched with tender chicken pieces, sautéed mushrooms, and parmesan.', 'Kremalı Alfredo sos; tavuk parçaları, mantar ve parmesan ile zenginleştirilir.', 1000.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(32, 5, 'Gluten-Free Pasta Options', 'Glütensiz Makarna Seçenekleri', 'gluten-free-pasta', 'Available in gluten-free pasta to suit dietary needs.', 'All pasta dishes are available in gluten-free options, prepared carefully to suit your dietary requirements.', 'Glütensiz makarna seçenekleri mevcut; diyet gereksinimlerinize uygun olarak hazırlanır.', 950.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(33, 6, 'French Fries', 'Patates Kızartması', 'french-fries', 'Crispy golden-fried potato slices.', 'Classic crispy golden-fried potato slices, seasoned and served hot.', 'Çıtır ve altın renginde kızartılmış patates dilimleri.', 700.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(34, 6, 'Truffle Fries with Parmesan', 'Trüflü Parmesanlı Patates', 'truffle-fries-parmesan', 'Crispy fries with aromatic truffle oil and parmesan.', 'Crispy fries drizzled with aromatic truffle oil and finished with freshly grated parmesan cheese.', 'Çıtır patates kızartmaları, aromatik trüf sosu ve parmesan ile servis edilir.', 800.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(35, 6, 'Crispy Chicken Tenders', 'Çıtır Tavuk Parçaları', 'crispy-chicken-tenders', 'Golden crispy chicken tenders served with french fries.', 'Golden-fried crispy chicken tenders served alongside classic french fries, perfect for sharing.', 'Altın renginde çıtır tavuk parçaları, patates kızartması eşliğinde servis edilir.', 1000.00, '', 1, '2026-05-22 13:01:50', '2026-06-01 14:37:07'),
(36, 6, 'Dynamite Shrimp', 'Dinamit Karides', 'dynamite-shrimp', 'Crispy shrimp with sriracha, lime, and special dynamite sauce.', 'Crispy fried shrimp tossed in a vibrant sauce of sriracha, lime juice, and our house special dynamite sauce.', 'Çıtır karidesler; sriracha, lime ve özel dynamite sos ile buluşturulur.', 1300.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(37, 6, 'Tempura Zucchini', 'Tempura Kabak', 'tempura-zucchini', 'Tempura-battered zucchini with dill yogurt dipping sauce.', 'Thinly sliced zucchini in a light, crispy tempura batter, served with a fresh dill yogurt dipping sauce.', 'Tempura kaplamalı kabak dilimleri, dereotlu yoğurt sos eşliğinde servis edilir.', 950.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(38, 6, 'Mixed Appetizer Plate', 'Karışık Başlangıç Tabağı', 'mixed-appetizer-plate', 'Sharing platter: fries, sausage, crispy chicken, and spicy onion rings.', 'A generous sharing platter featuring french fries, sausage pieces, crispy chicken tenders, and spicy onion rings.', 'Patates kızartması, sosis parçaları, çıtır tavuk ve acılı soğan halkası içeren paylaşım tabağı.', 1250.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(39, 7, 'Caesar Salad (with Chicken)', 'Tavuklu Sezar Salata', 'caesar-salad-chicken', 'Romaine lettuce with grilled chicken, parmesan, and croutons.', 'Crisp iceberg and romaine lettuce tossed with grilled chicken breast, shaved parmesan, and golden croutons in classic Caesar dressing.', 'Iceberg ve Yedikule marulu; ızgara tavuk, parmesan ve kruton ile hazırlanır.', 1000.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(40, 7, 'Greek Salad', 'Yunan Salatası', 'greek-salad', 'Tomatoes, cucumber, red onion, black olives, and feta cheese.', 'A refreshing Aegean-style salad with ripe tomatoes, cucumber, red onion, black olives, and creamy feta cheese.', 'Domates, salatalık, kırmızı soğan, siyah zeytin ve feta peyniriyle hazırlanan ferah Ege salatası.', 950.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(41, 7, 'Caprese Salad', 'Caprese Salatası', 'caprese-salad', 'Fresh mozzarella, tomatoes, and basil with pesto sauce.', 'Sliced fresh mozzarella and ripe tomatoes layered with basil leaves and finished with a drizzle of pesto sauce.', 'Taze mozzarella, domates ve fesleğen yaprakları pesto sos ile tamamlanır.', 1100.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(42, 7, 'Avocado & Artichoke Salad', 'Avokado & Enginarlı Salata', 'avocado-artichoke-salad', 'Avocado, artichoke hearts, and mesclun with light vinaigrette.', 'Fresh avocado and artichoke hearts on a bed of mesclun greens, dressed in a light, tangy vinaigrette.', 'Avokado, enginar kalbi ve mascolin yeşillikleri hafif Vinegret sos ile servis edilir.', 1100.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(43, 8, 'Honey Soy Glazed Salmon', 'Bal & Soya Soslu Somon', 'honey-soy-glazed-salmon', 'Grilled salmon with honeysoy glaze, broccoli, and asparagus.', 'Grilled salmon fillet with a honeysoy glaze, lightly caramelized on the surface. Served with steamed broccoli, cauliflower, and asparagus.', 'Balsoya glaze ile ızgaralanmış somon fileto, yüzeyinde hafif karamelizasyon oluşturularak servis edilir. Buharda pişmiş brokoli, karnabahar ve kuşkonmaz eşlik eder.', 1500.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(44, 8, 'Ojo de Bife', 'Ojo de Bife', 'ojo-de-bife', 'Beef rib-eye with chimichurri, molasses demi-glace, and mash.', 'Beef rib-eye served with rich chimichurri sauce and a molasses-touched demi-glace. Balanced with velvety mashed potatoes, the dish pairs the deep flavour of the meat with the freshness of herbs.', 'Dana antrikot, yoğun aromalı chimichurri sos ve pekmez dokunuşlu demi-glace eşliğinde servis edilir. Kadifemsi patates püresiyle dengelenen tabak, etin derin lezzetini taze otların ferahlığıyla buluşturur.', 1600.00, '', 1, '2026-05-22 13:01:50', '2026-06-01 14:36:49'),
(45, 8, 'Yakitori Chicken Skewers', 'Yakitori Tavuk Şiş', 'yakitori-chicken-skewers', 'Marinated chicken skewers caramelized with a brown sugar glaze.', 'Marinated chicken skewers caramelized with a brown sugar-balanced glaze. Garnished with spring onion, baby carrots, baby zucchini, and roasted cherry tomatoes.', 'Marine tavuk şişler, esmer şekerle dengelenmiş glaze sos ile karamelize edilerek servis edilir. Frenk soğanının aromatik dokunuşu; baby havuç, baby kabak ve közlenmiş çeri domateslerle tabağa taze ve rafine bir denge kazandırır.', 1100.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(46, 8, 'Chateaubriand with Lemon Garlic Purslane', 'Limon & Sarımsaklı Semizotlu Chateaubriand', 'chateaubriand-lemon-garlic-purslane', 'Beef tenderloin seared using classic chateaubriand technique.', 'Beef tenderloin seared at high heat using classic chateaubriand technique, preserving its tender centre. Lemon and garlic-infused purslane adds freshness and vibrant balance to the plate.', 'Dana bonfile, yüksek ısıda mühürlenerek merkezinde yumuşak dokusunu koruyan klasik chateaubriand tekniğiyle hazırlanır. Taze limon ve sarımsakla aromalandırılmış semizotu, tabağa canlılık ve ferah bir denge katar.', 1700.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(47, 8, 'Sea Bass en Papillote', 'Levrek en Papillote', 'sea-bass-en-papillote', 'Sea bass cooked en papillote with beurre blanc and artichoke pesto.', 'Sea bass cooked en papillote to preserve its natural aroma, served with silky beurre blanc. Artichoke pesto\'s mild herbal character brings elegant balance alongside baby corn and fresh asparagus.', 'Levrek, kendi aroması korunarak beurre blanc sosun ipeksi dokusuyla servis edilir. Enginar pestosunun hafif otsu karakteri; baby mısır ve kuşkonmazın taze dokusuyla tabağa zarif bir denge kazandırır.', 1350.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(48, 8, 'Traditional Turkish Meatballs', 'Geleneksel Türk Köftesi', 'traditional-turkish-meatballs', 'Classic Turkish köfte with pide bread, fries, and garlic yogurt.', 'Traditional Turkish meatballs served with tırnak pide bread and crispy fries. Garlic strained yogurt provides a classic finishing touch a warm, familiar flavour of Anatolian cuisine in a modern presentation.', 'Geleneksel Türk köftesi, tırnak pide ve çıtır patates kızartması eşliğinde servis edilir. Sarımsaklı süzme yoğurt, köftenin baharatlı ve dengeli lezzetini tamamlayan klasik bir dokunuş sunar. Anadolu mutfağının sıcak ve tanıdık tatlarını modern sunum anlayışıyla buluşturan özel bir yorumdur.', 1100.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(49, 48, 'Churros with Clotted Cream', 'Kaymaklı Churros', 'churros-clotted-cream', 'Golden crispy churros served hot with rich clotted cream.', 'Golden crispy on the outside and soft inside, churros prepared the traditional way and served hot. Paired with rich clotted cream, cinnamon, and subtle caramel notes create a balanced, elegant dessert experience.', 'Dışı altın renginde çıtır, içi yumuşak dokulu churroslar geleneksel yöntemle hazırlanarak sıcak servis edilir. Yoğun kıvamlı kaymak eşliğinde sunulan tatlı, tarçın ve hafif karamelize notalarla dengeli bir lezzet profili oluşturur. Klasik sokak lezzetini modern sunum anlayışıyla buluşturan bu özel tatlı, zarif ve keyifli bir final deneyimi sunar.', 800.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(50, 48, 'Crème de la Fruit with Lotus', 'Lotuslu Meyveli Krema', 'creme-de-la-fruit-lotus', 'Velvety fruit cream with seasonal fruits and Lotus biscuit.', 'Velvety fruit cream with the fresh aroma of seasonal fruits and caramelised notes of Lotus biscuits. A layered dessert reflecting modern patisserie with lightness and complex aromas, presented in elegant fine dining style.', 'Kadifemsi meyve kreması, taze meyvelerin ferah aroması ve Lotus bisküvisinin karamelize notalarıyla dengeli bir lezzet sunar. Katmanlı dokusuyla öne çıkan tatlı, hafifliği ve yoğun aromatik yapısıyla modern pastacılık anlayışını yansıtır. Zarif fine dining sunumuyla hazırlanan bu özel reçete, tatlı finalini sofistike ve rafine bir dokunuşla tamamlar.', 800.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(51, 48, 'Soufflé', 'Sufle', 'souffle', 'Warm chocolate soufflé with a flowing velvety interior.', 'Warm soufflé prepared with rich cocoa aroma, featuring a flowing and velvety interior beneath its thin, delicate crust. Served immediately from the oven.', 'Yoğun kakao aromasıyla hazırlanan sıcak sufle, ince kabuğunun altında akışkan ve kadifemsi bir doku sunar.', 800.00, '', 1, '2026-05-22 13:01:50', '2026-05-22 13:01:50'),
(52, 49, 'Grass', 'Grass', 'cocktail-grass', 'A fresh, herb-forward signature cocktail.', 'A refreshing cocktail with vibrant herbal and green notes, perfect for a summer beach day.', 'Ferah otsu ve yeşil notalarıyla öne çıkan, yaz plajına özel imza kokteyl.', 750.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(53, 49, 'Placebo', 'Placebo', 'cocktail-placebo', 'A signature cocktail with a surprising twist.', 'A signature Moy Beach cocktail with a complex, unexpected flavour profile that keeps you coming back.', 'Karmaşık ve beklenmedik tat profiliyle tekrar tekrar içmek isteteceğiniz imza Moy Beach kokteyli.', 750.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(54, 49, 'Chili Mango', 'Chili Mango', 'cocktail-chili-mango', 'Tropical mango with a spicy chili kick.', 'Ripe mango flavour balanced with the heat of chili sweet, tropical, and bold.', 'Olgun mango aromasının acı biberle dengelendiği tatlı, tropikal ve cesur bir kokteyl.', 750.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(55, 49, 'Purple Basil', 'Purble Basil', 'cocktail-purple-basil', 'Aromatic basil cocktail with a floral, herbaceous character.', 'A floral and herbaceous cocktail featuring aromatic purple basil as its star ingredient.', 'Aromatik mor fesleğenin yıldız olduğu, çiçeksi ve otsu karakterde bir kokteyl.', 750.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(56, 49, 'Tuxedo', 'Tuxedo', 'cocktail-tuxedo', 'An elegant, spirit-forward classic cocktail.', 'An elegant, spirit-forward cocktail with sophisticated notes, served in refined style.', 'Sofistike notaları olan, şık ve güçlü bir kokteyl.', 750.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(57, 49, 'Passion Martini', 'Passion Martini', 'cocktail-passion-martini', 'Passion fruit martini tropical and refreshing.', 'A vibrant martini bursting with tropical passion fruit flavour, light and invigorating.', 'Tropikal çpassion fruit aromasıyla dolu, hafif ve canlandırıcı bir martini.', 750.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(58, 49, 'Whiskey Sour Satsuma', 'Whiskey Sour Satsuma', 'cocktail-whiskey-sour-satsuma', 'Whiskey sour with a citrusy satsuma mandarin twist.', 'A classic whiskey sour elevated with the bright, citrusy flavour of satsuma mandarin.', 'Klasik whiskey sour\'a satsuma mandalinasının parlak narenciye aromasıyla verilen yeni bir yorum.', 750.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(59, 49, 'Peach Spritz', 'Peach Spritz', 'cocktail-peach-spritz', 'Light and bubbly peach spritz, perfect for a hot day.', 'A light, sparkling cocktail with the sweet, fruity aroma of fresh peach — ideal for sunny days.', 'Taze şeftali aromasının hafif köpüklü yapıyla buluştuğu, güneşli günlere özel bir kokteyl.', 750.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(60, 49, 'Moy Cocktail', 'Moy Kokteyl', 'cocktail-moy', 'Moy Beach\'s own house signature cocktail.', 'The one-of-a-kind house cocktail of Moy Beach, crafted exclusively for our guests.', 'Moy Beach\'in yalnızca misafirlerimiz için özel olarak hazırladığı eşsiz imza kokteyli.', 800.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(61, 50, 'Espresso Martini', 'Espresso Martini', 'classic-espresso-martini', 'Vodka, coffee liqueur, and fresh espresso shaken to perfection.', 'A smooth and indulgent cocktail made with vodka, coffee liqueur, and a freshly pulled espresso shot, shaken until velvety.', 'Votka, kahve likörü ve taze espresso shotuyla hazırlanan pürüzsüz ve lüks bir kokteyl.', 800.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(62, 50, 'Margarita', 'Margarita', 'classic-margarita', 'Tequila, triple sec, and lime juice — the timeless classic.', 'The iconic cocktail made with tequila, triple sec, and fresh lime juice, served with a salted rim.', 'Tekila, triple sec ve taze misket limonuyla hazırlanan, tuzlu kenarlıkla servis edilen ikonik kokteyl.', 800.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(63, 50, 'Long Island Iced Tea', 'Long Island Ice Tea', 'classic-long-island', 'A bold blend of five spirits with cola and lemon.', 'The legendary five-spirit cocktail — vodka, rum, tequila, gin, and triple sec — topped with cola and a squeeze of lemon.', 'Votka, rom, tekila, cin ve triple sec\'in kola ve limonla buluştuğu efsanevi beş-sert içki kokteyli.', 800.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(64, 50, 'Aperol Spritz', 'Aperol Spritz', 'classic-aperol-spritz', 'Aperol, Prosecco, and soda the perfect Italian aperitif.', 'Italy\'s favourite aperitif: Aperol bitter liqueur combined with Prosecco and a splash of soda water, served over ice.', 'İtalya\'nın sevilen aperitifi: Aperol acı likörünün Prosecco ve soda suyuyla buluştuğu, buz üzerinde servis edilen ferah içecek.', 880.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(65, 51, 'Efes Special Series 50cl', 'Efes Özel Seri 50cl', 'beer-efes-ozel-50cl', 'Premium Efes lager in a 50cl can.', 'Efes Special Series premium lager, served in a 50cl can crisp, refreshing, and iconic.', 'Efes Özel Seri premium lager, 50cl kutuda servis edilir.', 440.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(66, 51, 'Efes Malt 50cl', 'Efes Malt 50cl', 'beer-efes-malt-50cl', 'Efes Malt dark beer, 50cl.', 'Efes Malt, a smooth dark beer with rich malt flavour, served in a 50cl can.', 'Zengin malt aromasıyla pürüzsüz bir koyu bira olan Efes Malt, 50cl kutuda servis edilir.', 440.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(67, 51, 'Belfast 50cl', 'Belfast 50cl', 'beer-belfast-50cl', 'Belfast craft beer, 50cl.', 'Belfast craft lager with a smooth, well-balanced taste, served in a 50cl can.', 'Pürüzsüz ve iyi dengeli tadıyla Belfast craft lager, 50cl kutuda servis edilir.', 440.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(68, 51, 'Bomonti Unfiltered 50cl', 'Bomonti Filtresiz 50cl', 'beer-bomonti-filtresiz-50cl', 'Bomonti unfiltered wheat beer, 50cl.', 'Bomonti Unfiltered a naturally hazy wheat beer with a rich, full-bodied character, 50cl.', 'Bomonti Filtresiz doğal bulanık buğday birası, dolgun gövdesiyle 50cl kutuda servis edilir.', 440.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(69, 51, 'Bud 33cl', 'Bud 33cl', 'beer-bud-33cl', 'Budweiser lager, 33cl bottle.', 'Budweiser the classic American lager, light and refreshing, served in a 33cl bottle.', 'Budweiser klasik Amerikan lager, hafif ve ferah, 33cl şişede servis edilir.', 440.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(70, 51, 'Becks 33cl', 'Becks 33cl', 'beer-becks-33cl', 'Beck\'s German pilsner, 33cl bottle.', 'Beck\'s the crisp, clean German pilsner with a hoppy finish, 33cl bottle.', 'Beck\'s — sert atlama aromasıyla temiz ve çıtır Alman pilsneri, 33cl şişede.', 440.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(71, 51, 'Miller 33cl', 'Miller 33cl', 'beer-miller-33cl', 'Miller Genuine Draft, 33cl bottle.', 'Miller Genuine Draft — a light, smooth American lager, served in a 33cl bottle.', 'Miller Genuine Draft — hafif ve pürüzsüz Amerikan lager, 33cl şişede.', 440.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(72, 51, 'Erdinger 33cl', 'Erdinger 33cl', 'beer-erdinger-33cl', 'Erdinger German wheat beer, 33cl bottle.', 'Erdinger Weissbier — Bavaria\'s finest wheat beer with fruity and spicy notes, 33cl bottle.', 'Erdinger Weissbier Bavyera\'nın en iyisi, meyveli ve baharatlı notalarıyla buğday birası, 33cl.', 470.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(73, 51, 'Heineken 33cl', 'Heineken 33cl', 'beer-heineken-33cl', 'Heineken premium lager, 33cl bottle.', 'Heineken — the world-famous Dutch premium lager, crisp and refreshing, 33cl bottle.', 'Heineken — dünyaca ünlü Hollanda premium lager, çıtır ve ferah, 33cl şişede.', 480.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(74, 51, 'Corona 35.5cl', 'Corona 35.5cl', 'beer-corona-35cl', 'Corona Extra Mexican lager, 35.5cl bottle.', 'Corona Extra — the iconic Mexican lager, best enjoyed with a wedge of lime, 35.5cl bottle.', 'Corona Extra — ikonik Meksika lager, misket limonuyla en güzel şekilde içilir, 35.5cl şişede.', 470.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(75, 52, 'Jägermeister Shot', 'Jägermeister Shot', 'shot-jagermeister', 'Jägermeister herbal liqueur, served as a shot.', 'A classic shot of Jägermeister — the iconic German herbal liqueur with 56 botanicals, served chilled.', '56 botanik ile hazırlanan efsanevi Alman bitkisel likörü Jägermeister, soğuk olarak servis edilir.', 350.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(76, 52, 'Baileys Shot', 'Baileys Shot', 'shot-baileys', 'Baileys Irish Cream, smooth and creamy shot.', 'A smooth and indulgent shot of Baileys Original Irish Cream — creamy, rich, and irresistible.', 'Kremamsı ve zengin Baileys Original Irish Cream ile hazırlanan pürüzsüz ve lüks bir shot.', 350.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(77, 52, 'Tequila Shot', 'Tekila Shot', 'shot-tequila', 'Tequila shot served with salt and lemon.', 'A classic tequila shot served the traditional way — with a pinch of salt and a wedge of lemon.', 'Geleneksel yöntemle, bir tutam tuz ve misket limonu dilimiyle servis edilen klasik tekila shot.', 350.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(78, 53, 'Johnnie Walker Black Label 70cl', 'Johnnie Walker Black Label 70cl', 'bottle-jw-black-label', 'Blended Scotch whisky, aged 12 years. Full bottle.', 'Johnnie Walker Black Label — a blended Scotch whisky aged at least 12 years, rich, smooth, and complex. Served as a full 70cl bottle. Please ask staff for pricing.', 'Johnnie Walker Black Label — en az 12 yıl yaşlandırılmış blended Scotch viski, zengin ve pürüzsüz. 70cl şişe olarak servis edilir. Fiyat için personelimize danışınız.', 0.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(79, 53, 'Double Black Label 70cl', 'Double Black Label 70cl', 'bottle-double-black', 'Johnnie Walker Double Black, bold and smoky. Full bottle.', 'Johnnie Walker Double Black — a bolder, smokier evolution of the Black Label, with layers of peat and spice. 70cl bottle. Please ask staff for pricing.', 'Johnnie Walker Double Black — Black Label\'ın daha güçlü ve tütsülü yorumu, turba ve baharat notalarıyla. 70cl şişe. Fiyat için personelimize danışınız.', 0.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(80, 53, 'Johnnie Walker Gold Reserve 70cl', 'Johnnie Walker Gold Reserve 70cl', 'bottle-jw-gold-reserve', 'Johnnie Walker Gold Reserve blended Scotch. Full bottle.', 'Johnnie Walker Gold Reserve a honey-sweet, creamy blended Scotch whisky with vanilla and toffee notes. 70cl bottle. Please ask staff for pricing.', 'Johnnie Walker Gold Reserve bal, vanilyalı ve toffee notalarıyla kremamsı blended Scotch viski. 70cl şişe. Fiyat için danışınız.', 0.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(81, 53, 'Johnnie Walker 18 Year Old 70cl', 'Johnnie Walker 18yo 70cl', 'bottle-jw-18yo', 'Johnnie Walker 18 Year Old blended Scotch. Full bottle.', 'Johnnie Walker 18 Year Old a sophisticated, aged blended Scotch with exceptional depth and smoothness. 70cl bottle. Please ask staff for pricing.', 'Johnnie Walker 18 Yıllık olağanüstü derinlik ve pürüzsüzlüğe sahip, sofistike bir blended Scotch viski. 70cl şişe. Fiyat için danışınız.', 0.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(82, 53, 'Mortlach 12 Year Old 70cl', 'Mortlach 12 70cl', 'bottle-mortlach-12', 'Mortlach 12yo single malt Scotch whisky. Full bottle.', 'Mortlach 12 Year Old a meaty, complex single malt from Speyside, known for its distinctive distillation process. 70cl bottle. Please ask staff for pricing.', 'Mortlach 12 Yıllık kendine özgü damıtma süreciyle bilinen Speyside\'den etli ve karmaşık bir single malt. 70cl şişe. Fiyat için danışınız.', 0.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(83, 53, 'The Singleton 70cl', 'The Singleton 70cl', 'bottle-singleton', 'The Singleton single malt Scotch whisky. Full bottle.', 'The Singleton — an approachable, smooth single malt Scotch whisky with sweet and fruity notes. 70cl bottle. Please ask staff for pricing.', 'The Singleton — tatlı ve meyveli notalarıyla erişilebilir ve pürüzsüz bir single malt Scotch viski. 70cl şişe. Fiyat için danışınız.', 0.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(84, 53, 'Talisker 70cl', 'Talisker 70cl', 'bottle-talisker', 'Talisker Isle of Skye single malt Scotch. Full bottle.', 'Talisker — the iconic Island single malt from the Isle of Skye, with powerful peaty, maritime character. 70cl bottle. Please ask staff for pricing.', 'Talisker — Skye Adası\'nın ikonik Island single malt\'ı, güçlü turba ve deniz karakteriyle. 70cl şişe. Fiyat için danışınız.', 0.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(85, 53, 'Dimple Golden Selection 70cl', 'Dimple Golden Selection 70cl', 'bottle-dimple-golden', 'Dimple Golden Selection blended Scotch whisky. Full bottle.', 'Dimple Golden Selection — a smooth and well-rounded blended Scotch whisky, gentle and easy to enjoy. 70cl bottle. Please ask staff for pricing.', 'Dimple Golden Selection — pürüzsüz ve dengeli bir blended Scotch viski, içimi kolay ve zarif. 70cl şişe. Fiyat için danışınız.', 0.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(86, 53, 'Smirnoff North 70cl', 'Smirnoff North 70cl', 'bottle-smirnoff-north', 'Smirnoff North premium vodka. Full bottle.', 'Smirnoff North — a clean and crisp premium vodka, triple-distilled for exceptional smoothness. 70cl bottle. Please ask staff for pricing.', 'Smirnoff North — olağanüstü pürüzsüzlük için üç kez damıtılmış, temiz ve çıtır bir premium votka. 70cl şişe. Fiyat için danışınız.', 0.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(87, 53, 'Ketel One 70cl', 'Ketel One 70cl', 'bottle-ketel-one', 'Ketel One Dutch wheat vodka. Full bottle.', 'Ketel One — a premium Dutch wheat vodka distilled in copper pot stills, silky smooth with a crisp finish. 70cl bottle. Please ask staff for pricing.', 'Ketel One — bakır pot distilasyonuyla üretilen premium Hollanda buğday votkası, ipeksi pürüzsüz ve çıtır bitiş. 70cl şişe. Fiyat için danışınız.', 0.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(88, 53, 'Gordon\'s Pink Gin 70cl', 'Gordon\'s Pink Cin 70cl', 'bottle-gordons-pink-gin', 'Gordon\'s Pink Gin with strawberry flavour. Full bottle.', 'Gordon\'s Premium Pink Gin — infused with natural raspberry, strawberry, and redcurrant flavours for a fruity twist on the classic. 70cl bottle. Please ask staff for pricing.', 'Gordon\'s Premium Pink Gin ahududu, çilek ve kırmızı frenk üzümü aromalarıyla klasiğe meyveli bir yorum. 70cl şişe. Fiyat için danışınız.', 0.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(89, 53, 'Tanqueray No. Ten 70cl', 'Tanqueray No. Ten 70cl', 'bottle-tanqueray-no-ten', 'Tanqueray No. Ten ultra-premium gin. Full bottle.', 'Tanqueray No. TEN an ultra-premium gin distilled with fresh citrus fruits including grapefruit, orange, and lime. 70cl bottle. Please ask staff for pricing.', 'Tanqueray No. TEN greyfurt, portakal ve misket limonuyla damıtılmış ultra-premium cin. 70cl şişe. Fiyat için danışınız.', 0.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(90, 53, 'Captain Morgan 70cl', 'Captain Morgan 70cl', 'bottle-captain-morgan', 'Captain Morgan spiced rum. Full bottle.', 'Captain Morgan Original Spiced Gold Caribbean rum blended with warming spices and natural flavours. 70cl bottle. Please ask staff for pricing.', 'Captain Morgan Original Spiced Gold ısıtıcı baharatlar ve doğal aromalarla harmanlanmış Karayip rumu. 70cl şişe. Fiyat için danışınız.', 0.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(91, 53, 'Don Julio 70cl', 'Don Julio 70cl', 'bottle-don-julio', 'Don Julio 100% blue agave tequila. Full bottle.', 'Don Julio a premium 100% blue agave tequila from the highlands of Jalisco, smooth and refined. 70cl bottle. Please ask staff for pricing.', 'Don Julio Jalisco yaylalarından premium %100 mavi agave tekilası, pürüzsüz ve rafine. 70cl şişe. Fiyat için danışınız.', 0.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(92, 54, 'Cola', 'Kola', 'soft-cola', 'Classic cola, served chilled.', 'Classic cola served cold the timeless refreshment.', 'Soğuk servis edilen klasik kola.', 250.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(93, 54, 'Fanta', 'Fanta', 'soft-fanta', 'Orange-flavoured fizzy drink, served chilled.', 'Fanta orange a fizzy, fruity drink full of citrus flavour, served chilled.', 'Narenciye aromasıyla dolu, meyveli ve köpüklü Fanta portakal, soğuk servis edilir.', 250.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(94, 54, 'Sprite', 'Sprite', 'soft-sprite', 'Lemon-lime sparkling soft drink, served chilled.', 'Sprite a crisp, lemon-lime sparkling soft drink, clean and refreshing.', 'Limon-misket limonu aromasıyla çıtır ve ferah, köpüklü Sprite.', 250.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(95, 54, 'Iced Tea (Lemon / Peach / Mango)', 'Ice Tea (Limon / Şeftali / Mango)', 'soft-ice-tea', 'Iced tea in lemon, peach, or mango flavour.', 'Refreshing iced tea available in three flavours: lemon, peach, and mango.', 'Limon, şeftali veya mango seçeneğiyle sunulan ferah ice tea.', 250.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(96, 54, 'Churchill', 'Churcill', 'soft-churchill', 'Churchill premium sparkling mineral water.', 'Churchill premium sparkling mineral water fine bubbles, clean taste, perfectly refreshing.', 'Churchill premium köpüklü maden suyu ince kabarcıklar, temiz tat ve mükemmel serinleticilik.', 330.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(97, 54, 'Uludağ Premium Soda', 'Uudağ Premium Soda', 'soft-uludag-premium-soda', 'Uludağ premium soda water.', 'Uludağ Premium Soda a fine Turkish sparkling water with a crisp, clean refreshing taste.', 'Uludağ Premium Soda çıtır ve temiz bir tada sahip, Türkiye\'nin sevilen köpüklü içeceği.', 220.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(98, 54, 'Water 0.33cl (Uludağ Glass Bottle)', 'Su 0.33cl (Uludağ Cam Şişe)', 'soft-water-uludag-glass', 'Still water in a 0.33cl Uludağ glass bottle.', 'Still mineral water served in an elegant 0.33cl Uludağ glass bottle.', 'Şık Uludağ cam şişesinde servis edilen 0.33cl maden suyu.', 100.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(99, 54, 'Jobi Freshly Squeezed Orange Juice', 'Jobi Sıkma Portakal', 'soft-jobi-orange', 'Freshly squeezed orange juice by Jobi.', 'Jobi freshly squeezed orange juice — 100% natural, vibrant, and full of vitamins.', 'Jobi taze sıkma portakal suyu — %100 doğal, canlı ve vitamin dolu.', 270.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(100, 54, 'Jobi Freshly Squeezed Pomegranate Juice', 'Jobi Sıkma Nar Suyu', 'soft-jobi-pomegranate', 'Freshly squeezed pomegranate juice by Jobi.', 'Jobi freshly squeezed pomegranate juice — rich in antioxidants, deep and naturally sweet.', 'Jobi taze sıkma nar suyu — antioksidan açısından zengin, derin ve doğal tatlılıkta.', 270.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(101, 54, 'Jobi Freshly Squeezed Mandarin Juice', 'Jobi Sıkma Mandalina Suyu', 'soft-jobi-mandarin', 'Freshly squeezed mandarin juice by Jobi.', 'Jobi freshly squeezed mandarin juice — naturally sweet citrus juice with a bright, fresh aroma.', 'Jobi taze sıkma mandalina suyu — parlak ve taze aromasıyla doğal tatlılıkta narenciye suyu.', 290.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(102, 54, 'Cool Lime', 'Cool Lime', 'soft-cool-lime', 'Refreshing lime-flavoured sparkling drink.', 'A cool and invigorating lime-flavoured sparkling drink — crisp, citrusy, and refreshing.', 'Çıtır, narenciyeli ve ferahlatan misket limonlu köpüklü içecek.', 300.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(103, 54, 'Strawberry & Hibiscus', 'Strawberry & Hibiscus', 'soft-strawberry-hibiscus', 'Sparkling strawberry and hibiscus flavoured drink.', 'A floral and fruity sparkling drink combining sweet strawberry with the floral tartness of hibiscus.', 'Tatlı çilekle hibiskusun çiçeksi ekşiliğini birleştiren, meyveli ve çiçeksi köpüklü içecek.', 300.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(104, 54, 'Lemonade', 'Limonata', 'soft-lemonade', 'Classic freshly made lemonade.', 'Freshly made lemonade — the perfect balance of sweet and tangy citrus refreshment.', 'Tatlı ve ekşinin mükemmel dengesinde, taze yapılmış limonata.', 270.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(105, 54, 'Red Bull Energy Drink', 'Red Bull Energy Drink', 'soft-redbull', 'Original Red Bull energy drink.', 'Red Bull Energy Drink — the original energy drink that gives you wings, with taurine, caffeine, and B-vitamins.', 'Red Bull Enerji İçeceği — taurin, kafein ve B vitaminleriyle orijinal enerji içeceği.', 270.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(106, 54, 'Red Bull Sugar Free', 'Red Bull Sugarfree', 'soft-redbull-sugarfree', 'Red Bull sugar-free energy drink.', 'Red Bull Sugar Free — all the energy of Red Bull with no sugar, for a lighter lift.', 'Red Bull Şekersiz — aynı enerji, şekersiz ve daha hafif seçenek.', 270.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(107, 54, 'Red Bull Blue Edition', 'Red Bull Blue Edition', 'soft-redbull-blue', 'Red Bull Blue Edition with blueberry flavour.', 'Red Bull Blue Edition — the energy of Red Bull infused with the flavour of blueberry.', 'Red Bull Blue Edition — yaban mersini aromasıyla Red Bull enerjisi.', 270.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(108, 54, 'Red Bull Pink Edition', 'Red Bull Pink Edition', 'soft-redbull-pink', 'Red Bull Pink Edition with strawberry & wild berry flavour.', 'Red Bull Pink Edition — Red Bull energy infused with the sweet flavour of strawberry and wild berry.', 'Red Bull Pink Edition — çilek ve yaban mersini aromasıyla Red Bull enerjisi.', 270.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(109, 54, 'Red Bull White Edition', 'Red Bull White Edition', 'soft-redbull-white', 'Red Bull White Edition with coconut & ginger flavour.', 'Red Bull White Edition — Red Bull energy combined with the exotic flavour of coconut and ginger.', 'Red Bull White Edition — hindistan cevizi ve zencefil aromasıyla Red Bull enerjisi.', 270.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(110, 55, 'Jägermeister', 'Jägermeister', 'glass-jagermeister', 'Jägermeister herbal liqueur, served by the glass.', 'Jägermeister — the iconic German herbal liqueur with 56 botanicals, served by the glass over ice.', 'Jägermeister — 56 botanik ile hazırlanan efsanevi Alman bitkisel likörü, buz üzerinde kadeh olarak servis edilir.', 800.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(111, 55, 'Gordon\'s', 'Gordon\'s', 'glass-gordons', 'Gordon\'s London Dry Gin, served by the glass.', 'Gordon\'s London Dry Gin — a classic, crisp gin with juniper and citrus notes, served by the glass.', 'Gordon\'s London Dry Gin ardıç ve narenciye notalarıyla klasik ve çıtır cin, kadeh olarak servis edilir.', 750.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(112, 55, 'Gordon\'s Pink', 'Gordon\'s Pink', 'glass-gordons-pink', 'Gordon\'s Premium Pink Gin, served by the glass.', 'Gordon\'s Premium Pink Gin a fruity, berry-infused gin with a sweet, refreshing character, by the glass.', 'Gordon\'s Premium Pink Gin — meyveli, tatlı ve ferah karakterde meyve aromalı cin, kadeh olarak.', 750.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(113, 55, 'Tanqueray No. Ten', 'Tanqueray No. Ten', 'glass-tanqueray-no-ten', 'Tanqueray No. Ten ultra-premium gin, by the glass.', 'Tanqueray No. TEN — an ultra-premium gin distilled with whole fresh citrus fruits, served by the glass.', 'Tanqueray No. TEN — taze narenciye meyvesiyle damıtılmış ultra-premium cin, kadeh olarak.', 1100.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(114, 55, 'Tanqueray Gin', 'Tanqueray Gin', 'glass-tanqueray-gin', 'Tanqueray London Dry Gin, served by the glass.', 'Tanqueray London Dry Gin — a bold, full-flavoured gin with distinctive juniper character, by the glass.', 'Tanqueray London Dry Gin — güçlü ve belirgin ardıç karakteriyle cesur, tam aromalı cin, kadeh olarak.', 950.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(115, 55, 'Ketel One', 'Ketel One', 'glass-ketel-one', 'Ketel One Dutch wheat vodka, served by the glass.', 'Ketel One — a premium Dutch wheat vodka distilled in copper pot stills, served by the glass.', 'Ketel One — bakır pot distilasyonuyla üretilen premium Hollanda buğday votkası, kadeh olarak.', 900.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(116, 55, 'Dimple', 'Dimple', 'glass-dimple', 'Dimple Scotch whisky, served by the glass.', 'Dimple — a classic, smooth blended Scotch whisky, gentle and approachable, served by the glass.', 'Dimple — klasik ve pürüzsüz blended Scotch viski, kadeh olarak servis edilir.', 800.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(117, 55, 'Johnnie Walker Black Label', 'Johnnie Walker Black Label', 'glass-jw-black-label', 'Johnnie Walker Black Label 12yo, by the glass.', 'Johnnie Walker Black Label — aged 12 years, rich and smoky with layers of complexity, served by the glass.', 'Johnnie Walker Black Label — 12 yıl yaşlandırılmış, zengin ve tütsülü katmanlı kompleks viski, kadeh olarak.', 750.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(118, 55, 'Lagavulin', 'Lagavulin', 'glass-lagavulin', 'Lagavulin Islay single malt Scotch, by the glass.', 'Lagavulin — the legendary Islay single malt with intense peat smoke, seaweed, and dark fruit notes. Served by the glass.', 'Lagavulin — yoğun turba dumanı, deniz yosunu ve koyu meyve notalarıyla efsanevi Islay single malt, kadeh olarak.', 900.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(119, 56, 'Red Wine (by the glass)', 'Kırmızı Şarap Kadeh', 'wine-red-glass', 'House red wine served by the glass.', 'Our carefully selected house red wine, served by the glass — rich, full-bodied, and smooth.', 'Özenle seçilmiş ev kırmızı şarabı, kadeh olarak servis edilir — zengin, dolgun gövdeli ve pürüzsüz.', 500.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(120, 56, 'White Wine (by the glass)', 'Beyaz Şarap Kadeh', 'wine-white-glass', 'House white wine served by the glass.', 'Our carefully selected house white wine, served by the glass — crisp, light, and refreshing.', 'Özenle seçilmiş ev beyaz şarabı, kadeh olarak servis edilir — çıtır, hafif ve ferahlatıcı.', 500.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00');
INSERT INTO `products` (`id`, `category_id`, `name`, `name_tr`, `slug`, `short_description`, `description`, `description_tr`, `price`, `image_url`, `status`, `created_at`, `updated_at`) VALUES
(121, 56, 'Rosé Wine (by the glass)', 'Rose Şarap Kadeh', 'wine-rose-glass', 'House rosé wine served by the glass.', 'Our carefully selected house rosé wine, served by the glass — fresh, fruity, and perfectly balanced.', 'Özenle seçilmiş ev rosé şarabı, kadeh olarak servis edilir — taze, meyveli ve mükemmel dengeli.', 500.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(122, 57, 'Pistachio', 'Antep Fıstığı', 'nuts-pistachio', 'Roasted Antep pistachios.', 'Premium roasted pistachios from Gaziantep (Antep) — rich, buttery, and deeply flavourful.', 'Gaziantep\'in kaliteli, zengin ve lezzetli kavrulmuş Antep fıstığı.', 310.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(123, 57, 'Almonds', 'Badem', 'nuts-almonds', 'Roasted almonds.', 'Lightly roasted almonds crunchy, nutritious, and satisfying.', 'Hafifçe kavrulmuş badem çıtır, besleyici ve doyurucu.', 260.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(124, 57, 'Hazelnuts', 'Fındık', 'nuts-hazelnuts', 'Roasted Turkish hazelnuts.', 'Roasted Turkish hazelnuts rich in flavour and naturally sweet, the finest from the Black Sea region.', 'Kavrulmuş Türk fındığı Karadeniz bölgesinin en iyisi, zengin aromalı ve doğal tatlılıkta.', 260.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(125, 57, 'Karnaval Mix', 'Karnaval', 'nuts-karnaval', 'Karnaval mixed snack selection.', 'Karnaval a mixed snack assortment perfect for sharing alongside your drinks.', 'Karnaval içeceklerin yanında paylaşmak için ideal, karışık atıştırmalık çerez seçkisi.', 310.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(126, 57, 'Yellow Chickpeas', 'Sarı Leblebi', 'nuts-yellow-chickpeas', 'Roasted yellow chickpeas a classic Turkish snack.', 'Sarı leblebi roasted yellow chickpeas, a beloved traditional Turkish snack, crunchy and light.', 'Sarı leblebi kavrulmuş ve çıtır, sevilen geleneksel Türk atıştırmalığı.', 210.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(127, 57, 'Salted Peanuts', 'Tuzlu Fıstık', 'nuts-salted-peanuts', 'Roasted and salted peanuts.', 'Classic roasted salted peanuts crunchy, perfectly seasoned, and ideal with a cold drink.', 'Klasik kavrulmuş tuzlu fıstık çıtır, mükemmel baharatlı ve soğuk içecekle mükemmel uyum.', 210.00, '', 1, '2026-05-22 13:02:00', '2026-05-22 13:02:00'),
(128, 53, 'Gordons Cin 70cl', 'Gordons GIN 70cl', 'gordons-cin-70cl', 'Gordon\'s London Dry Gin, 70cl bottle. Classic juniper-forward gin.', 'Gordon\'s London Dry Gin, 70cl bottle. Classic juniper-forward gin.', 'Gordon\'s London Dry Gin, 70cl şişe. Klasik, ardıç aromalı cin.', 0.00, '', 1, '2026-05-22 13:09:23', '2026-05-22 13:09:49'),
(129, 19, 'Espresso Single', 'Espresso Single', 'coffee-espresso-single', 'A single shot of rich, concentrated espresso.', 'A single shot of rich, concentrated espresso — bold, smooth, and full of flavour.', 'Yoğun ve konsantre, tek shot espresso — güçlü, pürüzsüz ve bol aromali.', 300.00, '', 1, '2026-05-24 12:26:02', '2026-05-24 12:26:02'),
(130, 19, 'Espresso Double', 'Espresso Double', 'coffee-espresso-double', 'A double shot of rich, concentrated espresso.', 'A double shot of rich espresso — stronger intensity with a deep, smooth finish.', 'Daha güçlü yoğunluğuyla derin ve pürüzsüz bir bitiş sunan çift shot espresso.', 310.00, '', 1, '2026-05-24 12:26:02', '2026-05-24 12:26:02'),
(131, 19, 'Americano', 'Americano', 'coffee-americano', 'Espresso shots topped with hot water for a smooth, full coffee.', 'Espresso diluted with hot water — smooth, full-bodied, and perfectly balanced.', 'Sıcak su ile seyreltilmiş espresso — pürüzsüz, dolgun gövdeli ve mükemmel dengeli.', 345.00, '', 1, '2026-05-24 12:26:02', '2026-05-24 12:26:02'),
(132, 19, 'Cortado', 'Cortado', 'coffee-cortado', 'Equal parts espresso and steamed milk for a balanced, smooth coffee.', 'Espresso cut with an equal amount of warm steamed milk — smooth, rich, and perfectly balanced.', 'Eşit miktarda ılık buharda pişirilmiş sütle yumuşatılmış espresso — pürüzsüz, zengin ve dengeli.', 370.00, '', 1, '2026-05-24 12:26:02', '2026-05-24 12:26:02'),
(133, 19, 'Flat White', 'Flat White', 'coffee-flat-white', 'Velvety steamed milk over a double ristretto shot.', 'A double ristretto topped with silky, velvety steamed milk — intense coffee flavour with a creamy finish.', 'Çift ristretto üzerine ipeksi buharda pişirilmiş süt — yoğun kahve aroması ve kremsi bitiş.', 370.00, '', 1, '2026-05-24 12:26:02', '2026-05-24 12:26:02'),
(134, 19, 'Cappucino', 'Cappucino', 'coffee-cappucino', 'Espresso with steamed milk and a thick layer of milk foam.', 'Classic cappucino — espresso topped with equal parts steamed milk and thick, airy foam.', 'Klasik cappucino — eşit oranda buharda pişirilmiş süt ve yoğun köpükle servis edilen espresso.', 370.00, '', 1, '2026-05-24 12:26:02', '2026-05-24 12:26:02'),
(135, 19, 'Cafe Latte', 'Cafe Latte', 'coffee-cafe-latte', 'Espresso with steamed milk and a light layer of foam.', 'Espresso blended with generous steamed milk and a light layer of foam — smooth, creamy, and comforting.', 'Bol buharda pişirilmiş süt ve ince köpük tabakasıyla espresso — pürüzsüz, kremsi ve rahatlatıcı.', 370.00, '', 1, '2026-05-24 12:26:02', '2026-05-24 12:26:02'),
(136, 19, 'Matcha Latte', 'Matcha Latte', 'coffee-matcha-latte', 'Premium ceremonial matcha whisked with steamed milk.', 'Premium ceremonial grade matcha whisked to a smooth paste and blended with velvety steamed milk.', 'Premium seremoni kalitesinde matcha, pürüzsüz bir macun haline getirilip kadifemsi buharda sütle karıştırılır.', 410.00, '', 1, '2026-05-24 12:26:02', '2026-05-24 12:26:02'),
(137, 19, 'Mocha', 'Mocha', 'coffee-mocha', 'Espresso with chocolate sauce and steamed milk.', 'Espresso combined with rich chocolate sauce and steamed milk — indulgent, sweet, and deeply satisfying.', 'Zengin çikolata sosu ve buharda pişirilmiş sütle espresso — bol, tatlı ve derin bir lezzet.', 380.00, '', 1, '2026-05-24 12:26:02', '2026-05-24 12:26:02'),
(138, 19, 'Hot Chocolate', 'Sıcak Çikolata', 'coffee-hot-chocolate', 'Rich and creamy premium hot chocolate.', 'Premium hot chocolate made with rich cocoa — thick, velvety, and deeply comforting.', 'Zengin kakao ile hazırlanan premium sıcak çikolata — yoğun, kadifemsi ve rahatlatıcı.', 370.00, '', 1, '2026-05-24 12:26:02', '2026-05-24 12:26:02'),
(139, 19, 'Türk Kahvesi', 'Türk Kahvesi', 'coffee-turk-kahvesi', 'Traditional Turkish coffee brewed in a copper cezve.', 'Traditional Turkish coffee, finely ground and brewed in a copper cezve — rich, aromatic, and full of heritage.', 'Ince öğütülmüş ve bakır cezvede pişirilen geleneksel Türk kahvesi — zengin, aromatik ve tarihe dolu.', 295.00, '', 1, '2026-05-24 12:26:02', '2026-05-24 12:26:02'),
(140, 19, 'Filter', 'Filter Kahve', 'coffee-filter', 'Slow-brewed filter coffee with a clean, smooth taste.', 'Slow-brewed filter coffee — clean, bright, and full of nuanced flavour notes.', 'Yavaş demlenmiş filtre kahve — temiz, parlak ve nüanslı aroma notalarıyla dolu.', 345.00, '', 1, '2026-05-24 12:26:02', '2026-05-24 12:26:02'),
(141, 19, 'Cold Brew', 'Cold Brew', 'coffee-cold-brew', 'Coffee steeped in cold water for 12+ hours for a smooth, low-acid brew.', 'Cold brew coffee steeped for over 12 hours in cold water — incredibly smooth, low in acidity, and naturally sweet.', '12 saatten fazla soğuk suda demlenen cold brew — son derece pürüzsüz, düşük asitli ve doğal tatlılıkta.', 425.00, '', 1, '2026-05-24 12:26:02', '2026-05-24 12:26:02'),
(142, 19, 'Iced Americano', 'Iced Americano', 'coffee-iced-americano', 'Chilled espresso shots over ice topped with cold water.', 'Double espresso shots poured over ice and topped with cold water — refreshing, bold, and smooth.', 'Buz üzerine dökülmüş çift espresso, soğuk su ile tamamlanır — ferahlatıcı, güçlü ve pürüzsüz.', 400.00, '', 1, '2026-05-24 12:26:02', '2026-05-24 12:26:02'),
(143, 19, 'Iced Latte', 'Iced Latte', 'coffee-iced-latte', 'Espresso poured over ice with cold milk.', 'Espresso poured over ice and blended with cold milk — smooth, creamy, and refreshingly cool.', 'Buz üzerine dökülmüş espresso ve soğuk süt — pürüzsüz, kremsi ve ferahlatıcı serinlikte.', 415.00, '', 1, '2026-05-24 12:26:02', '2026-05-24 12:26:02'),
(144, 19, 'Freddo Espresso', 'Freddo Espresso', 'coffee-freddo-espresso', 'Greek-style chilled and frothed espresso served over ice.', 'Greek-style freddo espresso — double espresso shaken to a creamy froth and served over ice.', 'Yunan usulü freddo espresso — çift espresso çalkalanarak kremsi köpük elde edilir ve buz üzerinde servis edilir.', 400.00, '', 1, '2026-05-24 12:26:02', '2026-05-24 12:26:02'),
(145, 19, 'Freddo Cappucino', 'Freddo Cappucino', 'coffee-freddo-cappucino', 'Greek-style chilled espresso topped with cold frothed milk.', 'Greek-style freddo cappucino — iced espresso topped with thick, cold frothed milk for a refreshing experience.', 'Yunan usulü freddo cappucino — buz espresso üzerine soğuk, yoğun köpüklü süt ile servis edilir.', 415.00, '', 1, '2026-05-24 12:26:02', '2026-05-24 12:26:02'),
(146, 19, 'Freddo Flat White', 'Freddo Flat White', 'coffee-freddo-flat-white', 'Greek-style chilled flat white with velvety cold milk foam.', 'Greek-style freddo flat white — iced double ristretto with silky cold milk foam for a smooth, intense coffee experience.', 'Yunan usulü freddo flat white — çift ristretto üzerine ipeksi soğuk süt köpüğü ile yoğun kahve deneyimi.', 415.00, '', 1, '2026-05-24 12:26:02', '2026-05-24 12:26:02'),
(147, 19, 'Iced Chocolate Mocha', 'Iced Chocolate Mocha', 'iced-chocolate-mocha', 'Iced espresso with chocolate sauce and cold milk.', 'Espresso combined with chocolate sauce over ice with cold milk — a rich, indulgent iced coffee treat.', 'Espresso, çikolata sosu, buz ve soğuk süt ile birleştirilir — zengin ve lezzetli soğuk kahve keyfi.', 415.00, '', 1, '2026-05-24 12:26:02', '2026-05-24 13:24:33'),
(148, 19, 'Iced Chocolate', 'Iced Chocolate', 'iced-chocolate', 'Rich cold chocolate drink served over ice.', 'Rich chocolate blended with cold milk and poured over ice — a refreshing and indulgent chocolate treat.', 'Zengin çikolata, soğuk sütle harmanlanıp buz üzerine dökülür — ferahlatıcı ve lezzetli çikolata keyfi.', 400.00, '', 1, '2026-05-24 12:26:02', '2026-05-24 13:24:50'),
(149, 19, 'Iced Matcha Latte', 'Iced Matcha Latte', 'iced-matcha-latte', 'Premium matcha blended with cold milk and served over ice.', 'Premium ceremonial matcha blended with cold milk and poured over ice — earthy, refreshing, and naturally sweet.', 'Premium seremoni kalitesinde matcha, soğuk sütle karıştırılıp buz üzerine dökülür — toprak aroması, ferahlatıcı ve doğal tatlılıkta.', 455.00, '', 1, '2026-05-24 12:26:02', '2026-05-24 13:23:10'),
(150, 19, 'Extra', 'Ekstra', 'extra', '(Caramel/Vanilla/Strawberry/Chocolate)', '', '(Karamel / Vanilya / Çilek / Çikolata)', 35.00, '', 1, '2026-05-24 12:26:02', '2026-05-24 13:27:36'),
(151, 19, 'Vegan Oat Milk Extra', 'Vegan Yulaf Sütü Ekstrası', 'vegan-oat-milk-extra', 'Upgrade any drink with vegan oat milk.', 'Upgrade your drink with plant-based oat milk — creamy, vegan-friendly, and delicious.', 'Bitkisel yulaf sütü ile içeceğinizi yükseltin — kremsi, vegan dostu ve lezzetli.', 80.00, '', 1, '2026-05-24 12:26:02', '2026-05-24 13:23:50'),
(152, 51, 'Stella Artois', 'Stella Artois', 'stella-artois', '', '', '', 470.00, '', 1, '2026-05-26 17:01:09', '2026-05-26 17:01:09'),
(153, 55, 'Baileys', 'Baileys', 'baileys', '', '', '', 800.00, '', 1, '2026-05-29 13:33:48', '2026-05-29 13:33:48'),
(154, 59, 'Layd', 'Layd', 'layd', '', '', '', 1000.00, '', 1, '2026-05-29 13:35:29', '2026-05-29 13:35:29'),
(155, 59, 'Love', 'Love', 'love', '', '', '', 1000.00, '', 1, '2026-05-29 13:35:38', '2026-05-29 13:35:38'),
(156, 59, 'Moskow', 'Moskow', 'moskow', '', '', '', 1000.00, '', 1, '2026-05-29 13:35:47', '2026-05-29 13:35:47'),
(157, 59, 'Merlin', 'Merlin', 'merlin', '', '', '', 1000.00, '', 1, '2026-05-29 13:35:59', '2026-05-29 13:35:59'),
(158, 59, 'Mastic', 'Mastic', 'mastic', '', '', '', 1000.00, '', 1, '2026-05-29 13:36:09', '2026-05-29 13:36:09'),
(159, 59, 'BlueBerry', 'BlueBerry', 'blueberry', '', '', '', 1000.00, '', 1, '2026-05-29 13:36:20', '2026-05-29 13:36:20'),
(160, 59, 'Coca-Cola', 'Coca-Cola', 'coca-cola', '', '', '', 1000.00, '', 1, '2026-05-29 13:36:30', '2026-05-29 13:36:30'),
(161, 59, 'Melon', 'Melon', 'melon', '', '', '', 1000.00, '', 1, '2026-05-29 13:36:46', '2026-05-29 13:36:46'),
(162, 59, 'Watermelon', 'Watermelon', 'watermelon', '', '', '', 1000.00, '', 1, '2026-05-29 13:36:56', '2026-05-29 13:36:56'),
(163, 59, 'Bakü', 'Bakü', 'bakü', '', '', '', 1000.00, '', 1, '2026-05-29 13:37:11', '2026-05-29 13:37:11'),
(164, 59, 'Baked Peaches', 'Baked Peaches', 'baked-peaches', '', '', '', 1000.00, '', 1, '2026-05-29 13:37:33', '2026-05-29 13:37:33'),
(165, 59, 'Dejaw', 'Dejaw', 'dejaw', '', '', '', 1000.00, '', 1, '2026-05-29 13:38:38', '2026-05-29 13:38:38'),
(166, 59, '[EXTRAS] Ice Hose', '[EKSTRA] Ice Hose', '[extras]-ice-hose', '', '', '', 200.00, '', 1, '2026-05-29 13:39:45', '2026-05-29 13:39:45'),
(167, 59, '[EXTRAS] Changing The Head', '[EKSTRA] Changing The Head', '[extras]-changing-the-head', '', '', '', 750.00, '', 1, '2026-05-29 13:40:56', '2026-05-29 13:40:56'),
(168, 7, 'Arugula Salad', 'Roka Salatası', 'arugula-salad', 'Pink Tomatoes, Arugula, Parmesan and Balsamic Glaze', 'Pembe Domates,Roka, Permesan ve Glza Balzamic', '', 750.00, '', 1, '2026-06-01 14:40:04', '2026-06-01 14:40:04');
--
-- Dökümü yapılmış tablolar için indeksler
--
--
-- Tablo için indeksler `products`
--
ALTER TABLE `products`
ADD PRIMARY KEY (`id`),
ADD KEY `category_id` (`category_id`);
--
-- Dökümü yapılmış tablolar için AUTO_INCREMENT değeri
--
--
-- Tablo için AUTO_INCREMENT değeri `products`
--
ALTER TABLE `products`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=169;
COMMIT;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;