diff --git a/.gitignore b/.gitignore
index b7ac666..51e763f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,3 +42,5 @@ next-env.d.ts
# brainstorming sessions
.superpowers/
+
+/app/generated/prisma
diff --git a/README.md b/README.md
index e215bc4..f9caa64 100644
--- a/README.md
+++ b/README.md
@@ -1,36 +1,34 @@
-This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
+# Kozmos QR Menu
-## Getting Started
+Modern, minimalist ve çok dilli (TR/EN) QR Menü Uygulaması.
-First, run the development server:
+## Özellikler
-```bash
-npm run dev
-# or
-yarn dev
-# or
-pnpm dev
-# or
-bun dev
-```
+- **Çift Dil Desteği (i18n):** Türkçe ve İngilizce menü seçenekleri
+- **Admin Paneli:** Ürün, kategori ekleme/silme ve sıralama (Sürükle-bırak benzeri butonlar ile)
+- **Minimalist UI Tasarımı:** Kullanıcı dostu, modern arayüz
+- **PostgreSQL & Prisma:** Güvenli ve performanslı veri yönetimi
+- **Resim Yükleme:** Admin paneli üzerinden ürünlere görsel ekleyebilme
+- **Responsive:** Tüm mobil cihazlar ile tam uyumlu görünüm
-Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+## Kurulum
-You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
+1. Depoyu klonlayın ve bağımlılıkları yükleyin:
+ ```bash
+ npm install
+ ```
-This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
+2. `.env` ve `.env.local` dosyalarınızı ayarlayın (Veritabanı bağlantısı vb.).
-## Learn More
+3. Veritabanını güncelleyin ve istemciyi oluşturun:
+ ```bash
+ npx prisma db push
+ npx prisma generate
+ ```
-To learn more about Next.js, take a look at the following resources:
+4. Geliştirme sunucusunu başlatın:
+ ```bash
+ npm run dev
+ ```
-- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
-- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
-
-You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
-
-## Deploy on Vercel
-
-The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
-
-Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
+Uygulamaya `http://localhost:3000` adresinden ulaşabilirsiniz.
diff --git a/app/[lang]/page.tsx b/app/[lang]/page.tsx
index 70ae7db..1cd37cc 100644
--- a/app/[lang]/page.tsx
+++ b/app/[lang]/page.tsx
@@ -1,6 +1,6 @@
import { notFound } from 'next/navigation'
import { getDictionary, hasLocale } from './dictionaries'
-import { categories } from '@/data/menu'
+import { getCategories } from '@/data/menu'
import Header from '@/components/Header'
import CategorySection from '@/components/CategorySection'
import BottomTabBar from '@/components/BottomTabBar'
@@ -15,6 +15,7 @@ export default async function MenuPage({
if (!hasLocale(lang)) notFound()
const dict = await getDictionary(lang)
+ const categories = await getCategories()
return (
<>
diff --git a/app/actions/auth.ts b/app/actions/auth.ts
new file mode 100644
index 0000000..052764d
--- /dev/null
+++ b/app/actions/auth.ts
@@ -0,0 +1,33 @@
+'use server'
+
+import { cookies } from 'next/headers'
+import { redirect } from 'next/navigation'
+
+export async function login(prevState: any, formData: FormData) {
+ const username = formData.get('username')
+ const password = formData.get('password')
+
+ if (
+ username === process.env.ADMIN_USERNAME &&
+ password === process.env.ADMIN_PASSWORD
+ ) {
+ const cookieStore = await cookies()
+ cookieStore.set('admin_session', process.env.ADMIN_SECRET || 'secret', {
+ httpOnly: true,
+ secure: process.env.NODE_ENV === 'production',
+ sameSite: 'lax',
+ path: '/',
+ maxAge: 60 * 60 * 24 * 7 // 1 week
+ })
+
+ redirect('/admin')
+ } else {
+ return { error: 'Geçersiz kullanıcı adı veya şifre' }
+ }
+}
+
+export async function logout() {
+ const cookieStore = await cookies()
+ cookieStore.delete('admin_session')
+ redirect('/admin/login')
+}
diff --git a/app/actions/menu.ts b/app/actions/menu.ts
new file mode 100644
index 0000000..fb6a318
--- /dev/null
+++ b/app/actions/menu.ts
@@ -0,0 +1,22 @@
+'use server'
+
+import { saveCategories, type Category } from '@/data/menu'
+import { revalidatePath } from 'next/cache'
+import { cookies } from 'next/headers'
+
+async function checkAuth() {
+ const cookieStore = await cookies()
+ const session = cookieStore.get('admin_session')?.value
+ if (!session || session !== (process.env.ADMIN_SECRET || 'secret')) {
+ throw new Error('Unauthorized')
+ }
+}
+
+export async function saveEntireMenu(categories: Category[]) {
+ await checkAuth()
+ await saveCategories(categories)
+ // Revalidate both language paths
+ revalidatePath('/tr')
+ revalidatePath('/en')
+ return { success: true }
+}
diff --git a/app/actions/upload.ts b/app/actions/upload.ts
new file mode 100644
index 0000000..3ae9993
--- /dev/null
+++ b/app/actions/upload.ts
@@ -0,0 +1,34 @@
+'use server'
+
+import { promises as fs } from 'fs'
+import path from 'path'
+import { cookies } from 'next/headers'
+
+export async function uploadImage(formData: FormData) {
+ const cookieStore = await cookies()
+ const session = cookieStore.get('admin_session')?.value
+ const secret = process.env.ADMIN_SECRET || 'secret'
+
+ if (!session || session !== secret) {
+ throw new Error('Unauthorized')
+ }
+
+ const file = formData.get('file') as File
+ if (!file) {
+ throw new Error('No file uploaded')
+ }
+
+ const bytes = await file.arrayBuffer()
+ const buffer = Buffer.from(bytes)
+
+ const extension = path.extname(file.name) || '.jpg'
+ const filename = `${Date.now()}-${Math.random().toString(36).substring(7)}${extension}`
+
+ const uploadDir = path.join(process.cwd(), 'public', 'uploads')
+ await fs.mkdir(uploadDir, { recursive: true })
+
+ const filePath = path.join(uploadDir, filename)
+ await fs.writeFile(filePath, buffer)
+
+ return { url: `/uploads/${filename}` }
+}
diff --git a/app/admin/layout.tsx b/app/admin/layout.tsx
new file mode 100644
index 0000000..9df5483
--- /dev/null
+++ b/app/admin/layout.tsx
@@ -0,0 +1,25 @@
+import { ReactNode } from 'react'
+import { logout } from '@/app/actions/auth'
+
+export default function AdminLayout({ children }: { children: ReactNode }) {
+ return (
+
+
+
+
+ {children}
+
+
+ )
+}
diff --git a/app/admin/login/page.tsx b/app/admin/login/page.tsx
new file mode 100644
index 0000000..54c5dbf
--- /dev/null
+++ b/app/admin/login/page.tsx
@@ -0,0 +1,61 @@
+'use client'
+
+import { useActionState } from 'react'
+import { login } from '@/app/actions/auth'
+
+export default function LoginPage() {
+ const [state, formAction, isPending] = useActionState(login, null)
+
+ return (
+
+
+
+
Kozmos Admin
+
Yönetim paneline giriş yapın
+
+
+
+
+
+ )
+}
diff --git a/app/admin/page.tsx b/app/admin/page.tsx
new file mode 100644
index 0000000..f455e0c
--- /dev/null
+++ b/app/admin/page.tsx
@@ -0,0 +1,10 @@
+import { getCategories } from '@/data/menu'
+import AdminDashboard from '@/components/admin/AdminDashboard'
+
+export const dynamic = 'force-dynamic'
+
+export default async function AdminPage() {
+ const categories = await getCategories()
+
+ return
+}
diff --git a/components/CategorySection.tsx b/components/CategorySection.tsx
index 4dd7d8b..8bef4f1 100644
--- a/components/CategorySection.tsx
+++ b/components/CategorySection.tsx
@@ -11,25 +11,17 @@ export default function CategorySection({ category, lang, currency }: Props) {
const l = lang as 'tr' | 'en'
return (
-
+
{/* Category header */}
-
-
- {/* Emoji badge with gradient ring */}
+
+
+ {/* Subtle minimal Emoji badge */}
- {/* Outer glow ring */}
-
{category.emoji}
@@ -37,75 +29,43 @@ export default function CategorySection({ category, lang, currency }: Props) {
{/* Title */}
-
+
{category.label[l]}
+ {/* Item count badge */}
+
+ ({category.items.length})
+
-
- {/* Item count badge */}
-
- {category.items.length}
-
- {/* Decorative divider */}
-
-
-
- {/* Items card */}
-
- {/* Thin top gradient line */}
+ {/* Minimalist divider */}
+
-
- {category.items.map((item, i) => (
-
- ))}
-
+ {/* Items list (no background card) */}
+
+ {category.items.map((item, i) => (
+
+ ))}
)
diff --git a/components/MenuItemRow.tsx b/components/MenuItemRow.tsx
index be5cbdc..25506da 100644
--- a/components/MenuItemRow.tsx
+++ b/components/MenuItemRow.tsx
@@ -13,48 +13,56 @@ export default function MenuItemRow({ item, lang, currency, index, isLast }: Pro
return (
- {/* Emoji circle with gradient */}
+ {/* Image or Emoji circle */}
- {item.emoji || '✨'}
+ {item.image ? (
+
![{item.name[l]}]({item.image})
+ ) : (
+ item.emoji || '✨'
+ )}
- {/* Text */}
-
-
- {item.name[l]}
-
+ {/* Text Content */}
+
+
+
+ {item.name[l]}
+
+
+ {/* Price */}
+ {item.price !== undefined && (
+
+ {item.price} {currency}
+
+ )}
+
+
{item.description && (
{item.description[l]}
)}
-
- {/* Price pill */}
- {item.price !== undefined && (
-
- {currency}{item.price}
-
- )}
)
}
diff --git a/components/admin/AdminDashboard.tsx b/components/admin/AdminDashboard.tsx
new file mode 100644
index 0000000..c03c12d
--- /dev/null
+++ b/components/admin/AdminDashboard.tsx
@@ -0,0 +1,356 @@
+'use client'
+
+import { useState, useTransition, useEffect } from 'react'
+import type { Category, MenuItem } from '@/data/menu'
+import { saveEntireMenu } from '@/app/actions/menu'
+import { uploadImage } from '@/app/actions/upload'
+
+export default function AdminDashboard({ initialCategories }: { initialCategories: Category[] }) {
+ const [categories, setCategories] = useState
(initialCategories)
+ const [isPending, startTransition] = useTransition()
+ const [isSaved, setIsSaved] = useState(false)
+ const [selectedCatId, setSelectedCatId] = useState(initialCategories[0]?.id || null)
+
+ // Auto-select first category if selected one is deleted
+ useEffect(() => {
+ if (selectedCatId && !categories.find(c => c.id === selectedCatId) && categories.length > 0) {
+ setSelectedCatId(categories[0].id)
+ }
+ }, [categories, selectedCatId])
+
+ const handleSave = () => {
+ startTransition(async () => {
+ await saveEntireMenu(categories)
+ setIsSaved(true)
+ setTimeout(() => setIsSaved(false), 3000)
+ })
+ }
+
+ const handleImageUpload = async (catId: string, itemId: string, e: React.ChangeEvent) => {
+ const file = e.target.files?.[0]
+ if (!file) return
+ const formData = new FormData()
+ formData.append('file', file)
+ try {
+ const res = await uploadImage(formData)
+ if (res?.url) {
+ updateItem(catId, itemId, 'image', res.url)
+ }
+ } catch (err) {
+ console.error(err)
+ alert("Resim yüklenemedi.")
+ }
+ }
+
+ // --- Item CRUD ---
+ const updateItem = (catId: string, itemId: string, field: string, value: any, lang?: 'tr' | 'en') => {
+ setCategories(cats => cats.map(cat => {
+ if (cat.id !== catId) return cat
+ return {
+ ...cat,
+ items: cat.items.map(item => {
+ if (item.id !== itemId) return item
+ if (lang) {
+ return { ...item, [field]: { ...(item as any)[field], [lang]: value } }
+ }
+ return { ...item, [field]: value }
+ })
+ }
+ }))
+ }
+
+ const addItem = (catId: string) => {
+ const newItem: MenuItem = {
+ id: `item-${Date.now()}`,
+ name: { tr: 'Yeni Ürün', en: 'New Item' },
+ description: { tr: '', en: '' },
+ price: 0,
+ highlight: false,
+ }
+ setCategories(cats => cats.map(cat => {
+ if (cat.id !== catId) return cat
+ return { ...cat, items: [...cat.items, newItem] }
+ }))
+ }
+
+ const deleteItem = (catId: string, itemId: string) => {
+ if (!confirm('Bu ürünü silmek istediğinize emin misiniz?')) return
+ setCategories(cats => cats.map(cat => {
+ if (cat.id !== catId) return cat
+ return { ...cat, items: cat.items.filter(item => item.id !== itemId) }
+ }))
+ }
+
+ const moveItem = (catId: string, itemIndex: number, direction: 'up' | 'down') => {
+ setCategories(cats => cats.map(cat => {
+ if (cat.id !== catId) return cat
+ const newItems = [...cat.items]
+ if (direction === 'up' && itemIndex > 0) {
+ [newItems[itemIndex - 1], newItems[itemIndex]] = [newItems[itemIndex], newItems[itemIndex - 1]]
+ } else if (direction === 'down' && itemIndex < newItems.length - 1) {
+ [newItems[itemIndex + 1], newItems[itemIndex]] = [newItems[itemIndex], newItems[itemIndex + 1]]
+ }
+ return { ...cat, items: newItems }
+ }))
+ }
+
+ // --- Category CRUD ---
+ const updateCategory = (catId: string, field: string, value: any, lang?: 'tr' | 'en') => {
+ setCategories(cats => cats.map(cat => {
+ if (cat.id !== catId) return cat
+ if (lang && field === 'label') {
+ return { ...cat, label: { ...cat.label, [lang]: value } }
+ }
+ return { ...cat, [field]: value }
+ }))
+ }
+
+ const addCategory = () => {
+ const newCategory: Category = {
+ id: `cat-${Date.now()}`,
+ emoji: '🍽️',
+ label: { tr: 'Yeni Kategori', en: 'New Category' },
+ items: [],
+ }
+ setCategories(cats => [...cats, newCategory])
+ setSelectedCatId(newCategory.id)
+ }
+
+ const deleteCategory = (catId: string) => {
+ if (!confirm('Bu kategoriyi ve içindeki tüm ürünleri silmek istediğinize emin misiniz?')) return
+ setCategories(cats => cats.filter(cat => cat.id !== catId))
+ }
+
+ const moveCategory = (catIndex: number, direction: 'up' | 'down') => {
+ setCategories(cats => {
+ const newCats = [...cats]
+ if (direction === 'up' && catIndex > 0) {
+ [newCats[catIndex - 1], newCats[catIndex]] = [newCats[catIndex], newCats[catIndex - 1]]
+ } else if (direction === 'down' && catIndex < newCats.length - 1) {
+ [newCats[catIndex + 1], newCats[catIndex]] = [newCats[catIndex], newCats[catIndex + 1]]
+ }
+ return newCats
+ })
+ }
+
+ const selectedCategory = categories.find(c => c.id === selectedCatId)
+
+ return (
+
+ {/* Top Bar */}
+
+
Menü Yönetimi
+
+
+
+
+
+
+ {/* Left Sidebar (Categories) */}
+
+
+
+
+
+ {categories.map((cat, catIndex) => (
+
setSelectedCatId(cat.id)}
+ >
+
+ {cat.emoji}
+
+ {cat.label.tr}
+
+
+
+ {/* Category Actions (visible on hover or when selected) */}
+
+
+
+
+
+
+ ))}
+ {categories.length === 0 && (
+
Kategori bulunamadı.
+ )}
+
+
+
+ {/* Right Content (Items) */}
+
+ {selectedCategory ? (
+
+ {/* Category Edit Header */}
+
+
Kategori Ayarları
+
+
+ updateCategory(selectedCategory.id, 'emoji', e.target.value)}
+ className="w-14 h-14 text-center text-3xl border border-gray-300 rounded-xl bg-gray-50"
+ title="Kategori Emojisi"
+ />
+ Emoji
+
+
+
+
+
+ {/* Items List */}
+
+
+
Ürünler ({selectedCategory.items.length})
+
+
+ {selectedCategory.items.map((item, itemIndex) => (
+
+ {/* Item Actions (Top Right) */}
+
+
+
+
+
+
+
+ {/* TR Fields */}
+
+
Türkçe (TR)
+
+
+ updateItem(selectedCategory.id, item.id, 'name', e.target.value, 'tr')}
+ className="w-full text-sm px-3 py-2 border rounded-md bg-gray-50 focus:bg-white"
+ />
+
+
+
+
+
+
+ {/* EN Fields */}
+
+
İngilizce (EN)
+
+
+ updateItem(selectedCategory.id, item.id, 'name', e.target.value, 'en')}
+ className="w-full text-sm px-3 py-2 border rounded-md bg-gray-50 focus:bg-white"
+ />
+
+
+
+
+
+
+
+ {/* Common Fields */}
+
+
+
+ updateItem(selectedCategory.id, item.id, 'price', Number(e.target.value))}
+ className="w-full text-sm px-3 py-2 border rounded-md bg-gray-50 focus:bg-white font-mono"
+ />
+
+
+
+ updateItem(selectedCategory.id, item.id, 'emoji', e.target.value)}
+ className="w-full text-sm px-3 py-2 border rounded-md bg-gray-50 focus:bg-white text-center text-xl"
+ />
+
+
+
+
handleImageUpload(selectedCategory.id, item.id, e)}
+ className="w-full text-xs px-2 py-1.5 border rounded-md file:mr-2 file:py-1 file:px-2 file:rounded file:border-0 file:bg-emerald-50 file:text-emerald-700 file:text-xs"
+ />
+ {item.image && (
+
+

+
+
+ )}
+
+
+
+ ))}
+
+
+
+
+ ) : (
+
+ Düzenlemek için sol taraftan bir kategori seçin.
+
+ )}
+
+
+
+ )
+}
diff --git a/data/menu.json b/data/menu.json
new file mode 100644
index 0000000..1aa45f4
--- /dev/null
+++ b/data/menu.json
@@ -0,0 +1,1628 @@
+[
+ {
+ "id": "kahvaltiliklar",
+ "emoji": "🍳",
+ "label": {
+ "tr": "Kahvaltılıklar",
+ "en": "Breakfasts"
+ },
+ "items": [
+ {
+ "id": "k1",
+ "price": 180,
+ "name": {
+ "tr": "Sucuklu Yumurta",
+ "en": "Eggs with Sucuk"
+ },
+ "description": {
+ "tr": "Kaşar peynirli, çıtır domates",
+ "en": "Kaşar cheese, crispy tomato"
+ }
+ },
+ {
+ "id": "k2",
+ "price": 150,
+ "name": {
+ "tr": "Menemen",
+ "en": "Menemen"
+ },
+ "description": {
+ "tr": "Domates, yeşil biber, 2 yumurta",
+ "en": "Tomato, green pepper, 2 eggs"
+ }
+ },
+ {
+ "id": "k3",
+ "price": 175,
+ "name": {
+ "tr": "Kaşarlı Menemen",
+ "en": "Menemen with Kaşar Cheese"
+ },
+ "description": {
+ "tr": "Rondanın 5 kaşar peyniri, domates, yeşil biber, çeşitli 2 yumurta",
+ "en": "5 portions of kaşar cheese, tomato, green pepper, 2 eggs"
+ }
+ },
+ {
+ "id": "k4",
+ "price": 140,
+ "name": {
+ "tr": "Sade Omlet",
+ "en": "Plain Omelette"
+ },
+ "description": {
+ "tr": "Çeşitme 2 yumurta, mezelemeniz kaşar peyniri",
+ "en": "2 eggs, kaşar cheese"
+ }
+ },
+ {
+ "id": "k5",
+ "price": 165,
+ "name": {
+ "tr": "Mantarlı Omlet",
+ "en": "Mushroom Omelette"
+ },
+ "description": {
+ "tr": "Mantarlarla zenginleştirilmiş kaşar peyniri, çıtır biber, çimento, yeşil biber, satır, mantarı",
+ "en": "Mushrooms, kaşar cheese, crispy peppers"
+ }
+ },
+ {
+ "id": "k6",
+ "price": 170,
+ "name": {
+ "tr": "Sebzeli Omlet",
+ "en": "Vegetable Omelette"
+ },
+ "description": {
+ "tr": "Mantarlar, zenginleştirilmiş kaşar peyniri, kapıya biber, çimento, yeşil biber ve saman mantarı",
+ "en": "Mushrooms, kaşar cheese, capia pepper, green pepper"
+ }
+ },
+ {
+ "id": "k7",
+ "price": 210,
+ "name": {
+ "tr": "Vegan Kahvaltı",
+ "en": "Vegan Breakfast"
+ },
+ "description": {
+ "tr": "Avokado, çeşit domatesi, zeytin, tost, roka, havuç",
+ "en": "Avocado, cherry tomato, olives, toast, arugula, carrot"
+ }
+ },
+ {
+ "id": "k8",
+ "price": 650,
+ "name": {
+ "tr": "Serpme Kahvaltı – 2 Kişilik",
+ "en": "Mixed Breakfast - For 2"
+ },
+ "description": {
+ "tr": "Kaşar peyniri, bal & kaymak, siyah zeytin, yeşil zeytin, sürüm, hinse türreyi, çeşit domatesi, erikçimler, tatlıpırçık, havuç, maydanoz ve sonsuz çay",
+ "en": "Kaşar cheese, honey & clotted cream, black & green olives, cherry tomato, jams, carrot, parsley and unlimited tea"
+ }
+ }
+ ]
+ },
+ {
+ "id": "burgerler",
+ "emoji": "🍔",
+ "label": {
+ "tr": "Burgerler",
+ "en": "Burgers"
+ },
+ "items": [
+ {
+ "id": "b1",
+ "price": 250,
+ "name": {
+ "tr": "Kids Smash Burger",
+ "en": "Kids Smash Burger"
+ },
+ "description": {
+ "tr": "Kids Smash burger, cheddar peyniri, çıtır patates, kızartılmış turşu, domates, marul, burger sosu, patates cipsi",
+ "en": "Kids Smash burger, cheddar cheese, crispy fries, fried pickles, tomato, lettuce, burger sauce, potato chips"
+ }
+ },
+ {
+ "id": "b2",
+ "price": 320,
+ "name": {
+ "tr": "Dana Smash Burger",
+ "en": "Beef Smash Burger"
+ },
+ "description": {
+ "tr": "Dana Smash burger, cheddar peyniri, karamelize soğan, turşu, domates, marul, burger sosu, patates cipsi",
+ "en": "Beef Smash burger, cheddar cheese, caramelized onions, pickles, tomato, lettuce, burger sauce, potato chips"
+ }
+ },
+ {
+ "id": "b3",
+ "price": 390,
+ "name": {
+ "tr": "Dana Smash Big Burger",
+ "en": "Beef Smash Big Burger"
+ },
+ "description": {
+ "tr": "Dana Smash burger smash kafası, cheddar peyniri, karamelize soğan, çıtır patates, kızartılmış turşu, domates, marul, burger sosu, patates cipsi",
+ "en": "Beef Smash big burger, cheddar cheese, caramelized onions, crispy fries, fried pickles, tomato, lettuce, burger sauce, potato chips"
+ }
+ },
+ {
+ "id": "b4",
+ "price": 280,
+ "name": {
+ "tr": "Çıtır Tavuk Burger",
+ "en": "Crispy Chicken Burger"
+ },
+ "description": {
+ "tr": "Çıtır Tavuk Burger harika cheddar peyniri, burger sos, patates cipsi",
+ "en": "Crispy Chicken Burger, cheddar cheese, burger sauce, potato chips"
+ }
+ }
+ ]
+ },
+ {
+ "id": "tatlilar",
+ "emoji": "🍮",
+ "label": {
+ "tr": "Tatlılar",
+ "en": "Desserts"
+ },
+ "items": [
+ {
+ "id": "t1",
+ "price": 180,
+ "name": {
+ "tr": "Meşhur Akyaka Tatlısı",
+ "en": "Famous Akyaka Dessert"
+ },
+ "description": {
+ "tr": "Deniz tuzlu, bürüme, bitter çikolata, Gökova sütlemi",
+ "en": "Sea salt, bitter chocolate, Gökova milk"
+ }
+ },
+ {
+ "id": "t2",
+ "price": 140,
+ "name": {
+ "tr": "Fırın Helva",
+ "en": "Baked Halva"
+ }
+ }
+ ]
+ },
+ {
+ "id": "tostlar",
+ "emoji": "🥪",
+ "label": {
+ "tr": "Tostlar",
+ "en": "Toasts"
+ },
+ "items": [
+ {
+ "id": "ts1",
+ "price": 150,
+ "name": {
+ "tr": "Peynirli Tost",
+ "en": "Cheese Toast"
+ },
+ "description": {
+ "tr": "Kaşar peyniri, çeşit domatesi, zeytin, patates cipsi",
+ "en": "Kaşar cheese, cherry tomato, olives, potato chips"
+ }
+ },
+ {
+ "id": "ts2",
+ "price": 170,
+ "name": {
+ "tr": "Sucuklu Tost",
+ "en": "Sucuk Toast"
+ },
+ "description": {
+ "tr": "Kaşar sucuklu, çeşit domatesi, zeytin, patates cipsi",
+ "en": "Sucuk, kaşar cheese, cherry tomato, olives, potato chips"
+ }
+ },
+ {
+ "id": "ts3",
+ "price": 185,
+ "name": {
+ "tr": "Akdeniz Tost",
+ "en": "Mediterranean Toast"
+ },
+ "description": {
+ "tr": "Kaşar pul, Ezine peyniri, tulum peyniri, kaşar domates, çeşit domatesi, zeytin, patates cipsi",
+ "en": "Ezine cheese, tulum cheese, kaşar cheese, cherry tomato, olives, potato chips"
+ }
+ },
+ {
+ "id": "ts4",
+ "price": 180,
+ "name": {
+ "tr": "Karışık Tost",
+ "en": "Mixed Toast"
+ },
+ "description": {
+ "tr": "Kaşar sucuklu, kaşar peyniri, peri domates, zeytin, salatalık, dilimlen",
+ "en": "Sucuk, kaşar cheese, cherry tomato, olives, cucumber slices"
+ }
+ },
+ {
+ "id": "ts5",
+ "price": 195,
+ "name": {
+ "tr": "Ayvalık Tostu",
+ "en": "Ayvalik Toast"
+ },
+ "description": {
+ "tr": "Ekşi, karamize turşu, kırmızı bilet kaşar peyniri, çeşit domatesi, salatalık, dilimlen",
+ "en": "Sourdough bread, caramelized pickles, red pepper kaşar cheese, cherry tomato, cucumber slices"
+ }
+ }
+ ]
+ },
+ {
+ "id": "salatalar",
+ "emoji": "🥗",
+ "label": {
+ "tr": "Salatalar",
+ "en": "Salads"
+ },
+ "items": [
+ {
+ "id": "sl1",
+ "price": 160,
+ "name": {
+ "tr": "Çoban Salata",
+ "en": "Shepherd's Salad"
+ }
+ },
+ {
+ "id": "sl2",
+ "price": 185,
+ "name": {
+ "tr": "Roka Salatası",
+ "en": "Arugula Salad"
+ },
+ "description": {
+ "tr": "Domates, zenginleştirilmiş üzüme bol peyniri",
+ "en": "Tomato, grapes, cheese"
+ }
+ },
+ {
+ "id": "sl3",
+ "price": 210,
+ "name": {
+ "tr": "Yaz Salatası",
+ "en": "Summer Salad"
+ },
+ "description": {
+ "tr": "Kaşar ve her, taze domates, kırmızı soğan, beyaz peynir, fasulye, çeşitli yeşil besinleri, dalya üzümleri, çeşit domatesi",
+ "en": "Fresh tomato, red onion, white cheese, beans, mixed greens, grapes, cherry tomato"
+ }
+ },
+ {
+ "id": "sl4",
+ "price": 220,
+ "name": {
+ "tr": "Avokado Salatası",
+ "en": "Avocado Salad"
+ },
+ "description": {
+ "tr": "Avokado, çeşit domatesi, salatalık, yeşil zeytin",
+ "en": "Avocado, cherry tomato, cucumber, green olives"
+ }
+ },
+ {
+ "id": "sl5",
+ "price": 250,
+ "name": {
+ "tr": "Ton Balıklı Kinoalı Salata",
+ "en": "Tuna & Quinoa Salad"
+ },
+ "description": {
+ "tr": "Siyah kinoa, ton balığı, maydanoz, çeşit domatesi, salatalık, dilimlen",
+ "en": "Black quinoa, tuna fish, parsley, cherry tomato, cucumber slices"
+ }
+ }
+ ]
+ },
+ {
+ "id": "pizzalar",
+ "emoji": "🍕",
+ "label": {
+ "tr": "Pizzalar",
+ "en": "Pizzas"
+ },
+ "items": [
+ {
+ "id": "pz1",
+ "price": 280,
+ "name": {
+ "tr": "Margherita Pizza",
+ "en": "Margherita Pizza"
+ },
+ "description": {
+ "tr": "Pizza sosu, mozzarella peyniri, fesleğen, zeytin makul",
+ "en": "Pizza sauce, mozzarella cheese, basil, olives"
+ }
+ },
+ {
+ "id": "pz2",
+ "price": 310,
+ "name": {
+ "tr": "Akdeniz Pizza",
+ "en": "Mediterranean Pizza"
+ },
+ "description": {
+ "tr": "Kaşar peyniri, yeşil biber, mantar, fesleğen, mozzarella peyniri, piliç sosu",
+ "en": "Kaşar cheese, green pepper, mushroom, basil, mozzarella cheese"
+ }
+ },
+ {
+ "id": "pz3",
+ "price": 330,
+ "name": {
+ "tr": "Sucuk Pizza",
+ "en": "Sucuk Pizza"
+ },
+ "description": {
+ "tr": "Yeşil biber, domates biber, mantar, kaşar",
+ "en": "Green pepper, tomato, pepper, mushroom, kaşar"
+ }
+ },
+ {
+ "id": "pz4",
+ "price": 290,
+ "name": {
+ "tr": "Enginar lı Pizza",
+ "en": "Artichoke Pizza"
+ },
+ "description": {
+ "tr": "Belirli peşik, fesleğen, mozzarella peyniri, domates sosu",
+ "en": "Artichoke, basil, mozzarella cheese, tomato sauce"
+ }
+ }
+ ]
+ },
+ {
+ "id": "wraps",
+ "emoji": "🌯",
+ "label": {
+ "tr": "Wraps",
+ "en": "Wraps"
+ },
+ "items": [
+ {
+ "id": "w1",
+ "price": 220,
+ "name": {
+ "tr": "Sebzeli Wrap",
+ "en": "Veggie Wrap"
+ },
+ "description": {
+ "tr": "Mantar, toflu peynirli, turşu, soğan, samon biber, yeşil biber, kaşar biber",
+ "en": "Mushroom, tofu cheese, pickles, onions, yellow pepper, green pepper, red pepper"
+ }
+ },
+ {
+ "id": "w2",
+ "price": 250,
+ "name": {
+ "tr": "Tavuklu Wrap",
+ "en": "Chicken Wrap"
+ },
+ "description": {
+ "tr": "Salatalık doğası, turşu soğan, kısma biber, marul, kon türfü turşu",
+ "en": "Cucumber, pickled onions, red pepper, lettuce, pickles"
+ }
+ }
+ ]
+ },
+ {
+ "id": "mantilar",
+ "emoji": "🥟",
+ "label": {
+ "tr": "Mantılar",
+ "en": "Manti & Ravioli"
+ },
+ "items": [
+ {
+ "id": "mt1",
+ "price": 240,
+ "name": {
+ "tr": "Ev Yapımı Mantı",
+ "en": "Homemade Manti"
+ },
+ "description": {
+ "tr": "Yoğurt ve sas eşliğince",
+ "en": "Served with yogurt and sauce"
+ }
+ },
+ {
+ "id": "mt2",
+ "price": 260,
+ "name": {
+ "tr": "Cevizli Mantı",
+ "en": "Walnut Manti"
+ },
+ "description": {
+ "tr": "Yoğurt ve sas eşliğince",
+ "en": "Served with yogurt and sauce"
+ }
+ },
+ {
+ "id": "mt3",
+ "price": 230,
+ "name": {
+ "tr": "Akdeniz Mantısı",
+ "en": "Mediterranean Manti"
+ },
+ "description": {
+ "tr": "Fesleğen, maydanoz, cziz, yoğurtlu sos",
+ "en": "Basil, parsley, walnuts, yogurt sauce"
+ }
+ }
+ ]
+ },
+ {
+ "id": "makarnalar",
+ "emoji": "🍝",
+ "label": {
+ "tr": "Makarnalar",
+ "en": "Pastas"
+ },
+ "items": [
+ {
+ "id": "mk1",
+ "price": 220,
+ "name": {
+ "tr": "Penne Arrabbiata",
+ "en": "Penne Arrabbiata"
+ }
+ },
+ {
+ "id": "mk2",
+ "price": 240,
+ "name": {
+ "tr": "Spaghetti al Pesto",
+ "en": "Spaghetti al Pesto"
+ }
+ },
+ {
+ "id": "mk3",
+ "price": 270,
+ "name": {
+ "tr": "Fettuccine Alfredo",
+ "en": "Fettuccine Alfredo"
+ }
+ },
+ {
+ "id": "mk4",
+ "price": 280,
+ "name": {
+ "tr": "Spagetti Bolonez",
+ "en": "Spaghetti Bolognese"
+ }
+ }
+ ]
+ },
+ {
+ "id": "ana-yemekler",
+ "emoji": "🍗",
+ "label": {
+ "tr": "Ana Yemekler",
+ "en": "Main Courses"
+ },
+ "items": [
+ {
+ "id": "ay1",
+ "price": 290,
+ "name": {
+ "tr": "Tavuk Izgara",
+ "en": "Grilled Chicken"
+ },
+ "description": {
+ "tr": "Tavuk göğsü, karamelize yeşil biber, soğan, yeşillik, domatesli cipsi",
+ "en": "Chicken breast, caramelized green pepper, onions, greens, potato chips"
+ }
+ },
+ {
+ "id": "ay2",
+ "price": 320,
+ "name": {
+ "tr": "Tavuk Pirzola",
+ "en": "Chicken Chops"
+ },
+ "description": {
+ "tr": "Kalemime yeşil biber, soğan, yeşil fık, patates cipsi",
+ "en": "Green pepper, onions, greens, potato chips"
+ }
+ },
+ {
+ "id": "ay3",
+ "price": 340,
+ "name": {
+ "tr": "Köfte",
+ "en": "Meatballs"
+ },
+ "description": {
+ "tr": "Maydanoz, yeşil biber, soğan, yeşillik, patlıcan cipsi",
+ "en": "Parsley, green pepper, onions, greens, eggplant chips"
+ }
+ },
+ {
+ "id": "ay4",
+ "price": 450,
+ "name": {
+ "tr": "Karışık Izgara",
+ "en": "Mixed Grill"
+ },
+ "description": {
+ "tr": "Köfte, tavuk göğsü, kısık pırzola. Kalemima yeşil biber, soğan, yeşillik, patlıcan cipsi",
+ "en": "Meatballs, chicken breast, chicken chops. Green pepper, onions, greens, eggplant chips"
+ }
+ },
+ {
+ "id": "ay5",
+ "price": 580,
+ "name": {
+ "tr": "Sac Kavurma – 2 Kişilik",
+ "en": "Sac Kavurma - For 2"
+ },
+ "description": {
+ "tr": "Mevsim salatası ve patates cipsi eşliğinde",
+ "en": "Served with seasonal salad and potato chips"
+ }
+ },
+ {
+ "id": "ay6",
+ "price": 400,
+ "name": {
+ "tr": "Günlük Balık Çeşitleri",
+ "en": "Daily Fish Catch"
+ }
+ }
+ ]
+ },
+ {
+ "id": "atistirmaliklar",
+ "emoji": "🍟",
+ "label": {
+ "tr": "Atıştırmalıklar",
+ "en": "Snacks"
+ },
+ "items": [
+ {
+ "id": "at1",
+ "price": 90,
+ "name": {
+ "tr": "Patates Cipsi",
+ "en": "Potato Chips"
+ }
+ },
+ {
+ "id": "at2",
+ "price": 180,
+ "name": {
+ "tr": "Çıtır Tavuk",
+ "en": "Crispy Chicken"
+ },
+ "description": {
+ "tr": "Çıtır tavuk ve patates cipsi",
+ "en": "Crispy chicken and potato chips"
+ }
+ },
+ {
+ "id": "at3",
+ "price": 280,
+ "name": {
+ "tr": "Peynir Tabağı",
+ "en": "Cheese Platter"
+ },
+ "description": {
+ "tr": "Ezine peyniri, tulum peyniri, ham, haşar, çeşit kaşar, kuru domates",
+ "en": "Ezine cheese, tulum cheese, kaşar cheese, dried tomatoes"
+ }
+ },
+ {
+ "id": "at4",
+ "price": 350,
+ "name": {
+ "tr": "Bira Tabağı",
+ "en": "Beer Platter"
+ },
+ "description": {
+ "tr": "Roka & patates cipsi, çıtır tavuk, çeşit kaşar",
+ "en": "Arugula & potato chips, crispy chicken, cheese varieties"
+ }
+ },
+ {
+ "id": "at5",
+ "price": 420,
+ "name": {
+ "tr": "Deniz Tabağı",
+ "en": "Seafood Platter"
+ },
+ "description": {
+ "tr": "Halk kalamar, çıtır karides, roka",
+ "en": "Calamari rings, crispy shrimp, arugula"
+ }
+ }
+ ]
+ },
+ {
+ "id": "sicak-icecekler",
+ "emoji": "☕",
+ "label": {
+ "tr": "Sıcak İçecekler & Kahveler",
+ "en": "Hot Drinks & Coffees"
+ },
+ "items": [
+ {
+ "id": "si1",
+ "price": 40,
+ "name": {
+ "tr": "Çay",
+ "en": "Tea"
+ }
+ },
+ {
+ "id": "si2",
+ "price": 80,
+ "name": {
+ "tr": "Türk Kahvesi",
+ "en": "Turkish Coffee"
+ }
+ },
+ {
+ "id": "si3",
+ "price": 120,
+ "name": {
+ "tr": "Duble Türk Kahvesi",
+ "en": "Double Turkish Coffee"
+ }
+ },
+ {
+ "id": "si4",
+ "price": 110,
+ "name": {
+ "tr": "Filtre Kahve",
+ "en": "Filter Coffee"
+ }
+ },
+ {
+ "id": "si5",
+ "price": 90,
+ "name": {
+ "tr": "Espresso",
+ "en": "Espresso"
+ }
+ },
+ {
+ "id": "si6",
+ "price": 110,
+ "name": {
+ "tr": "Americano",
+ "en": "Americano"
+ }
+ },
+ {
+ "id": "si7",
+ "price": 130,
+ "name": {
+ "tr": "Latte",
+ "en": "Latte"
+ }
+ },
+ {
+ "id": "si8",
+ "price": 130,
+ "name": {
+ "tr": "Cappuccino",
+ "en": "Cappuccino"
+ }
+ }
+ ]
+ },
+ {
+ "id": "soguk-icecekler",
+ "emoji": "🥤",
+ "label": {
+ "tr": "Soğuk İçecekler",
+ "en": "Cold Drinks"
+ },
+ "items": [
+ {
+ "id": "so1",
+ "price": 30,
+ "name": {
+ "tr": "Su",
+ "en": "Water"
+ }
+ },
+ {
+ "id": "so2",
+ "price": 50,
+ "name": {
+ "tr": "Soda",
+ "en": "Sparkling Water"
+ }
+ },
+ {
+ "id": "so3",
+ "price": 60,
+ "name": {
+ "tr": "Meyveli Soda",
+ "en": "Fruit Sparkling Water"
+ }
+ },
+ {
+ "id": "so4",
+ "price": 60,
+ "name": {
+ "tr": "Ayran",
+ "en": "Ayran"
+ }
+ },
+ {
+ "id": "so5",
+ "price": 120,
+ "name": {
+ "tr": "Limonata",
+ "en": "Lemonade"
+ }
+ },
+ {
+ "id": "so6",
+ "price": 80,
+ "name": {
+ "tr": "Churchill",
+ "en": "Churchill"
+ }
+ },
+ {
+ "id": "so7",
+ "price": 70,
+ "name": {
+ "tr": "Coca-Cola / Fanta / Sprite",
+ "en": "Coca-Cola / Fanta / Sprite"
+ }
+ },
+ {
+ "id": "so8",
+ "price": 70,
+ "name": {
+ "tr": "Ice Tea",
+ "en": "Ice Tea"
+ }
+ },
+ {
+ "id": "so9",
+ "price": 90,
+ "name": {
+ "tr": "Red Bull",
+ "en": "Red Bull"
+ }
+ },
+ {
+ "id": "so10",
+ "price": 60,
+ "name": {
+ "tr": "Vişne Suyu",
+ "en": "Cherry Juice"
+ }
+ },
+ {
+ "id": "so11",
+ "price": 110,
+ "name": {
+ "tr": "Portakal Suyu",
+ "en": "Orange Juice"
+ }
+ },
+ {
+ "id": "so12",
+ "price": 120,
+ "name": {
+ "tr": "Nar Suyu",
+ "en": "Pomegranate Juice"
+ }
+ }
+ ]
+ },
+ {
+ "id": "soguk-kahveler",
+ "emoji": "🧋",
+ "label": {
+ "tr": "Soğuk Kahveler",
+ "en": "Cold Coffees"
+ },
+ "items": [
+ {
+ "id": "sk1",
+ "price": 130,
+ "name": {
+ "tr": "Iced Americano",
+ "en": "Iced Americano"
+ }
+ },
+ {
+ "id": "sk2",
+ "price": 150,
+ "name": {
+ "tr": "Iced Latte",
+ "en": "Iced Latte"
+ }
+ },
+ {
+ "id": "sk3",
+ "price": 160,
+ "name": {
+ "tr": "Iced Mocha",
+ "en": "Iced Mocha"
+ }
+ }
+ ]
+ },
+ {
+ "id": "frozen",
+ "emoji": "🧊",
+ "label": {
+ "tr": "Frozen",
+ "en": "Frozen"
+ },
+ "items": [
+ {
+ "id": "fz1",
+ "price": 170,
+ "name": {
+ "tr": "Çilek",
+ "en": "Strawberry"
+ }
+ },
+ {
+ "id": "fz2",
+ "price": 170,
+ "name": {
+ "tr": "Mango",
+ "en": "Mango"
+ }
+ },
+ {
+ "id": "fz3",
+ "price": 180,
+ "name": {
+ "tr": "Orman Meyvesi",
+ "en": "Forest Fruits"
+ }
+ }
+ ]
+ },
+ {
+ "id": "milkshakes",
+ "emoji": "🥛",
+ "label": {
+ "tr": "Milkshakes",
+ "en": "Milkshakes"
+ },
+ "items": [
+ {
+ "id": "ms1",
+ "price": 160,
+ "name": {
+ "tr": "Muz",
+ "en": "Banana"
+ }
+ },
+ {
+ "id": "ms2",
+ "price": 160,
+ "name": {
+ "tr": "Vanilya",
+ "en": "Vanilla"
+ }
+ },
+ {
+ "id": "ms3",
+ "price": 160,
+ "name": {
+ "tr": "Çilek",
+ "en": "Strawberry"
+ }
+ },
+ {
+ "id": "ms4",
+ "price": 160,
+ "name": {
+ "tr": "Çikolata",
+ "en": "Chocolate"
+ }
+ },
+ {
+ "id": "ms5",
+ "price": 170,
+ "name": {
+ "tr": "Mango",
+ "en": "Mango"
+ }
+ },
+ {
+ "id": "ms6",
+ "price": 180,
+ "name": {
+ "tr": "Orman Meyvesi",
+ "en": "Forest Fruits"
+ }
+ },
+ {
+ "id": "ms7",
+ "price": 170,
+ "name": {
+ "tr": "Espresso",
+ "en": "Espresso"
+ }
+ }
+ ]
+ },
+ {
+ "id": "refreshers",
+ "emoji": "🌿",
+ "label": {
+ "tr": "Refreshers",
+ "en": "Refreshers"
+ },
+ "items": [
+ {
+ "id": "rf1",
+ "price": 120,
+ "name": {
+ "tr": "Limonata",
+ "en": "Lemonade"
+ }
+ },
+ {
+ "id": "rf2",
+ "price": 120,
+ "name": {
+ "tr": "Nar Suyu",
+ "en": "Pomegranate Juice"
+ }
+ },
+ {
+ "id": "rf3",
+ "price": 110,
+ "name": {
+ "tr": "Portakal Suyu",
+ "en": "Orange Juice"
+ }
+ },
+ {
+ "id": "rf4",
+ "price": 140,
+ "name": {
+ "tr": "Cool Lime",
+ "en": "Cool Lime"
+ }
+ },
+ {
+ "id": "rf5",
+ "price": 150,
+ "name": {
+ "tr": "Rooibos Peach",
+ "en": "Rooibos Peach"
+ }
+ }
+ ]
+ },
+ {
+ "id": "biralar",
+ "emoji": "🍺",
+ "label": {
+ "tr": "Biralar",
+ "en": "Beers"
+ },
+ "items": [
+ {
+ "id": "br1",
+ "price": 150,
+ "name": {
+ "tr": "Tuborg Gold",
+ "en": "Tuborg Gold"
+ },
+ "description": {
+ "tr": "50 cl",
+ "en": "50 cl"
+ }
+ },
+ {
+ "id": "br2",
+ "price": 160,
+ "name": {
+ "tr": "Tuborg Filtresiz",
+ "en": "Tuborg Unfiltered"
+ },
+ "description": {
+ "tr": "50 cl",
+ "en": "50 cl"
+ }
+ },
+ {
+ "id": "br3",
+ "price": 170,
+ "name": {
+ "tr": "Tuborg Amber",
+ "en": "Tuborg Amber"
+ },
+ "description": {
+ "tr": "50 cl",
+ "en": "50 cl"
+ }
+ },
+ {
+ "id": "br4",
+ "price": 160,
+ "name": {
+ "tr": "Carlsberg",
+ "en": "Carlsberg"
+ },
+ "description": {
+ "tr": "50 cl",
+ "en": "50 cl"
+ }
+ },
+ {
+ "id": "br5",
+ "price": 150,
+ "name": {
+ "tr": "Efes Malt",
+ "en": "Efes Malt"
+ },
+ "description": {
+ "tr": "50 cl",
+ "en": "50 cl"
+ }
+ },
+ {
+ "id": "br6",
+ "price": 160,
+ "name": {
+ "tr": "Efes Green",
+ "en": "Efes Green"
+ },
+ "description": {
+ "tr": "50 cl",
+ "en": "50 cl"
+ }
+ },
+ {
+ "id": "br7",
+ "price": 190,
+ "name": {
+ "tr": "Stella Artois",
+ "en": "Stella Artois"
+ },
+ "description": {
+ "tr": "44 cl",
+ "en": "44 cl"
+ }
+ },
+ {
+ "id": "br8",
+ "price": 150,
+ "name": {
+ "tr": "Beck's",
+ "en": "Beck's"
+ },
+ "description": {
+ "tr": "33 cl",
+ "en": "33 cl"
+ }
+ },
+ {
+ "id": "br9",
+ "price": 150,
+ "name": {
+ "tr": "Bud",
+ "en": "Bud"
+ },
+ "description": {
+ "tr": "33 cl",
+ "en": "33 cl"
+ }
+ }
+ ]
+ },
+ {
+ "id": "saraplar",
+ "emoji": "🍷",
+ "label": {
+ "tr": "Şaraplar",
+ "en": "Wines"
+ },
+ "items": [
+ {
+ "id": "sr1",
+ "price": 280,
+ "name": {
+ "tr": "Beyaz Kadeh",
+ "en": "White Glass"
+ }
+ },
+ {
+ "id": "sr2",
+ "price": 280,
+ "name": {
+ "tr": "Rosé Kadeh",
+ "en": "Rosé Glass"
+ }
+ },
+ {
+ "id": "sr3",
+ "price": 300,
+ "name": {
+ "tr": "Kırmızı Kadeh",
+ "en": "Red Glass"
+ }
+ },
+ {
+ "id": "sr4",
+ "price": 1100,
+ "name": {
+ "tr": "Sevilen Colombard Semillon – Beyaz Şişe",
+ "en": "Sevilen Colombard Semillon – White Bottle"
+ }
+ },
+ {
+ "id": "sr5",
+ "price": 1200,
+ "name": {
+ "tr": "Sevilen Letter R – Blush Şişe",
+ "en": "Sevilen Letter R – Blush Bottle"
+ }
+ },
+ {
+ "id": "sr6",
+ "price": 1400,
+ "name": {
+ "tr": "Sevilen Parsel II – Kırmızı Şişe",
+ "en": "Sevilen Parsel II – Red Bottle"
+ }
+ }
+ ]
+ },
+ {
+ "id": "alkolsuz-kokteyller",
+ "emoji": "🧃",
+ "label": {
+ "tr": "Alkolsüz Kokteyller",
+ "en": "Mocktails"
+ },
+ "items": [
+ {
+ "id": "ak1",
+ "price": 240,
+ "name": {
+ "tr": "Golden Jüpiter",
+ "en": "Golden Jupiter"
+ },
+ "description": {
+ "tr": "Red Bull Peach Edition, fesleğen, portakal suyu, limon",
+ "en": "Red Bull Peach Edition, basil, orange juice, lemon"
+ }
+ },
+ {
+ "id": "ak2",
+ "price": 220,
+ "name": {
+ "tr": "Venus Wave",
+ "en": "Venus Wave"
+ },
+ "description": {
+ "tr": "Red Bull White Edition, ananas suyu, lime",
+ "en": "Red Bull White Edition, pineapple juice, lime"
+ }
+ },
+ {
+ "id": "ak3",
+ "price": 210,
+ "name": {
+ "tr": "Kozmos Twist",
+ "en": "Kozmos Twist"
+ },
+ "description": {
+ "tr": "Red Bull White Edition, portakal, lime, soda",
+ "en": "Red Bull White Edition, orange, lime, sparkling water"
+ }
+ }
+ ]
+ },
+ {
+ "id": "house-kokteyller",
+ "emoji": "🍹",
+ "label": {
+ "tr": "House Kokteyller",
+ "en": "House Cocktails"
+ },
+ "items": [
+ {
+ "id": "hk1",
+ "price": 380,
+ "name": {
+ "tr": "Grass",
+ "en": "Grass"
+ },
+ "description": {
+ "tr": "Gin, yeşil erik, buzlu turşu, limon suyu, greyfurt, portakal",
+ "en": "Gin, green plum, ice pickle, lemon juice, grapefruit, orange"
+ }
+ },
+ {
+ "id": "hk2",
+ "price": 400,
+ "name": {
+ "tr": "Strawberry Hibiscus",
+ "en": "Strawberry Hibiscus"
+ },
+ "description": {
+ "tr": "Hibiscus çayı, greyfurt, Cointreau, Casemigos teklila",
+ "en": "Hibiscus tea, grapefruit, Cointreau, Casamigos tequila"
+ }
+ },
+ {
+ "id": "hk3",
+ "price": 350,
+ "name": {
+ "tr": "Satsuma",
+ "en": "Satsuma"
+ },
+ "description": {
+ "tr": "Satsuma, greyfurt, portakal, votka",
+ "en": "Satsuma, grapefruit, orange, vodka"
+ }
+ },
+ {
+ "id": "hk4",
+ "price": 360,
+ "name": {
+ "tr": "Chili Mango",
+ "en": "Chili Mango"
+ },
+ "description": {
+ "tr": "Mango, Antrout taberi, turunç, kamişeri",
+ "en": "Mango, chili pepper, citrus"
+ }
+ },
+ {
+ "id": "hk5",
+ "price": 340,
+ "name": {
+ "tr": "Black Mulberry",
+ "en": "Black Mulberry"
+ },
+ "description": {
+ "tr": "Karadut, lime, sweet & sour, votka",
+ "en": "Black mulberry, lime, sweet & sour, vodka"
+ }
+ },
+ {
+ "id": "hk6",
+ "price": 390,
+ "name": {
+ "tr": "Lunar Bloom",
+ "en": "Lunar Bloom"
+ },
+ "description": {
+ "tr": "Beyaz çay, şeftali, beyaz şarap",
+ "en": "White tea, peach, white wine"
+ }
+ },
+ {
+ "id": "hk7",
+ "price": 380,
+ "name": {
+ "tr": "White Peach",
+ "en": "White Peach"
+ },
+ "description": {
+ "tr": "Beyaz çay, şeftali, rom",
+ "en": "White tea, peach, rum"
+ }
+ },
+ {
+ "id": "hk8",
+ "price": 420,
+ "name": {
+ "tr": "Kozmos",
+ "en": "Kozmos"
+ },
+ "description": {
+ "tr": "Cinzano, sweet & sour, ananas suyu, limon suyu, Kozmos mavi",
+ "en": "Cinzano, sweet & sour, pineapple juice, lemon juice, Kozmos blue"
+ }
+ }
+ ]
+ },
+ {
+ "id": "raki",
+ "emoji": "🥃",
+ "label": {
+ "tr": "Rakı",
+ "en": "Raki"
+ },
+ "items": [
+ {
+ "id": "rk1",
+ "price": 250,
+ "name": {
+ "tr": "Lokal Rakı Tek",
+ "en": "Local Raki Single"
+ }
+ },
+ {
+ "id": "rk2",
+ "price": 380,
+ "name": {
+ "tr": "Lokal Rakı Duble",
+ "en": "Local Raki Double"
+ }
+ },
+ {
+ "id": "rk3",
+ "price": 1200,
+ "name": {
+ "tr": "Yeni Rakı Özel Seri",
+ "en": "Yeni Raki Special Series"
+ },
+ "description": {
+ "tr": "35 cl",
+ "en": "35 cl"
+ }
+ },
+ {
+ "id": "rk4",
+ "price": 1600,
+ "name": {
+ "tr": "Yeni Rakı Özel Seri",
+ "en": "Yeni Raki Special Series"
+ },
+ "description": {
+ "tr": "50 cl",
+ "en": "50 cl"
+ }
+ },
+ {
+ "id": "rk5",
+ "price": 2100,
+ "name": {
+ "tr": "Yeni Rakı Özel Seri",
+ "en": "Yeni Raki Special Series"
+ },
+ "description": {
+ "tr": "70 cl",
+ "en": "70 cl"
+ }
+ },
+ {
+ "id": "rk6",
+ "price": 1350,
+ "name": {
+ "tr": "Beylerbeyi Göbek",
+ "en": "Beylerbeyi Gobek"
+ },
+ "description": {
+ "tr": "35 cl",
+ "en": "35 cl"
+ }
+ },
+ {
+ "id": "rk7",
+ "price": 1800,
+ "name": {
+ "tr": "Beylerbeyi Göbek",
+ "en": "Beylerbeyi Gobek"
+ },
+ "description": {
+ "tr": "50 cl",
+ "en": "50 cl"
+ }
+ },
+ {
+ "id": "rk8",
+ "price": 2350,
+ "name": {
+ "tr": "Beylerbeyi Göbek",
+ "en": "Beylerbeyi Gobek"
+ },
+ "description": {
+ "tr": "70 cl",
+ "en": "70 cl"
+ }
+ }
+ ]
+ },
+ {
+ "id": "klasik-kokteyller",
+ "emoji": "🍸",
+ "label": {
+ "tr": "Klasik Kokteyller",
+ "en": "Classic Cocktails"
+ },
+ "items": [
+ {
+ "id": "kk1",
+ "price": 360,
+ "name": {
+ "tr": "Espresso Martini",
+ "en": "Espresso Martini"
+ }
+ },
+ {
+ "id": "kk2",
+ "price": 340,
+ "name": {
+ "tr": "Mojito",
+ "en": "Mojito"
+ }
+ },
+ {
+ "id": "kk3",
+ "price": 350,
+ "name": {
+ "tr": "Margarita",
+ "en": "Margarita"
+ }
+ },
+ {
+ "id": "kk4",
+ "price": 350,
+ "name": {
+ "tr": "Aperol Spritz",
+ "en": "Aperol Spritz"
+ }
+ },
+ {
+ "id": "kk5",
+ "price": 340,
+ "name": {
+ "tr": "Lynchburg Lemonade",
+ "en": "Lynchburg Lemonade"
+ }
+ },
+ {
+ "id": "kk6",
+ "price": 360,
+ "name": {
+ "tr": "Whiskey Sour",
+ "en": "Whiskey Sour"
+ }
+ },
+ {
+ "id": "kk7",
+ "price": 390,
+ "name": {
+ "tr": "Long Island Iced Tea",
+ "en": "Long Island Iced Tea"
+ }
+ },
+ {
+ "id": "kk8",
+ "price": 350,
+ "name": {
+ "tr": "Negroni",
+ "en": "Negroni"
+ }
+ }
+ ]
+ },
+ {
+ "id": "shots",
+ "emoji": "🥃",
+ "label": {
+ "tr": "Shots",
+ "en": "Shots"
+ },
+ "items": [
+ {
+ "id": "sh1",
+ "price": 180,
+ "name": {
+ "tr": "Olmeca Gold",
+ "en": "Olmeca Gold"
+ }
+ },
+ {
+ "id": "sh2",
+ "price": 180,
+ "name": {
+ "tr": "Olmeca Silver",
+ "en": "Olmeca Silver"
+ }
+ },
+ {
+ "id": "sh3",
+ "price": 200,
+ "name": {
+ "tr": "Olmeca Dark Chocolate",
+ "en": "Olmeca Dark Chocolate"
+ }
+ },
+ {
+ "id": "sh4",
+ "price": 180,
+ "name": {
+ "tr": "Aperol Spritz",
+ "en": "Aperol Spritz"
+ }
+ },
+ {
+ "id": "sh5",
+ "price": 180,
+ "name": {
+ "tr": "Lynchburg Lemonade",
+ "en": "Lynchburg Lemonade"
+ }
+ },
+ {
+ "id": "sh6",
+ "price": 180,
+ "name": {
+ "tr": "Whiskey Sour",
+ "en": "Whiskey Sour"
+ }
+ }
+ ]
+ },
+ {
+ "id": "spirits",
+ "emoji": "🫙",
+ "label": {
+ "tr": "Spirits",
+ "en": "Spirits"
+ },
+ "items": [
+ {
+ "id": "sp1",
+ "price": 300,
+ "name": {
+ "tr": "Absolut Vodka",
+ "en": "Absolut Vodka"
+ }
+ },
+ {
+ "id": "sp2",
+ "price": 320,
+ "name": {
+ "tr": "Absolut Vanilla",
+ "en": "Absolut Vanilla"
+ }
+ },
+ {
+ "id": "sp3",
+ "price": 340,
+ "name": {
+ "tr": "Beefeater Gin",
+ "en": "Beefeater Gin"
+ }
+ },
+ {
+ "id": "sp4",
+ "price": 340,
+ "name": {
+ "tr": "Beefeater Pink",
+ "en": "Beefeater Pink"
+ }
+ },
+ {
+ "id": "sp5",
+ "price": 360,
+ "name": {
+ "tr": "Beefeater Blood Orange",
+ "en": "Beefeater Blood Orange"
+ }
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/data/menu.ts b/data/menu.ts
index 935a14e..d046e0c 100644
--- a/data/menu.ts
+++ b/data/menu.ts
@@ -1,8 +1,11 @@
+import prisma from '@/lib/prisma'
+
export type LocalizedString = { tr: string; en: string }
export type MenuItem = {
id: string
emoji?: string
+ image?: string
name: LocalizedString
description?: LocalizedString
price?: number
@@ -18,319 +21,82 @@ export type Category = {
export const WHATSAPP_NUMBER = '905XXXXXXXXXX'
-export const categories: Category[] = [
- {
- id: 'kahvaltiliklar',
- emoji: '🍳',
- label: { tr: 'Kahvaltılıklar', en: 'Breakfasts' },
- items: [
- { id: 'k1', name: { tr: 'Sucuklu Yumurta', en: 'Eggs with Sucuk' }, description: { tr: 'Kaşar peynirli, çıtır domates', en: 'Kaşar cheese, crispy tomato' } },
- { id: 'k2', name: { tr: 'Menemen', en: 'Menemen' }, description: { tr: 'Domates, yeşil biber, 2 yumurta', en: 'Tomato, green pepper, 2 eggs' } },
- { id: 'k3', name: { tr: 'Kaşarlı Menemen', en: 'Menemen with Kaşar Cheese' }, description: { tr: 'Rondanın 5 kaşar peyniri, domates, yeşil biber, çeşitli 2 yumurta', en: '5 portions of kaşar cheese, tomato, green pepper, 2 eggs' } },
- { id: 'k4', name: { tr: 'Sade Omlet', en: 'Plain Omelette' }, description: { tr: 'Çeşitme 2 yumurta, mezelemeniz kaşar peyniri', en: '2 eggs, kaşar cheese' } },
- { id: 'k5', name: { tr: 'Mantarlı Omlet', en: 'Mushroom Omelette' }, description: { tr: 'Mantarlarla zenginleştirilmiş kaşar peyniri, çıtır biber, çimento, yeşil biber, satır, mantarı', en: 'Mushrooms, kaşar cheese, crispy peppers' } },
- { id: 'k6', name: { tr: 'Sebzeli Omlet', en: 'Vegetable Omelette' }, description: { tr: 'Mantarlar, zenginleştirilmiş kaşar peyniri, kapıya biber, çimento, yeşil biber ve saman mantarı', en: 'Mushrooms, kaşar cheese, capia pepper, green pepper' } },
- { id: 'k7', name: { tr: 'Vegan Kahvaltı', en: 'Vegan Breakfast' }, description: { tr: 'Avokado, çeşit domatesi, zeytin, tost, roka, havuç', en: 'Avocado, cherry tomato, olives, toast, arugula, carrot' } },
- { id: 'k8', name: { tr: 'Serpme Kahvaltı – 2 Kişilik', en: 'Mixed Breakfast - For 2' }, description: { tr: 'Kaşar peyniri, bal & kaymak, siyah zeytin, yeşil zeytin, sürüm, hinse türreyi, çeşit domatesi, erikçimler, tatlıpırçık, havuç, maydanoz ve sonsuz çay', en: 'Kaşar cheese, honey & clotted cream, black & green olives, cherry tomato, jams, carrot, parsley and unlimited tea' } },
- ],
- },
- {
- id: 'burgerler',
- emoji: '🍔',
- label: { tr: 'Burgerler', en: 'Burgers' },
- items: [
- { id: 'b1', name: { tr: 'Kids Smash Burger', en: 'Kids Smash Burger' }, description: { tr: 'Kids Smash burger, cheddar peyniri, çıtır patates, kızartılmış turşu, domates, marul, burger sosu, patates cipsi', en: 'Kids Smash burger, cheddar cheese, crispy fries, fried pickles, tomato, lettuce, burger sauce, potato chips' } },
- { id: 'b2', name: { tr: 'Dana Smash Burger', en: 'Beef Smash Burger' }, description: { tr: 'Dana Smash burger, cheddar peyniri, karamelize soğan, turşu, domates, marul, burger sosu, patates cipsi', en: 'Beef Smash burger, cheddar cheese, caramelized onions, pickles, tomato, lettuce, burger sauce, potato chips' } },
- { id: 'b3', name: { tr: 'Dana Smash Big Burger', en: 'Beef Smash Big Burger' }, description: { tr: 'Dana Smash burger smash kafası, cheddar peyniri, karamelize soğan, çıtır patates, kızartılmış turşu, domates, marul, burger sosu, patates cipsi', en: 'Beef Smash big burger, cheddar cheese, caramelized onions, crispy fries, fried pickles, tomato, lettuce, burger sauce, potato chips' } },
- { id: 'b4', name: { tr: 'Çıtır Tavuk Burger', en: 'Crispy Chicken Burger' }, description: { tr: 'Çıtır Tavuk Burger harika cheddar peyniri, burger sos, patates cipsi', en: 'Crispy Chicken Burger, cheddar cheese, burger sauce, potato chips' } },
- ],
- },
- {
- id: 'tatlilar',
- emoji: '🍮',
- label: { tr: 'Tatlılar', en: 'Desserts' },
- items: [
- { id: 't1', name: { tr: 'Meşhur Akyaka Tatlısı', en: 'Famous Akyaka Dessert' }, description: { tr: 'Deniz tuzlu, bürüme, bitter çikolata, Gökova sütlemi', en: 'Sea salt, bitter chocolate, Gökova milk' } },
- { id: 't2', name: { tr: 'Fırın Helva', en: 'Baked Halva' } },
- ],
- },
- {
- id: 'tostlar',
- emoji: '🥪',
- label: { tr: 'Tostlar', en: 'Toasts' },
- items: [
- { id: 'ts1', name: { tr: 'Peynirli Tost', en: 'Cheese Toast' }, description: { tr: 'Kaşar peyniri, çeşit domatesi, zeytin, patates cipsi', en: 'Kaşar cheese, cherry tomato, olives, potato chips' } },
- { id: 'ts2', name: { tr: 'Sucuklu Tost', en: 'Sucuk Toast' }, description: { tr: 'Kaşar sucuklu, çeşit domatesi, zeytin, patates cipsi', en: 'Sucuk, kaşar cheese, cherry tomato, olives, potato chips' } },
- { id: 'ts3', name: { tr: 'Akdeniz Tost', en: 'Mediterranean Toast' }, description: { tr: 'Kaşar pul, Ezine peyniri, tulum peyniri, kaşar domates, çeşit domatesi, zeytin, patates cipsi', en: 'Ezine cheese, tulum cheese, kaşar cheese, cherry tomato, olives, potato chips' } },
- { id: 'ts4', name: { tr: 'Karışık Tost', en: 'Mixed Toast' }, description: { tr: 'Kaşar sucuklu, kaşar peyniri, peri domates, zeytin, salatalık, dilimlen', en: 'Sucuk, kaşar cheese, cherry tomato, olives, cucumber slices' } },
- { id: 'ts5', name: { tr: 'Ayvalık Tostu', en: 'Ayvalik Toast' }, description: { tr: 'Ekşi, karamize turşu, kırmızı bilet kaşar peyniri, çeşit domatesi, salatalık, dilimlen', en: 'Sourdough bread, caramelized pickles, red pepper kaşar cheese, cherry tomato, cucumber slices' } },
- ],
- },
- {
- id: 'salatalar',
- emoji: '🥗',
- label: { tr: 'Salatalar', en: 'Salads' },
- items: [
- { id: 'sl1', name: { tr: 'Çoban Salata', en: "Shepherd's Salad" } },
- { id: 'sl2', name: { tr: 'Roka Salatası', en: 'Arugula Salad' }, description: { tr: 'Domates, zenginleştirilmiş üzüme bol peyniri', en: 'Tomato, grapes, cheese' } },
- { id: 'sl3', name: { tr: 'Yaz Salatası', en: 'Summer Salad' }, description: { tr: 'Kaşar ve her, taze domates, kırmızı soğan, beyaz peynir, fasulye, çeşitli yeşil besinleri, dalya üzümleri, çeşit domatesi', en: 'Fresh tomato, red onion, white cheese, beans, mixed greens, grapes, cherry tomato' } },
- { id: 'sl4', name: { tr: 'Avokado Salatası', en: 'Avocado Salad' }, description: { tr: 'Avokado, çeşit domatesi, salatalık, yeşil zeytin', en: 'Avocado, cherry tomato, cucumber, green olives' } },
- { id: 'sl5', name: { tr: 'Ton Balıklı Kinoalı Salata', en: 'Tuna & Quinoa Salad' }, description: { tr: 'Siyah kinoa, ton balığı, maydanoz, çeşit domatesi, salatalık, dilimlen', en: 'Black quinoa, tuna fish, parsley, cherry tomato, cucumber slices' } },
- ],
- },
- {
- id: 'pizzalar',
- emoji: '🍕',
- label: { tr: 'Pizzalar', en: 'Pizzas' },
- items: [
- { id: 'pz1', name: { tr: 'Margherita Pizza', en: 'Margherita Pizza' }, description: { tr: 'Pizza sosu, mozzarella peyniri, fesleğen, zeytin makul', en: 'Pizza sauce, mozzarella cheese, basil, olives' } },
- { id: 'pz2', name: { tr: 'Akdeniz Pizza', en: 'Mediterranean Pizza' }, description: { tr: 'Kaşar peyniri, yeşil biber, mantar, fesleğen, mozzarella peyniri, piliç sosu', en: 'Kaşar cheese, green pepper, mushroom, basil, mozzarella cheese' } },
- { id: 'pz3', name: { tr: 'Sucuk Pizza', en: 'Sucuk Pizza' }, description: { tr: 'Yeşil biber, domates biber, mantar, kaşar', en: 'Green pepper, tomato, pepper, mushroom, kaşar' } },
- { id: 'pz4', name: { tr: 'Enginar lı Pizza', en: 'Artichoke Pizza' }, description: { tr: 'Belirli peşik, fesleğen, mozzarella peyniri, domates sosu', en: 'Artichoke, basil, mozzarella cheese, tomato sauce' } },
- ],
- },
- {
- id: 'wraps',
- emoji: '🌯',
- label: { tr: 'Wraps', en: 'Wraps' },
- items: [
- { id: 'w1', name: { tr: 'Sebzeli Wrap', en: 'Veggie Wrap' }, description: { tr: 'Mantar, toflu peynirli, turşu, soğan, samon biber, yeşil biber, kaşar biber', en: 'Mushroom, tofu cheese, pickles, onions, yellow pepper, green pepper, red pepper' } },
- { id: 'w2', name: { tr: 'Tavuklu Wrap', en: 'Chicken Wrap' }, description: { tr: 'Salatalık doğası, turşu soğan, kısma biber, marul, kon türfü turşu', en: 'Cucumber, pickled onions, red pepper, lettuce, pickles' } },
- ],
- },
- {
- id: 'mantilar',
- emoji: '🥟',
- label: { tr: 'Mantılar', en: 'Manti & Ravioli' },
- items: [
- { id: 'mt1', name: { tr: 'Ev Yapımı Mantı', en: 'Homemade Manti' }, description: { tr: 'Yoğurt ve sas eşliğince', en: 'Served with yogurt and sauce' } },
- { id: 'mt2', name: { tr: 'Cevizli Mantı', en: 'Walnut Manti' }, description: { tr: 'Yoğurt ve sas eşliğince', en: 'Served with yogurt and sauce' } },
- { id: 'mt3', name: { tr: 'Akdeniz Mantısı', en: 'Mediterranean Manti' }, description: { tr: 'Fesleğen, maydanoz, cziz, yoğurtlu sos', en: 'Basil, parsley, walnuts, yogurt sauce' } },
- ],
- },
- {
- id: 'makarnalar',
- emoji: '🍝',
- label: { tr: 'Makarnalar', en: 'Pastas' },
- items: [
- { id: 'mk1', name: { tr: 'Penne Arrabbiata', en: 'Penne Arrabbiata' } },
- { id: 'mk2', name: { tr: 'Spaghetti al Pesto', en: 'Spaghetti al Pesto' } },
- { id: 'mk3', name: { tr: 'Fettuccine Alfredo', en: 'Fettuccine Alfredo' } },
- { id: 'mk4', name: { tr: 'Spagetti Bolonez', en: 'Spaghetti Bolognese' } },
- ],
- },
- {
- id: 'ana-yemekler',
- emoji: '🍗',
- label: { tr: 'Ana Yemekler', en: 'Main Courses' },
- items: [
- { id: 'ay1', name: { tr: 'Tavuk Izgara', en: 'Grilled Chicken' }, description: { tr: 'Tavuk göğsü, karamelize yeşil biber, soğan, yeşillik, domatesli cipsi', en: 'Chicken breast, caramelized green pepper, onions, greens, potato chips' } },
- { id: 'ay2', name: { tr: 'Tavuk Pirzola', en: 'Chicken Chops' }, description: { tr: 'Kalemime yeşil biber, soğan, yeşil fık, patates cipsi', en: 'Green pepper, onions, greens, potato chips' } },
- { id: 'ay3', name: { tr: 'Köfte', en: 'Meatballs' }, description: { tr: 'Maydanoz, yeşil biber, soğan, yeşillik, patlıcan cipsi', en: 'Parsley, green pepper, onions, greens, eggplant chips' } },
- { id: 'ay4', name: { tr: 'Karışık Izgara', en: 'Mixed Grill' }, description: { tr: 'Köfte, tavuk göğsü, kısık pırzola. Kalemima yeşil biber, soğan, yeşillik, patlıcan cipsi', en: 'Meatballs, chicken breast, chicken chops. Green pepper, onions, greens, eggplant chips' } },
- { id: 'ay5', name: { tr: 'Sac Kavurma – 2 Kişilik', en: 'Sac Kavurma - For 2' }, description: { tr: 'Mevsim salatası ve patates cipsi eşliğinde', en: 'Served with seasonal salad and potato chips' } },
- { id: 'ay6', name: { tr: 'Günlük Balık Çeşitleri', en: 'Daily Fish Catch' } },
- ],
- },
- {
- id: 'atistirmaliklar',
- emoji: '🍟',
- label: { tr: 'Atıştırmalıklar', en: 'Snacks' },
- items: [
- { id: 'at1', name: { tr: 'Patates Cipsi', en: 'Potato Chips' } },
- { id: 'at2', name: { tr: 'Çıtır Tavuk', en: 'Crispy Chicken' }, description: { tr: 'Çıtır tavuk ve patates cipsi', en: 'Crispy chicken and potato chips' } },
- { id: 'at3', name: { tr: 'Peynir Tabağı', en: 'Cheese Platter' }, description: { tr: 'Ezine peyniri, tulum peyniri, ham, haşar, çeşit kaşar, kuru domates', en: 'Ezine cheese, tulum cheese, kaşar cheese, dried tomatoes' } },
- { id: 'at4', name: { tr: 'Bira Tabağı', en: 'Beer Platter' }, description: { tr: 'Roka & patates cipsi, çıtır tavuk, çeşit kaşar', en: 'Arugula & potato chips, crispy chicken, cheese varieties' } },
- { id: 'at5', name: { tr: 'Deniz Tabağı', en: 'Seafood Platter' }, description: { tr: 'Halk kalamar, çıtır karides, roka', en: 'Calamari rings, crispy shrimp, arugula' } },
- ],
- },
- {
- id: 'sicak-icecekler',
- emoji: '☕',
- label: { tr: 'Sıcak İçecekler & Kahveler', en: 'Hot Drinks & Coffees' },
- items: [
- { id: 'si1', name: { tr: 'Çay', en: 'Tea' } },
- { id: 'si2', name: { tr: 'Türk Kahvesi', en: 'Turkish Coffee' } },
- { id: 'si3', name: { tr: 'Duble Türk Kahvesi', en: 'Double Turkish Coffee' } },
- { id: 'si4', name: { tr: 'Filtre Kahve', en: 'Filter Coffee' } },
- { id: 'si5', name: { tr: 'Espresso', en: 'Espresso' } },
- { id: 'si6', name: { tr: 'Americano', en: 'Americano' } },
- { id: 'si7', name: { tr: 'Latte', en: 'Latte' } },
- { id: 'si8', name: { tr: 'Cappuccino', en: 'Cappuccino' } },
- ],
- },
- {
- id: 'soguk-icecekler',
- emoji: '🥤',
- label: { tr: 'Soğuk İçecekler', en: 'Cold Drinks' },
- items: [
- { id: 'so1', name: { tr: 'Su', en: 'Water' } },
- { id: 'so2', name: { tr: 'Soda', en: 'Sparkling Water' } },
- { id: 'so3', name: { tr: 'Meyveli Soda', en: 'Fruit Sparkling Water' } },
- { id: 'so4', name: { tr: 'Ayran', en: 'Ayran' } },
- { id: 'so5', name: { tr: 'Limonata', en: 'Lemonade' } },
- { id: 'so6', name: { tr: 'Churchill', en: 'Churchill' } },
- { id: 'so7', name: { tr: 'Coca-Cola / Fanta / Sprite', en: 'Coca-Cola / Fanta / Sprite' } },
- { id: 'so8', name: { tr: 'Ice Tea', en: 'Ice Tea' } },
- { id: 'so9', name: { tr: 'Red Bull', en: 'Red Bull' } },
- { id: 'so10', name: { tr: 'Vişne Suyu', en: 'Cherry Juice' } },
- { id: 'so11', name: { tr: 'Portakal Suyu', en: 'Orange Juice' } },
- { id: 'so12', name: { tr: 'Nar Suyu', en: 'Pomegranate Juice' } },
- ],
- },
- {
- id: 'soguk-kahveler',
- emoji: '🧋',
- label: { tr: 'Soğuk Kahveler', en: 'Cold Coffees' },
- items: [
- { id: 'sk1', name: { tr: 'Iced Americano', en: 'Iced Americano' } },
- { id: 'sk2', name: { tr: 'Iced Latte', en: 'Iced Latte' } },
- { id: 'sk3', name: { tr: 'Iced Mocha', en: 'Iced Mocha' } },
- ],
- },
- {
- id: 'frozen',
- emoji: '🧊',
- label: { tr: 'Frozen', en: 'Frozen' },
- items: [
- { id: 'fz1', name: { tr: 'Çilek', en: 'Strawberry' } },
- { id: 'fz2', name: { tr: 'Mango', en: 'Mango' } },
- { id: 'fz3', name: { tr: 'Orman Meyvesi', en: 'Forest Fruits' } },
- ],
- },
- {
- id: 'milkshakes',
- emoji: '🥛',
- label: { tr: 'Milkshakes', en: 'Milkshakes' },
- items: [
- { id: 'ms1', name: { tr: 'Muz', en: 'Banana' } },
- { id: 'ms2', name: { tr: 'Vanilya', en: 'Vanilla' } },
- { id: 'ms3', name: { tr: 'Çilek', en: 'Strawberry' } },
- { id: 'ms4', name: { tr: 'Çikolata', en: 'Chocolate' } },
- { id: 'ms5', name: { tr: 'Mango', en: 'Mango' } },
- { id: 'ms6', name: { tr: 'Orman Meyvesi', en: 'Forest Fruits' } },
- { id: 'ms7', name: { tr: 'Espresso', en: 'Espresso' } },
- ],
- },
- {
- id: 'refreshers',
- emoji: '🌿',
- label: { tr: 'Refreshers', en: 'Refreshers' },
- items: [
- { id: 'rf1', name: { tr: 'Limonata', en: 'Lemonade' } },
- { id: 'rf2', name: { tr: 'Nar Suyu', en: 'Pomegranate Juice' } },
- { id: 'rf3', name: { tr: 'Portakal Suyu', en: 'Orange Juice' } },
- { id: 'rf4', name: { tr: 'Cool Lime', en: 'Cool Lime' } },
- { id: 'rf5', name: { tr: 'Rooibos Peach', en: 'Rooibos Peach' } },
- ],
- },
- {
- id: 'biralar',
- emoji: '🍺',
- label: { tr: 'Biralar', en: 'Beers' },
- items: [
- { id: 'br1', name: { tr: 'Tuborg Gold', en: 'Tuborg Gold' }, description: { tr: '50 cl', en: '50 cl' } },
- { id: 'br2', name: { tr: 'Tuborg Filtresiz', en: 'Tuborg Unfiltered' }, description: { tr: '50 cl', en: '50 cl' } },
- { id: 'br3', name: { tr: 'Tuborg Amber', en: 'Tuborg Amber' }, description: { tr: '50 cl', en: '50 cl' } },
- { id: 'br4', name: { tr: 'Carlsberg', en: 'Carlsberg' }, description: { tr: '50 cl', en: '50 cl' } },
- { id: 'br5', name: { tr: 'Efes Malt', en: 'Efes Malt' }, description: { tr: '50 cl', en: '50 cl' } },
- { id: 'br6', name: { tr: 'Efes Green', en: 'Efes Green' }, description: { tr: '50 cl', en: '50 cl' } },
- { id: 'br7', name: { tr: 'Stella Artois', en: 'Stella Artois' }, description: { tr: '44 cl', en: '44 cl' } },
- { id: 'br8', name: { tr: "Beck's", en: "Beck's" }, description: { tr: '33 cl', en: '33 cl' } },
- { id: 'br9', name: { tr: 'Bud', en: 'Bud' }, description: { tr: '33 cl', en: '33 cl' } },
- ],
- },
- {
- id: 'saraplar',
- emoji: '🍷',
- label: { tr: 'Şaraplar', en: 'Wines' },
- items: [
- { id: 'sr1', name: { tr: 'Beyaz Kadeh', en: 'White Glass' } },
- { id: 'sr2', name: { tr: 'Rosé Kadeh', en: 'Rosé Glass' } },
- { id: 'sr3', name: { tr: 'Kırmızı Kadeh', en: 'Red Glass' } },
- { id: 'sr4', name: { tr: 'Sevilen Colombard Semillon – Beyaz Şişe', en: 'Sevilen Colombard Semillon – White Bottle' } },
- { id: 'sr5', name: { tr: 'Sevilen Letter R – Blush Şişe', en: 'Sevilen Letter R – Blush Bottle' } },
- { id: 'sr6', name: { tr: 'Sevilen Parsel II – Kırmızı Şişe', en: 'Sevilen Parsel II – Red Bottle' } },
- ],
- },
- {
- id: 'alkolsuz-kokteyller',
- emoji: '🧃',
- label: { tr: 'Alkolsüz Kokteyller', en: 'Mocktails' },
- items: [
- { id: 'ak1', name: { tr: 'Golden Jüpiter', en: 'Golden Jupiter' }, description: { tr: 'Red Bull Peach Edition, fesleğen, portakal suyu, limon', en: 'Red Bull Peach Edition, basil, orange juice, lemon' } },
- { id: 'ak2', name: { tr: 'Venus Wave', en: 'Venus Wave' }, description: { tr: 'Red Bull White Edition, ananas suyu, lime', en: 'Red Bull White Edition, pineapple juice, lime' } },
- { id: 'ak3', name: { tr: 'Kozmos Twist', en: 'Kozmos Twist' }, description: { tr: 'Red Bull White Edition, portakal, lime, soda', en: 'Red Bull White Edition, orange, lime, sparkling water' } },
- ],
- },
- {
- id: 'house-kokteyller',
- emoji: '🍹',
- label: { tr: 'House Kokteyller', en: 'House Cocktails' },
- items: [
- { id: 'hk1', name: { tr: 'Grass', en: 'Grass' }, description: { tr: 'Gin, yeşil erik, buzlu turşu, limon suyu, greyfurt, portakal', en: 'Gin, green plum, ice pickle, lemon juice, grapefruit, orange' } },
- { id: 'hk2', name: { tr: 'Strawberry Hibiscus', en: 'Strawberry Hibiscus' }, description: { tr: 'Hibiscus çayı, greyfurt, Cointreau, Casemigos teklila', en: 'Hibiscus tea, grapefruit, Cointreau, Casamigos tequila' } },
- { id: 'hk3', name: { tr: 'Satsuma', en: 'Satsuma' }, description: { tr: 'Satsuma, greyfurt, portakal, votka', en: 'Satsuma, grapefruit, orange, vodka' } },
- { id: 'hk4', name: { tr: 'Chili Mango', en: 'Chili Mango' }, description: { tr: 'Mango, Antrout taberi, turunç, kamişeri', en: 'Mango, chili pepper, citrus' } },
- { id: 'hk5', name: { tr: 'Black Mulberry', en: 'Black Mulberry' }, description: { tr: 'Karadut, lime, sweet & sour, votka', en: 'Black mulberry, lime, sweet & sour, vodka' } },
- { id: 'hk6', name: { tr: 'Lunar Bloom', en: 'Lunar Bloom' }, description: { tr: 'Beyaz çay, şeftali, beyaz şarap', en: 'White tea, peach, white wine' } },
- { id: 'hk7', name: { tr: 'White Peach', en: 'White Peach' }, description: { tr: 'Beyaz çay, şeftali, rom', en: 'White tea, peach, rum' } },
- { id: 'hk8', name: { tr: 'Kozmos', en: 'Kozmos' }, description: { tr: 'Cinzano, sweet & sour, ananas suyu, limon suyu, Kozmos mavi', en: 'Cinzano, sweet & sour, pineapple juice, lemon juice, Kozmos blue' } },
- ],
- },
- {
- id: 'raki',
- emoji: '🥃',
- label: { tr: 'Rakı', en: 'Raki' },
- items: [
- { id: 'rk1', name: { tr: 'Lokal Rakı Tek', en: 'Local Raki Single' } },
- { id: 'rk2', name: { tr: 'Lokal Rakı Duble', en: 'Local Raki Double' } },
- { id: 'rk3', name: { tr: 'Yeni Rakı Özel Seri', en: 'Yeni Raki Special Series' }, description: { tr: '35 cl', en: '35 cl' } },
- { id: 'rk4', name: { tr: 'Yeni Rakı Özel Seri', en: 'Yeni Raki Special Series' }, description: { tr: '50 cl', en: '50 cl' } },
- { id: 'rk5', name: { tr: 'Yeni Rakı Özel Seri', en: 'Yeni Raki Special Series' }, description: { tr: '70 cl', en: '70 cl' } },
- { id: 'rk6', name: { tr: 'Beylerbeyi Göbek', en: 'Beylerbeyi Gobek' }, description: { tr: '35 cl', en: '35 cl' } },
- { id: 'rk7', name: { tr: 'Beylerbeyi Göbek', en: 'Beylerbeyi Gobek' }, description: { tr: '50 cl', en: '50 cl' } },
- { id: 'rk8', name: { tr: 'Beylerbeyi Göbek', en: 'Beylerbeyi Gobek' }, description: { tr: '70 cl', en: '70 cl' } },
- ],
- },
- {
- id: 'klasik-kokteyller',
- emoji: '🍸',
- label: { tr: 'Klasik Kokteyller', en: 'Classic Cocktails' },
- items: [
- { id: 'kk1', name: { tr: 'Espresso Martini', en: 'Espresso Martini' } },
- { id: 'kk2', name: { tr: 'Mojito', en: 'Mojito' } },
- { id: 'kk3', name: { tr: 'Margarita', en: 'Margarita' } },
- { id: 'kk4', name: { tr: 'Aperol Spritz', en: 'Aperol Spritz' } },
- { id: 'kk5', name: { tr: 'Lynchburg Lemonade', en: 'Lynchburg Lemonade' } },
- { id: 'kk6', name: { tr: 'Whiskey Sour', en: 'Whiskey Sour' } },
- { id: 'kk7', name: { tr: 'Long Island Iced Tea', en: 'Long Island Iced Tea' } },
- { id: 'kk8', name: { tr: 'Negroni', en: 'Negroni' } },
- ],
- },
- {
- id: 'shots',
- emoji: '🥃',
- label: { tr: 'Shots', en: 'Shots' },
- items: [
- { id: 'sh1', name: { tr: 'Olmeca Gold', en: 'Olmeca Gold' } },
- { id: 'sh2', name: { tr: 'Olmeca Silver', en: 'Olmeca Silver' } },
- { id: 'sh3', name: { tr: 'Olmeca Dark Chocolate', en: 'Olmeca Dark Chocolate' } },
- { id: 'sh4', name: { tr: 'Aperol Spritz', en: 'Aperol Spritz' } },
- { id: 'sh5', name: { tr: 'Lynchburg Lemonade', en: 'Lynchburg Lemonade' } },
- { id: 'sh6', name: { tr: 'Whiskey Sour', en: 'Whiskey Sour' } },
- ],
- },
- {
- id: 'spirits',
- emoji: '🫙',
- label: { tr: 'Spirits', en: 'Spirits' },
- items: [
- { id: 'sp1', name: { tr: 'Absolut Vodka', en: 'Absolut Vodka' } },
- { id: 'sp2', name: { tr: 'Absolut Vanilla', en: 'Absolut Vanilla' } },
- { id: 'sp3', name: { tr: 'Beefeater Gin', en: 'Beefeater Gin' } },
- { id: 'sp4', name: { tr: 'Beefeater Pink', en: 'Beefeater Pink' } },
- { id: 'sp5', name: { tr: 'Beefeater Blood Orange', en: 'Beefeater Blood Orange' } },
- ],
- },
-]
+export async function getCategories(): Promise {
+ try {
+ const categories = await prisma.category.findMany({
+ orderBy: { order: 'asc' },
+ include: {
+ items: {
+ orderBy: { order: 'asc' },
+ },
+ },
+ })
+
+ return categories.map((c) => ({
+ id: c.id,
+ emoji: c.emoji,
+ label: { tr: c.labelTr, en: c.labelEn },
+ items: c.items.map((i) => ({
+ id: i.id,
+ emoji: i.emoji || undefined,
+ image: i.image || undefined,
+ name: { tr: i.nameTr, en: i.nameEn },
+ description:
+ i.descriptionTr && i.descriptionEn
+ ? { tr: i.descriptionTr, en: i.descriptionEn }
+ : undefined,
+ price: i.price !== null ? i.price : undefined,
+ highlight: i.highlight,
+ })),
+ }))
+ } catch (error) {
+ console.error('Error reading from database:', error)
+ return []
+ }
+}
+
+export async function saveCategories(categories: Category[]): Promise {
+ try {
+ await prisma.$transaction(async (tx) => {
+ // 1. Delete all existing records (mimics overwriting the JSON file)
+ await tx.menuItem.deleteMany()
+ await tx.category.deleteMany()
+
+ // 2. Insert new records
+ for (let cIndex = 0; cIndex < categories.length; cIndex++) {
+ const c = categories[cIndex]
+ await tx.category.create({
+ data: {
+ id: c.id,
+ emoji: c.emoji,
+ labelTr: c.label.tr,
+ labelEn: c.label.en || c.label.tr,
+ order: cIndex,
+ },
+ })
+
+ for (let iIndex = 0; iIndex < c.items.length; iIndex++) {
+ const i = c.items[iIndex]
+ await tx.menuItem.create({
+ data: {
+ id: i.id,
+ categoryId: c.id,
+ emoji: i.emoji || null,
+ image: i.image || null,
+ nameTr: i.name.tr,
+ nameEn: i.name.en || i.name.tr,
+ descriptionTr: i.description?.tr || null,
+ descriptionEn: i.description?.en || null,
+ price: i.price ?? null,
+ highlight: i.highlight ?? false,
+ order: iIndex,
+ },
+ })
+ }
+ }
+ })
+ } catch (error) {
+ console.error('Error saving to database:', error)
+ throw new Error('Failed to save menu data to database')
+ }
+}
diff --git a/lib/prisma.ts b/lib/prisma.ts
new file mode 100644
index 0000000..f4ba39b
--- /dev/null
+++ b/lib/prisma.ts
@@ -0,0 +1,15 @@
+import { PrismaClient } from '@prisma/client'
+
+const prismaClientSingleton = () => {
+ return new PrismaClient()
+}
+
+declare global {
+ var prismaGlobal: undefined | ReturnType
+}
+
+const prisma = globalThis.prismaGlobal ?? prismaClientSingleton()
+
+export default prisma
+
+if (process.env.NODE_ENV !== 'production') globalThis.prismaGlobal = prisma
diff --git a/package-lock.json b/package-lock.json
index 43c5b50..00a0c8a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,13 +13,17 @@
"react-dom": "19.2.4"
},
"devDependencies": {
+ "@prisma/client": "^6.19.3",
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
+ "dotenv": "^17.4.2",
"eslint": "^9",
"eslint-config-next": "16.2.7",
+ "prisma": "^6.19.3",
"tailwindcss": "^4",
+ "tsx": "^4.22.4",
"typescript": "^5"
}
},
@@ -309,6 +313,448 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz",
+ "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz",
+ "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz",
+ "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz",
+ "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz",
+ "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz",
+ "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz",
+ "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz",
+ "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz",
+ "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz",
+ "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz",
+ "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz",
+ "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz",
+ "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz",
+ "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz",
+ "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz",
+ "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz",
+ "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz",
+ "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz",
+ "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz",
+ "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz",
+ "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz",
+ "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz",
+ "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz",
+ "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz",
+ "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz",
+ "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
@@ -1306,6 +1752,92 @@
"node": ">=12.4.0"
}
},
+ "node_modules/@prisma/client": {
+ "version": "6.19.3",
+ "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.19.3.tgz",
+ "integrity": "sha512-mKq3jQFhjvko5LTJFHGilsuQs+W+T3Gm451NzuTDGQxwCzwXHYnIu2zGkRoW+Exq3Rob7yp2MfzSrdIiZVhrBg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "peerDependencies": {
+ "prisma": "*",
+ "typescript": ">=5.1.0"
+ },
+ "peerDependenciesMeta": {
+ "prisma": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@prisma/config": {
+ "version": "6.19.3",
+ "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.19.3.tgz",
+ "integrity": "sha512-CBPT44BjlQxEt8kiMEauji2WHTDoVBOKl7UlewXmUgBPnr/oPRZC3psci5chJnYmH0ivEIog2OU9PGWoki3DLQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "c12": "3.1.0",
+ "deepmerge-ts": "7.1.5",
+ "effect": "3.21.0",
+ "empathic": "2.0.0"
+ }
+ },
+ "node_modules/@prisma/debug": {
+ "version": "6.19.3",
+ "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.19.3.tgz",
+ "integrity": "sha512-ljkJ+SgpXNktLG0Q/n4JGYCkKf0f8oYLyjImS2I8e2q2WCfdRRtWER062ZV/ixaNP2M2VKlWXVJiGzZaUgbKZw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/engines": {
+ "version": "6.19.3",
+ "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.19.3.tgz",
+ "integrity": "sha512-RSYxtlYFl5pJ8ZePgMv0lZ9IzVCOdTPOegrs2qcbAEFrBI1G33h6wyC9kjQvo0DnYEhEVY0X4LsuFHXLKQk88g==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "6.19.3",
+ "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7",
+ "@prisma/fetch-engine": "6.19.3",
+ "@prisma/get-platform": "6.19.3"
+ }
+ },
+ "node_modules/@prisma/engines-version": {
+ "version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7",
+ "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7.tgz",
+ "integrity": "sha512-03bgb1VD5gvuumNf+7fVGBzfpJPjmqV423l/WxsWk2cNQ42JD0/SsFBPhN6z8iAvdHs07/7ei77SKu7aZfq8bA==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/fetch-engine": {
+ "version": "6.19.3",
+ "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.19.3.tgz",
+ "integrity": "sha512-tKtl/qco9Nt7LU5iKhpultD8O4vMCZcU2CHjNTnRrL1QvSUr5W/GcyFPjNL87GtRrwBc7ubXXD9xy4EvLvt8JA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "6.19.3",
+ "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7",
+ "@prisma/get-platform": "6.19.3"
+ }
+ },
+ "node_modules/@prisma/get-platform": {
+ "version": "6.19.3",
+ "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.19.3.tgz",
+ "integrity": "sha512-xFj1VcJ1N3MKooOQAGO0W5tsd0W2QzIvW7DD7c/8H14Zmp4jseeWAITm+w2LLoLrlhoHdPPh0NMZ8mfL6puoHA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "6.19.3"
+ }
+ },
"node_modules/@rtsao/scc": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -1313,6 +1845,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@swc/helpers": {
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
@@ -2668,6 +3207,48 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
+ "node_modules/c12": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz",
+ "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^4.0.3",
+ "confbox": "^0.2.2",
+ "defu": "^6.1.4",
+ "dotenv": "^16.6.1",
+ "exsolve": "^1.0.7",
+ "giget": "^2.0.0",
+ "jiti": "^2.4.2",
+ "ohash": "^2.0.11",
+ "pathe": "^2.0.3",
+ "perfect-debounce": "^1.0.0",
+ "pkg-types": "^2.2.0",
+ "rc9": "^2.1.2"
+ },
+ "peerDependencies": {
+ "magicast": "^0.3.5"
+ },
+ "peerDependenciesMeta": {
+ "magicast": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/c12/node_modules/dotenv": {
+ "version": "16.6.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
+ "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
"node_modules/call-bind": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz",
@@ -2765,6 +3346,32 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/chokidar": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14.16.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/citty": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz",
+ "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "consola": "^3.2.3"
+ }
+ },
"node_modules/client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
@@ -2798,6 +3405,23 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/confbox": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz",
+ "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/consola": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
+ "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^14.18.0 || >=16.10.0"
+ }
+ },
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@@ -2913,6 +3537,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/deepmerge-ts": {
+ "version": "7.1.5",
+ "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz",
+ "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
"node_modules/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
@@ -2949,6 +3583,20 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/defu": {
+ "version": "6.1.7",
+ "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz",
+ "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/destr": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz",
+ "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -2972,6 +3620,19 @@
"node": ">=0.10.0"
}
},
+ "node_modules/dotenv": {
+ "version": "17.4.2",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
+ "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -2987,6 +3648,17 @@
"node": ">= 0.4"
}
},
+ "node_modules/effect": {
+ "version": "3.21.0",
+ "resolved": "https://registry.npmjs.org/effect/-/effect-3.21.0.tgz",
+ "integrity": "sha512-PPN80qRokCd1f015IANNhrwOnLO7GrrMQfk4/lnZRE/8j7UPWrNNjPV0uBrZutI/nHzernbW+J0hdqQysHiSnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.0.0",
+ "fast-check": "^3.23.1"
+ }
+ },
"node_modules/electron-to-chromium": {
"version": "1.5.366",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.366.tgz",
@@ -3001,6 +3673,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/empathic": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz",
+ "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ }
+ },
"node_modules/enhanced-resolve": {
"version": "5.22.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.22.1.tgz",
@@ -3192,6 +3874,48 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/esbuild": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz",
+ "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.28.0",
+ "@esbuild/android-arm": "0.28.0",
+ "@esbuild/android-arm64": "0.28.0",
+ "@esbuild/android-x64": "0.28.0",
+ "@esbuild/darwin-arm64": "0.28.0",
+ "@esbuild/darwin-x64": "0.28.0",
+ "@esbuild/freebsd-arm64": "0.28.0",
+ "@esbuild/freebsd-x64": "0.28.0",
+ "@esbuild/linux-arm": "0.28.0",
+ "@esbuild/linux-arm64": "0.28.0",
+ "@esbuild/linux-ia32": "0.28.0",
+ "@esbuild/linux-loong64": "0.28.0",
+ "@esbuild/linux-mips64el": "0.28.0",
+ "@esbuild/linux-ppc64": "0.28.0",
+ "@esbuild/linux-riscv64": "0.28.0",
+ "@esbuild/linux-s390x": "0.28.0",
+ "@esbuild/linux-x64": "0.28.0",
+ "@esbuild/netbsd-arm64": "0.28.0",
+ "@esbuild/netbsd-x64": "0.28.0",
+ "@esbuild/openbsd-arm64": "0.28.0",
+ "@esbuild/openbsd-x64": "0.28.0",
+ "@esbuild/openharmony-arm64": "0.28.0",
+ "@esbuild/sunos-x64": "0.28.0",
+ "@esbuild/win32-arm64": "0.28.0",
+ "@esbuild/win32-ia32": "0.28.0",
+ "@esbuild/win32-x64": "0.28.0"
+ }
+ },
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
@@ -3621,6 +4345,36 @@
"node": ">=0.10.0"
}
},
+ "node_modules/exsolve": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz",
+ "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-check": {
+ "version": "3.23.2",
+ "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz",
+ "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "pure-rand": "^6.1.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -3762,6 +4516,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -3893,6 +4662,24 @@
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
+ "node_modules/giget": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz",
+ "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "citty": "^0.1.6",
+ "consola": "^3.4.0",
+ "defu": "^6.1.4",
+ "node-fetch-native": "^1.6.6",
+ "nypm": "^0.6.0",
+ "pathe": "^2.0.3"
+ },
+ "bin": {
+ "giget": "dist/cli.mjs"
+ }
+ },
"node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -5247,6 +6034,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/node-fetch-native": {
+ "version": "1.6.7",
+ "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz",
+ "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/node-releases": {
"version": "2.0.47",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz",
@@ -5257,6 +6051,31 @@
"node": ">=18"
}
},
+ "node_modules/nypm": {
+ "version": "0.6.6",
+ "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.6.tgz",
+ "integrity": "sha512-vRyr0r4cbBapw07Xw8xrj9Teq3o7MUD35rSaTcanDbW+aK2XHDgJFiU6ZTj2GBw7Q12ysdsyFss+Vdz4hQ0Y6Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "citty": "^0.2.2",
+ "pathe": "^2.0.3",
+ "tinyexec": "^1.1.1"
+ },
+ "bin": {
+ "nypm": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/nypm/node_modules/citty": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.2.tgz",
+ "integrity": "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -5380,6 +6199,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/ohash": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz",
+ "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -5488,6 +6314,20 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/perfect-debounce": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
+ "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -5507,6 +6347,18 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/pkg-types": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz",
+ "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "confbox": "^0.2.4",
+ "exsolve": "^1.0.8",
+ "pathe": "^2.0.3"
+ }
+ },
"node_modules/possible-typed-array-names": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
@@ -5556,6 +6408,32 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/prisma": {
+ "version": "6.19.3",
+ "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.19.3.tgz",
+ "integrity": "sha512-++ZJ0ijLrDJF6hNB4t4uxg2br3fC4H9Yc9tcbjr2fcNFP3rh/SBNrAgjhsqBU4Ght8JPrVofG/ZkXfnSfnYsFg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/config": "6.19.3",
+ "@prisma/engines": "6.19.3"
+ },
+ "bin": {
+ "prisma": "build/index.js"
+ },
+ "engines": {
+ "node": ">=18.18"
+ },
+ "peerDependencies": {
+ "typescript": ">=5.1.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -5578,6 +6456,23 @@
"node": ">=6"
}
},
+ "node_modules/pure-rand": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
+ "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -5599,6 +6494,17 @@
],
"license": "MIT"
},
+ "node_modules/rc9": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz",
+ "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "defu": "^6.1.4",
+ "destr": "^2.0.3"
+ }
+ },
"node_modules/react": {
"version": "19.2.4",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
@@ -5627,6 +6533,20 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/readdirp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.18.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
"node_modules/reflect.getprototypeof": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@@ -6263,6 +7183,16 @@
"url": "https://opencollective.com/webpack"
}
},
+ "node_modules/tinyexec": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz",
+ "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/tinyglobby": {
"version": "0.2.17",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
@@ -6369,6 +7299,25 @@
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
+ "node_modules/tsx": {
+ "version": "4.22.4",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz",
+ "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "~0.28.0"
+ },
+ "bin": {
+ "tsx": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ }
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
diff --git a/package.json b/package.json
index d871e72..40a3aad 100644
--- a/package.json
+++ b/package.json
@@ -14,13 +14,17 @@
"react-dom": "19.2.4"
},
"devDependencies": {
+ "@prisma/client": "^6.19.3",
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
+ "dotenv": "^17.4.2",
"eslint": "^9",
"eslint-config-next": "16.2.7",
+ "prisma": "^6.19.3",
"tailwindcss": "^4",
+ "tsx": "^4.22.4",
"typescript": "^5"
}
}
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
new file mode 100644
index 0000000..97d4b4b
--- /dev/null
+++ b/prisma/schema.prisma
@@ -0,0 +1,32 @@
+generator client {
+ provider = "prisma-client-js"
+}
+
+datasource db {
+ provider = "postgresql"
+ url = env("DATABASE_URL")
+}
+
+model Category {
+ id String @id
+ emoji String
+ labelTr String
+ labelEn String
+ order Int @default(0)
+ items MenuItem[]
+}
+
+model MenuItem {
+ id String @id
+ emoji String?
+ image String?
+ nameTr String
+ nameEn String
+ descriptionTr String?
+ descriptionEn String?
+ price Float?
+ highlight Boolean @default(false)
+ order Int @default(0)
+ categoryId String
+ category Category @relation(fields: [categoryId], references: [id], onDelete: Cascade)
+}
diff --git a/proxy.ts b/proxy.ts
index 562d378..ea04b68 100644
--- a/proxy.ts
+++ b/proxy.ts
@@ -7,6 +7,26 @@ const defaultLocale = 'tr'
export function proxy(request: NextRequest) {
const { pathname } = request.nextUrl
+ // Admin Auth Logic
+ if (pathname.startsWith('/admin')) {
+ const session = request.cookies.get('admin_session')?.value
+ const secret = process.env.ADMIN_SECRET || 'secret'
+ const isLoginPage = pathname === '/admin/login'
+
+ if (!session || session !== secret) {
+ if (!isLoginPage) {
+ return NextResponse.redirect(new URL('/admin/login', request.url))
+ }
+ } else {
+ if (isLoginPage) {
+ return NextResponse.redirect(new URL('/admin', request.url))
+ }
+ }
+
+ return NextResponse.next()
+ }
+
+ // Locale Logic
const hasLocale = locales.some(
(locale) => pathname === `/${locale}` || pathname.startsWith(`/${locale}/`)
)
@@ -15,6 +35,8 @@ export function proxy(request: NextRequest) {
request.nextUrl.pathname = `/${defaultLocale}${pathname}`
return NextResponse.redirect(request.nextUrl)
}
+
+ return NextResponse.next()
}
export const config = {
diff --git a/scripts/migrate.ts b/scripts/migrate.ts
new file mode 100644
index 0000000..47b304e
--- /dev/null
+++ b/scripts/migrate.ts
@@ -0,0 +1,78 @@
+import { PrismaClient } from '@prisma/client'
+import fs from 'fs'
+import path from 'path'
+
+const prisma = new PrismaClient()
+
+type LocalizedString = { tr: string; en: string }
+
+type JsonMenuItem = {
+ id: string
+ emoji?: string
+ image?: string
+ name: LocalizedString
+ description?: LocalizedString
+ price?: number
+ highlight?: boolean
+}
+
+type JsonCategory = {
+ id: string
+ emoji: string
+ label: LocalizedString
+ items: JsonMenuItem[]
+}
+
+async function migrate() {
+ console.log('Starting migration...')
+
+ const filePath = path.join(process.cwd(), 'data', 'menu.json')
+ const fileContent = fs.readFileSync(filePath, 'utf-8')
+ const categories: JsonCategory[] = JSON.parse(fileContent)
+
+ // Clear existing data (if any)
+ await prisma.menuItem.deleteMany({})
+ await prisma.category.deleteMany({})
+ console.log('Cleared existing data')
+
+ for (let cIndex = 0; cIndex < categories.length; cIndex++) {
+ const cat = categories[cIndex]
+
+ await prisma.category.create({
+ data: {
+ id: cat.id,
+ emoji: cat.emoji,
+ labelTr: cat.label.tr,
+ labelEn: cat.label.en,
+ order: cIndex,
+ items: {
+ create: cat.items.map((item, iIndex) => ({
+ id: item.id,
+ emoji: item.emoji || null,
+ image: item.image || null,
+ nameTr: item.name.tr,
+ nameEn: item.name.en,
+ descriptionTr: item.description?.tr || null,
+ descriptionEn: item.description?.en || null,
+ price: item.price !== undefined ? item.price : null,
+ highlight: item.highlight || false,
+ order: iIndex,
+ }))
+ }
+ }
+ })
+
+ console.log(`Migrated category: ${cat.label.tr}`)
+ }
+
+ console.log('Migration complete!')
+}
+
+migrate()
+ .catch((e) => {
+ console.error(e)
+ process.exit(1)
+ })
+ .finally(async () => {
+ await prisma.$disconnect()
+ })