Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d33a38de4e | ||
|
|
df250c40e8 | ||
|
|
6ec4631e82 | ||
|
|
00195ae7d7 | ||
|
|
739de7de5c | ||
|
|
1f8195ff67 | ||
|
|
e7ba6c7807 | ||
|
|
c1569c0879 | ||
|
|
ae2386eacf | ||
|
|
d00b45ce72 | ||
|
|
102a9a744b | ||
|
|
b0645c0ac3 | ||
|
|
2c3599839b | ||
|
|
2530ab4005 | ||
|
|
b72846bd91 | ||
|
|
d2846ed628 | ||
|
|
4dbb68a73a | ||
|
|
932c174b5f | ||
|
|
34dd24eee2 | ||
|
|
8481a34e61 | ||
|
|
ccd8c90267 | ||
|
|
3629f98855 | ||
|
|
dd282f3987 | ||
|
|
4236ddb5ee | ||
|
|
c7e4832441 | ||
|
|
fa50e73198 | ||
|
|
808097377d | ||
|
|
6cde8dfb87 | ||
|
|
01c9f5f195 | ||
|
|
f768b92873 | ||
|
|
eb0b5d1886 | ||
|
|
38fd0dd030 | ||
|
|
18396f3b99 | ||
|
|
edb7af9288 | ||
|
|
a0cb04b2c6 | ||
|
|
4ce6505a4d |
@@ -5,6 +5,10 @@ build/
|
||||
.expo/
|
||||
.turbo/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
*.tsbuildinfo
|
||||
.env
|
||||
.env.local
|
||||
@@ -15,3 +19,7 @@ supabase/.temp/
|
||||
coverage/
|
||||
*.ipa
|
||||
*.apk
|
||||
|
||||
# Lockfile protections (monorepo strictly uses pnpm-lock.yaml)
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
auto-install-peers=true
|
||||
strict-peer-dependencies=false
|
||||
enable-pre-post-scripts=true
|
||||
only-built-dependencies=core-js,@swc/core,esbuild,sharp,@esbuild/darwin-arm64,@next/swc-darwin-arm64
|
||||
@@ -0,0 +1,144 @@
|
||||
# 🚀 Menulio — Cross-Platform SaaS & Dynamic QR Stand Studio Ecosystem
|
||||
|
||||
> **Modern Restoranlar İçin Dijital Menü Deneyimi & Anlık JSON Şablon Motoru Destekli Masa Kartı Tasarım Platformu**
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## 📌 Proje Özeti (Executive Summary)
|
||||
|
||||
**Menulio**, restoranların ve gastronomi işletmelerinin dijital menülerini ve fiziksel masa kartı QR stantlarını uçtan uca yönetmelerini sağlayan **Full-Stack Cross-Platform SaaS mimarisidir**.
|
||||
|
||||
Geleneksel QR menü sistemlerinden farklı olarak Menulio, **0 (Sıfır) App Store güncellemesi gerektiren "JSON-Driven Dynamic Template Engine" (V2)** teknolojisine sahiptir. Admin panelinden tasarlanan SVG/PNG arka planları, kavisli metin hesaplamaları (*curved text math*), 360° döndürme, renk paletleri ve özel QR yerleşimleri API üzerinden anında hem Web hem de Mobil (iOS/Android) platformlarında canlı yayınlanır.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Öne Çıkan Mimari Özellikler (Key Highlights)
|
||||
|
||||
### 1. 🎨 JSON-Driven Dynamic QR Stand Engine (V2)
|
||||
- **Sıfır Güncelleme Dağıtımı**: Mobil uygulamayı yeniden derlemeden Web Admin panelinden yeni masa kartı şablonu oluşturma ve anında yayınlama.
|
||||
- **Kavisli Metin Matematiği (`curveDegree`)**: Radyal açı hesaplamaları ile dairesel ve yay biçiminde metin hizalama (Yay ◡ ve ◠ kavis derecesi ayarlama).
|
||||
- **Figma & Instagram Story Tarzı Tuval Editörü**: Sürüklenebilir (*drag & drop*), ölçeklenebilir, 360° döndürülebilir metin ve QR katmanları.
|
||||
- **Manyetik Kılavuz Çizgileri**: Akıllı dikey/yatay merkezleme ve hizalama çizgileri (*snap-to-grid*).
|
||||
- **Matbaa Kalitesinde Baskı Çıktısı**: 300 DPI Ultra HD PNG ve A5 Matbaa standartlarında vektörel PDF çıktısı.
|
||||
|
||||
### 2. 📱 Cross-Platform Mobil Uygulama (Expo & React Native)
|
||||
- **iOS & Android Desteği**: Expo SDK 52 ve React Native mimarisi üzerine kurulu yüksek performanslı arayüz.
|
||||
- **Dokunsal Geri Bildirim (Haptic Feedback)**: İşlem anında `expo-haptics` ile kullanıcıya fiziksel titreşim uyarısı.
|
||||
- **Yerel iOS Derleme & Paketleme**: Xcode Archive & Fastlane entegrasyonu ile tam imzalı `embedded.mobileprovision` içerikli `.ipa` dağıtımı.
|
||||
|
||||
### 3. 🌐 Web Uygulaması & 10+ Lüks Menü Teması (Next.js 15 App Router)
|
||||
- **10 Özel Restoran Teması**: `Eleia Coastal`, `Neon Street Fuze`, `La Maison d'Élève`, `Swiss Brutalist`, `Amber Artisan`, `Elegant Gold`, `Modern Sapphire`, `Luxury Dark`, `Nordic Minimal`, `Classic Bistro`.
|
||||
- **Wildcard Subdomain Yönlendirmesi**: `restoran-adi.menul.io` şeklinde her işletmeye özel subdomain ve özel alan adı (custom domain) desteği.
|
||||
- **Canlı Cihaz Simülatörü**: Admin panelinde tema ve şablon değişikliklerini anında gösteren interaktif iPhone çerçevesi.
|
||||
|
||||
### 4. 🔐 Çift Katmanlı Yönetici Güvenliği (Dual-Layer Admin Auth)
|
||||
- **Next.js Server Middleware**: Yetkisiz sunucu isteklerini anında `/admin/login` sayfasına yönlendiren çerez tabanlı doğrulama.
|
||||
- **Client Auth Guard**: React tarafında yükleme animasyonu ve oturum doğrulama mekanizması.
|
||||
- **Supabase Auth & Fallback Credential**: Supabase PostgreSQL veritabanı entegrasyonu ve güvenli token çerezi (`menulio_admin_token`).
|
||||
|
||||
---
|
||||
|
||||
## 💻 Teknolojik Mimari & Kullanılan Teknolojiler
|
||||
|
||||
| Katman | Kullanılan Teknolojiler |
|
||||
| :--- | :--- |
|
||||
| **Monorepo Mimarisi** | `pnpm Workspaces`, Shared Type Definitions (`@menulio/shared`) |
|
||||
| **Web Uygulaması (Frontend)** | `Next.js 15 (App Router)`, `React 18`, `Tailwind CSS`, `Glassmorphism UI` |
|
||||
| **Mobil Uygulama** | `React Native`, `Expo (SDK 52)`, `Expo Router`, `ViewShot`, `Expo Haptics` |
|
||||
| **Veritabanı & Auth** | `Supabase (PostgreSQL, Auth, RLS)`, REST API, Local Disk Cache |
|
||||
| **Görsel & Baskı Motoru** | `html-to-image`, `jspdf`, `qrcode.react`, Google Fonts API |
|
||||
| **CI/CD & Derleme** | `Xcode 26`, `Fastlane`, `EAS Build (Expo Application Services)` |
|
||||
|
||||
---
|
||||
|
||||
## 📸 Ekran Görüntüleri & Görsel Bileşenler
|
||||
|
||||
```
|
||||
+-----------------------------------------------------------------------+
|
||||
| MENULIO ADMIN PANEL - DİNAMİK ŞABLON & TEMA STÜDYOSU |
|
||||
+-----------------------------------------------------------------------+
|
||||
| [ Sol Panel: Renk/Düzen Kontrolleri ] | [ Sağ: Canlı iPhone Simülatörü ]|
|
||||
| - Primary Color: #D4AF37 | +-----------------------------+ |
|
||||
| - Product Layout: Card / List | | OZZY KEBAP | |
|
||||
| - Category Layout: Tabs / Accordion | | [ QR KOD BÖLGESİ ] | |
|
||||
| - Curved Text Math: 35° Arc | | MENÜYÜ GÖRMEK İÇİN OKUTUN | |
|
||||
| - Font Family: Playfair / Inter | +-----------------------------+ |
|
||||
+-----------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📐 Proje Dosya & Klasör Yapısı
|
||||
|
||||
```
|
||||
menulio/
|
||||
├── apps/
|
||||
│ ├── mobile/ # React Native / Expo Mobil Uygulama
|
||||
│ │ ├── src/
|
||||
│ │ │ ├── app/ # Expo Router Dosya Tabanlı Yönlendirme
|
||||
│ │ │ ├── components/
|
||||
│ │ │ │ ├── qr-canvas/ # Tuval Editörü, Curved Text & Dokunmatik Katmanlar
|
||||
│ │ │ │ └── qr-templates/ # SVG & Vektörel Şablon Render Bileşenleri
|
||||
│ │ └── ios/ # Yerel Xcode Proje Klasörü & Podfile
|
||||
│ │
|
||||
│ ├── web/ # Next.js 15 Web & Admin Uygulaması
|
||||
│ │ ├── src/
|
||||
│ │ │ ├── app/
|
||||
│ │ │ │ ├── admin/ # Yönetici Paneli & Auth Guard (/admin)
|
||||
│ │ │ │ │ ├── templates/ # QR Stand Şablon Stüdyosu
|
||||
│ │ │ │ │ └── menu-templates/ # Web Menü Temaları Stüdyosu
|
||||
│ │ │ │ ├── qr/ # Müşteri QR Masa Kartı Jeneratörü
|
||||
│ │ │ │ └── menu/[slug]/ # Canlı Restoran Web Menüsü
|
||||
│ │ │ ├── components/ # Web Bileşenleri & Simülatörler
|
||||
│ │ │ └── lib/ # Auth, Supabase & Tema Tanımları
|
||||
│ │
|
||||
│ └── api/ # Express / Node.js Backend REST Servisleri
|
||||
│
|
||||
└── packages/
|
||||
└── shared/ # Tip Tanımları, Renk Paletleri & Ortak Sabitler
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Kurulum ve Çalıştırma (Getting Started)
|
||||
|
||||
Proje `pnpm` monorepo yapısında kurgulanmıştır.
|
||||
|
||||
### 1. Bağımlılıkları Yükleyin
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### 2. Geliştirme Sunucularını Başlatın
|
||||
|
||||
- **Tüm Uygulamaları Aynı Anda Başlatma**:
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
- **Sadece Web Uygulamasını Başlatma**:
|
||||
```bash
|
||||
pnpm --filter web dev
|
||||
```
|
||||
- **Sadece Mobil Uygulamayı (Expo) Başlatma**:
|
||||
```bash
|
||||
pnpm --filter mobile start
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 👨💻 Mühendislik & Problem Çözme Vurguları (Portfolio Highlights)
|
||||
|
||||
1. **React Native Font Rendering Fix**:
|
||||
- React Native'in iOS ve Android native font motorunda virgüllü CSS fallback dizgilerinin (`Georgia, 'Times New Roman', serif`) sessizce varsayılan sistem fontuna düşme problemi tespit edilerek, platforma özel tekil font çözümleme mekanizması geliştirildi.
|
||||
2. **Kavisli Metin Radyal Geometri Matematiği**:
|
||||
- Metin karakterlerinin dairesel bir yay etrafında doğru açı (`rotate`), `translateX` ve `translateY` offset hesaplamaları ile hem Web SVG/HTML hem de Native Text bileşenlerinde piksel mükemmelliğinde işlenmesi sağlandı.
|
||||
3. **Senkronize JSON Şablon Yayınlama**:
|
||||
- Web Admin panelinde yapılan herhangi bir tasarım veya metin düzenlemesinin, veritabanı ve REST API üzerinden anında mobil uygulama tuvalinde ve web jeneratöründe canlı olarak görünmesi sağlandı.
|
||||
|
||||
---
|
||||
|
||||
## 📄 Lisans
|
||||
|
||||
Bu proje kişisel portfolyo ve özel geliştirme amacıyla kurgulanmıştır. Tüm hakları saklıdır.
|
||||
@@ -0,0 +1,203 @@
|
||||
[
|
||||
{
|
||||
"key": "qr-1",
|
||||
"name": "Lumière Restaurant (QR-1)",
|
||||
"categoryName": "Özel Masa Kartı",
|
||||
"svgTemplateUrl": "/qr-1.svg",
|
||||
"aspectRatio": "1/1",
|
||||
"bgType": "gradient",
|
||||
"bgGradient": "radial-gradient(circle at 50% 50%, #132034 0%, #0b0d13 100%)",
|
||||
"plaqueColor": "#132034",
|
||||
"borderAccent": "#d4af37",
|
||||
"accentText": "#d4af37",
|
||||
"headerText": "#FFFFFF",
|
||||
"subText": "#e5c378",
|
||||
"dividerColor": "rgba(212, 175, 55, 0.3)",
|
||||
"isActive": true,
|
||||
"qrConfig": {
|
||||
"xPercent": 50,
|
||||
"yPercent": 38,
|
||||
"size": 115,
|
||||
"borderRadius": 14,
|
||||
"borderColor": "#D4AF37",
|
||||
"bgBoxColor": "#FFFFFF"
|
||||
},
|
||||
"layers": [
|
||||
{
|
||||
"id": "rName",
|
||||
"text": "{{RESTAURANT_NAME}}",
|
||||
"yPercent": 16,
|
||||
"fontSize": 18,
|
||||
"fontFamily": "Playfair Display, Georgia, serif",
|
||||
"color": "#D4AF37",
|
||||
"fontWeight": "800",
|
||||
"letterSpacing": 2
|
||||
},
|
||||
{
|
||||
"id": "sub",
|
||||
"text": "RESTAURANT & BAR",
|
||||
"yPercent": 26,
|
||||
"fontSize": 10,
|
||||
"fontFamily": "Plus Jakarta Sans, sans-serif",
|
||||
"color": "#E5C378",
|
||||
"fontWeight": "600",
|
||||
"letterSpacing": 1.5
|
||||
},
|
||||
{
|
||||
"id": "cta",
|
||||
"text": "MENÜYÜ GÖRMEK İÇİN OKUTUN",
|
||||
"yPercent": 80,
|
||||
"fontSize": 11,
|
||||
"fontFamily": "Playfair Display, Georgia, serif",
|
||||
"color": "#FFFFFF",
|
||||
"fontWeight": "700",
|
||||
"letterSpacing": 1
|
||||
},
|
||||
{
|
||||
"id": "table",
|
||||
"text": "MASA NO: 01 • WIFI: Bistro",
|
||||
"yPercent": 90,
|
||||
"fontSize": 9,
|
||||
"fontFamily": "Plus Jakarta Sans, sans-serif",
|
||||
"color": "#A1A1AA",
|
||||
"fontWeight": "600"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "qr-2",
|
||||
"name": "The Daily Grind Cafe (QR-2)",
|
||||
"categoryName": "Özel Masa Kartı",
|
||||
"svgTemplateUrl": "/qr-2.svg",
|
||||
"aspectRatio": "400/650",
|
||||
"bgType": "gradient",
|
||||
"bgGradient": "radial-gradient(circle at 50% 50%, #252c36 0%, #171c22 100%)",
|
||||
"plaqueColor": "#f6efe2",
|
||||
"borderAccent": "#3a2312",
|
||||
"accentText": "#8c6239",
|
||||
"headerText": "#2c1a0e",
|
||||
"subText": "#3a2312",
|
||||
"dividerColor": "rgba(140, 98, 57, 0.3)",
|
||||
"isActive": true,
|
||||
"qrConfig": {
|
||||
"xPercent": 50,
|
||||
"yPercent": 36,
|
||||
"size": 125,
|
||||
"borderRadius": 14,
|
||||
"borderColor": "#3a2312",
|
||||
"bgBoxColor": "#FFFFFF"
|
||||
},
|
||||
"layers": [
|
||||
{
|
||||
"id": "rName",
|
||||
"text": "{{RESTAURANT_NAME}}",
|
||||
"yPercent": 14,
|
||||
"fontSize": 18,
|
||||
"fontFamily": "Playfair Display, Georgia, serif",
|
||||
"color": "#8c6239",
|
||||
"fontWeight": "800",
|
||||
"letterSpacing": 2
|
||||
},
|
||||
{
|
||||
"id": "cta",
|
||||
"text": "MENÜYÜ GÖRMEK İÇİN OKUTUN",
|
||||
"yPercent": 82,
|
||||
"fontSize": 11,
|
||||
"fontFamily": "Playfair Display, Georgia, serif",
|
||||
"color": "#2c1a0e",
|
||||
"fontWeight": "700",
|
||||
"letterSpacing": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "qr-3",
|
||||
"name": "Pizzeria Bella Napoli (QR-3)",
|
||||
"categoryName": "Özel Masa Kartı",
|
||||
"svgTemplateUrl": "/qr-3.svg",
|
||||
"aspectRatio": "400/650",
|
||||
"bgType": "gradient",
|
||||
"bgGradient": "radial-gradient(circle at 50% 50%, #faf4e6 0%, #f7eed8 100%)",
|
||||
"plaqueColor": "#fdfbf5",
|
||||
"borderAccent": "#b92b27",
|
||||
"accentText": "#b92b27",
|
||||
"headerText": "#18181b",
|
||||
"subText": "#5d4037",
|
||||
"dividerColor": "rgba(185, 43, 39, 0.3)",
|
||||
"isActive": true,
|
||||
"qrConfig": {
|
||||
"xPercent": 50,
|
||||
"yPercent": 36,
|
||||
"size": 125,
|
||||
"borderRadius": 14,
|
||||
"borderColor": "#b92b27",
|
||||
"bgBoxColor": "#FFFFFF"
|
||||
},
|
||||
"layers": [
|
||||
{
|
||||
"id": "rName",
|
||||
"text": "{{RESTAURANT_NAME}}",
|
||||
"yPercent": 14,
|
||||
"fontSize": 18,
|
||||
"fontFamily": "Playfair Display, Georgia, serif",
|
||||
"color": "#b92b27",
|
||||
"fontWeight": "800",
|
||||
"letterSpacing": 2
|
||||
},
|
||||
{
|
||||
"id": "cta",
|
||||
"text": "MENÜYÜ GÖRMEK İÇİN OKUTUN",
|
||||
"yPercent": 82,
|
||||
"fontSize": 11,
|
||||
"fontFamily": "Playfair Display, Georgia, serif",
|
||||
"color": "#18181b",
|
||||
"fontWeight": "700",
|
||||
"letterSpacing": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "qr-8",
|
||||
"name": "Emerald & Gold Special (QR-8)",
|
||||
"categoryName": "Özel Kare Masa Kartı",
|
||||
"svgTemplateUrl": "/qr-8.svg",
|
||||
"aspectRatio": "1/1",
|
||||
"bgType": "gradient",
|
||||
"bgGradient": "radial-gradient(circle at 50% 50%, #0c4443 0%, #062423 100%)",
|
||||
"plaqueColor": "#f9f5eb",
|
||||
"borderAccent": "#d1a752",
|
||||
"accentText": "#d1a752",
|
||||
"headerText": "#ffffff",
|
||||
"subText": "#d1a752",
|
||||
"dividerColor": "rgba(209, 167, 82, 0.4)",
|
||||
"isActive": true,
|
||||
"qrConfig": {
|
||||
"xPercent": 50,
|
||||
"yPercent": 38,
|
||||
"size": 115,
|
||||
"borderRadius": 14,
|
||||
"borderColor": "#d1a752",
|
||||
"bgBoxColor": "#FFFFFF"
|
||||
},
|
||||
"layers": [
|
||||
{
|
||||
"id": "rName",
|
||||
"text": "{{RESTAURANT_NAME}}",
|
||||
"yPercent": 16,
|
||||
"fontSize": 18,
|
||||
"fontFamily": "Playfair Display, Georgia, serif",
|
||||
"color": "#d1a752",
|
||||
"fontWeight": "800"
|
||||
},
|
||||
{
|
||||
"id": "cta",
|
||||
"text": "MENÜYÜ GÖRMEK İÇİN OKUTUN",
|
||||
"yPercent": 80,
|
||||
"fontSize": 11,
|
||||
"fontFamily": "Playfair Display, Georgia, serif",
|
||||
"color": "#ffffff",
|
||||
"fontWeight": "700"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -4,11 +4,13 @@ import { env } from "./env.js";
|
||||
import { aiImportsRoutes } from "./routes/ai-imports.js";
|
||||
import { analyticsRoutes } from "./routes/analytics.js";
|
||||
import { domainsRoutes } from "./routes/domains.js";
|
||||
import { languagesRoutes } from "./routes/languages.js";
|
||||
import { menuCategoriesRoutes } from "./routes/menu-categories.js";
|
||||
import { menuItemsRoutes } from "./routes/menu-items.js";
|
||||
import { menusRoutes } from "./routes/menus.js";
|
||||
import { meRoutes } from "./routes/me.js";
|
||||
import { qrRoutes } from "./routes/qr.js";
|
||||
import { qrTemplatesRoutes } from "./routes/qr-templates.js";
|
||||
import { restaurantsRoutes } from "./routes/restaurants.js";
|
||||
import { subscriptionRoutes } from "./routes/subscription.js";
|
||||
|
||||
@@ -42,8 +44,10 @@ await app.register(menusRoutes);
|
||||
await app.register(menuCategoriesRoutes);
|
||||
await app.register(menuItemsRoutes);
|
||||
await app.register(qrRoutes);
|
||||
await app.register(qrTemplatesRoutes);
|
||||
await app.register(aiImportsRoutes);
|
||||
await app.register(domainsRoutes);
|
||||
await app.register(languagesRoutes);
|
||||
await app.register(analyticsRoutes);
|
||||
await app.register(subscriptionRoutes);
|
||||
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
import { env } from "../env.js";
|
||||
|
||||
export interface TranslatableItem {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
}
|
||||
|
||||
export interface TranslatableCategory {
|
||||
id: string;
|
||||
name: string;
|
||||
items: TranslatableItem[];
|
||||
}
|
||||
|
||||
export interface TranslatedItem {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
}
|
||||
|
||||
export interface TranslatedCategory {
|
||||
id: string;
|
||||
name: string;
|
||||
items: TranslatedItem[];
|
||||
}
|
||||
|
||||
const LANGUAGE_NAMES: Record<string, string> = {
|
||||
en: "English",
|
||||
de: "German",
|
||||
ar: "Arabic",
|
||||
ru: "Russian",
|
||||
fr: "French",
|
||||
};
|
||||
|
||||
function buildPrompt(targetLanguageName: string): string {
|
||||
return `Sen profesyonel bir restoran menüsü çevirmenisin. Sana Türkçe bir restoran menüsü (kategori adları, ürün adları ve açıklamaları) JSON olarak verilecek.
|
||||
Bunları ${targetLanguageName} diline çevir. Yemek isimlerini o mutfağın doğal terimleriyle çevir (örn. "Adana Kebap" -> "Adana Kebab", tamamen literal çeviri yapma, restoran menüsü diline uygun doğal bir üslup kullan).
|
||||
Her kategori ve ürünün "id" alanını AYNEN koru, sadece "name" ve "description" alanlarını çevir. description null ise null bırak.
|
||||
|
||||
SADECE aşağıdaki JSON formatında geçerli bir JSON yanıtı ver, başka hiçbir metin ekleme:
|
||||
{
|
||||
"categories": [
|
||||
{ "id": "...", "name": "çevrilmiş kategori adı", "items": [ { "id": "...", "name": "çevrilmiş ürün adı", "description": "çevrilmiş açıklama veya null" } ] }
|
||||
]
|
||||
}`;
|
||||
}
|
||||
|
||||
function cleanAndParse(text: string): { categories: TranslatedCategory[] } {
|
||||
const cleaned = text
|
||||
.replace(/^```json\s*/i, "")
|
||||
.replace(/^```\s*/i, "")
|
||||
.replace(/\s*```$/i, "")
|
||||
.trim();
|
||||
const parsed = JSON.parse(cleaned) as { categories: TranslatedCategory[] };
|
||||
if (!parsed.categories || !Array.isArray(parsed.categories)) {
|
||||
throw new Error("Invalid structure from translator: categories array missing");
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
async function translateWithGemini(
|
||||
categories: TranslatableCategory[],
|
||||
targetLanguageName: string,
|
||||
apiKey: string,
|
||||
): Promise<TranslatedCategory[]> {
|
||||
const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${apiKey}`;
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
contents: [
|
||||
{
|
||||
parts: [
|
||||
{ text: buildPrompt(targetLanguageName) },
|
||||
{ text: JSON.stringify({ categories }) },
|
||||
],
|
||||
},
|
||||
],
|
||||
generationConfig: { response_mime_type: "application/json", temperature: 0.2 },
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Gemini translate error (${response.status}): ${await response.text()}`);
|
||||
}
|
||||
|
||||
const data = (await response.json()) as {
|
||||
candidates?: { content?: { parts?: { text?: string }[] } }[];
|
||||
};
|
||||
const rawText = data.candidates?.[0]?.content?.parts?.[0]?.text;
|
||||
if (!rawText) throw new Error("No text response from Gemini translate");
|
||||
return cleanAndParse(rawText).categories;
|
||||
}
|
||||
|
||||
async function translateWithOpenAI(
|
||||
categories: TranslatableCategory[],
|
||||
targetLanguageName: string,
|
||||
apiKey: string,
|
||||
): Promise<TranslatedCategory[]> {
|
||||
const response = await fetch("https://api.openai.com/v1/chat/completions", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
|
||||
body: JSON.stringify({
|
||||
model: "gpt-4o-mini",
|
||||
response_format: { type: "json_object" },
|
||||
messages: [
|
||||
{ role: "system", content: buildPrompt(targetLanguageName) },
|
||||
{ role: "user", content: JSON.stringify({ categories }) },
|
||||
],
|
||||
temperature: 0.2,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`OpenAI translate error (${response.status}): ${await response.text()}`);
|
||||
}
|
||||
|
||||
const data = (await response.json()) as { choices?: { message?: { content?: string } }[] };
|
||||
const content = data.choices?.[0]?.message?.content;
|
||||
if (!content) throw new Error("No message content from OpenAI translate");
|
||||
return cleanAndParse(content).categories;
|
||||
}
|
||||
|
||||
export function resolveLanguageName(code: string): string {
|
||||
return LANGUAGE_NAMES[code] ?? code;
|
||||
}
|
||||
|
||||
export async function translateMenu(
|
||||
categories: TranslatableCategory[],
|
||||
languageCode: string,
|
||||
): Promise<TranslatedCategory[]> {
|
||||
if (categories.length === 0) return [];
|
||||
const targetLanguageName = resolveLanguageName(languageCode);
|
||||
|
||||
if (env.GEMINI_API_KEY) {
|
||||
try {
|
||||
return await translateWithGemini(categories, targetLanguageName, env.GEMINI_API_KEY);
|
||||
} catch (err) {
|
||||
console.warn("Gemini translate failed, falling back:", err);
|
||||
}
|
||||
}
|
||||
|
||||
if (env.OPENAI_API_KEY) {
|
||||
return translateWithOpenAI(categories, targetLanguageName, env.OPENAI_API_KEY);
|
||||
}
|
||||
|
||||
throw new Error("No translation provider configured (GEMINI_API_KEY / OPENAI_API_KEY missing)");
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import type { FastifyPluginAsync } from "fastify";
|
||||
import { z } from "zod";
|
||||
import { requireAuth, requireRestaurantMember } from "../lib/auth.js";
|
||||
import { supabase } from "../lib/supabase.js";
|
||||
import { translateMenu, type TranslatableCategory } from "../lib/translator.js";
|
||||
|
||||
const SUPPORTED_LANGUAGES = ["en", "de", "ar", "ru", "fr"] as const;
|
||||
|
||||
const addLanguageSchema = z.object({
|
||||
code: z.enum(SUPPORTED_LANGUAGES),
|
||||
});
|
||||
|
||||
async function getRestaurantMenuCategories(restaurantId: string): Promise<TranslatableCategory[]> {
|
||||
if (!supabase) return [];
|
||||
|
||||
const { data: locations } = await supabase.from("locations").select("id").eq("restaurant_id", restaurantId);
|
||||
const locationIds = (locations ?? []).map((l) => l.id);
|
||||
if (locationIds.length === 0) return [];
|
||||
|
||||
const { data: menus } = await supabase.from("menus").select("id").in("location_id", locationIds);
|
||||
const menuIds = (menus ?? []).map((m) => m.id);
|
||||
if (menuIds.length === 0) return [];
|
||||
|
||||
const { data: categories } = await supabase
|
||||
.from("menu_categories")
|
||||
.select("id, name, menu_items(id, name, description)")
|
||||
.in("menu_id", menuIds);
|
||||
|
||||
return (categories ?? []).map((cat) => ({
|
||||
id: cat.id,
|
||||
name: cat.name,
|
||||
items: (cat.menu_items ?? []).map((item: { id: string; name: string; description: string | null }) => ({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
})),
|
||||
}));
|
||||
}
|
||||
|
||||
export const languagesRoutes: FastifyPluginAsync = async (app) => {
|
||||
// Add a language: translates all current menu content into it, then
|
||||
// enables it. New items created afterwards are translated on save
|
||||
// (see menu-categories.ts / menu-items.ts).
|
||||
app.post("/restaurants/:id/languages", async (req, reply) => {
|
||||
const userId = await requireAuth(req, reply);
|
||||
if (!userId || !supabase) return;
|
||||
|
||||
const { id: restaurantId } = req.params as { id: string };
|
||||
const isMember = await requireRestaurantMember(userId, restaurantId);
|
||||
if (!isMember) return reply.code(403).send({ message: "forbidden" });
|
||||
|
||||
const parsed = addLanguageSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
return reply.code(400).send({ message: "invalid language code", issues: parsed.error.issues });
|
||||
}
|
||||
const { code } = parsed.data;
|
||||
|
||||
const { data: restaurant } = await supabase
|
||||
.from("restaurants")
|
||||
.select("enabled_languages")
|
||||
.eq("id", restaurantId)
|
||||
.single();
|
||||
|
||||
const current: string[] = restaurant?.enabled_languages ?? ["tr"];
|
||||
if (current.includes(code)) {
|
||||
return reply.code(409).send({ message: "Bu dil zaten aktif." });
|
||||
}
|
||||
|
||||
const categories = await getRestaurantMenuCategories(restaurantId);
|
||||
|
||||
try {
|
||||
const translated = await translateMenu(categories, code);
|
||||
|
||||
for (const cat of translated) {
|
||||
const original = categories.find((c) => c.id === cat.id);
|
||||
const { data: existingCatRow } = await supabase
|
||||
.from("menu_categories")
|
||||
.select("translations")
|
||||
.eq("id", cat.id)
|
||||
.single();
|
||||
await supabase
|
||||
.from("menu_categories")
|
||||
.update({
|
||||
translations: { ...(existingCatRow?.translations ?? {}), [code]: { name: cat.name } },
|
||||
})
|
||||
.eq("id", cat.id);
|
||||
|
||||
for (const item of cat.items) {
|
||||
const { data: existingItemRow } = await supabase
|
||||
.from("menu_items")
|
||||
.select("translations")
|
||||
.eq("id", item.id)
|
||||
.single();
|
||||
await supabase
|
||||
.from("menu_items")
|
||||
.update({
|
||||
translations: {
|
||||
...(existingItemRow?.translations ?? {}),
|
||||
[code]: { name: item.name, description: item.description },
|
||||
},
|
||||
})
|
||||
.eq("id", item.id);
|
||||
}
|
||||
void original;
|
||||
}
|
||||
} catch (err) {
|
||||
req.log.error(err);
|
||||
return reply.code(500).send({ message: "Menü çevirisi başarısız oldu", error: (err as Error).message });
|
||||
}
|
||||
|
||||
const { data: updated } = await supabase
|
||||
.from("restaurants")
|
||||
.update({ enabled_languages: [...current, code] })
|
||||
.eq("id", restaurantId)
|
||||
.select("enabled_languages")
|
||||
.single();
|
||||
|
||||
return reply.send({ enabledLanguages: updated?.enabled_languages ?? [...current, code] });
|
||||
});
|
||||
|
||||
app.delete("/restaurants/:id/languages/:code", async (req, reply) => {
|
||||
const userId = await requireAuth(req, reply);
|
||||
if (!userId || !supabase) return;
|
||||
|
||||
const { id: restaurantId, code } = req.params as { id: string; code: string };
|
||||
const isMember = await requireRestaurantMember(userId, restaurantId);
|
||||
if (!isMember) return reply.code(403).send({ message: "forbidden" });
|
||||
|
||||
const { data: restaurant } = await supabase
|
||||
.from("restaurants")
|
||||
.select("enabled_languages")
|
||||
.eq("id", restaurantId)
|
||||
.single();
|
||||
|
||||
const current: string[] = restaurant?.enabled_languages ?? ["tr"];
|
||||
const next = current.filter((c) => c !== code);
|
||||
|
||||
const { data: updated } = await supabase
|
||||
.from("restaurants")
|
||||
.update({ enabled_languages: next })
|
||||
.eq("id", restaurantId)
|
||||
.select("enabled_languages")
|
||||
.single();
|
||||
|
||||
return reply.send({ enabledLanguages: updated?.enabled_languages ?? next });
|
||||
});
|
||||
};
|
||||
@@ -3,6 +3,34 @@ import { z } from "zod";
|
||||
import { requireAuth } from "../lib/auth.js";
|
||||
import { getCategoryIfMember, getMenuIfMember } from "../lib/access.js";
|
||||
import { supabase } from "../lib/supabase.js";
|
||||
import { translateMenu } from "../lib/translator.js";
|
||||
|
||||
// New categories/items are translated into whatever languages the
|
||||
// restaurant already has enabled beyond Turkish (bulk-translated once when
|
||||
// a language is first added — see routes/languages.ts).
|
||||
async function translateNewCategoryInBackground(restaurantId: string, categoryId: string, name: string) {
|
||||
if (!supabase) return;
|
||||
const { data: restaurant } = await supabase
|
||||
.from("restaurants")
|
||||
.select("enabled_languages")
|
||||
.eq("id", restaurantId)
|
||||
.single();
|
||||
const extraLanguages = (restaurant?.enabled_languages ?? []).filter((l: string) => l !== "tr");
|
||||
if (extraLanguages.length === 0) return;
|
||||
|
||||
const translations: Record<string, { name: string }> = {};
|
||||
for (const lang of extraLanguages) {
|
||||
try {
|
||||
const [translated] = await translateMenu([{ id: categoryId, name, items: [] }], lang);
|
||||
if (translated) translations[lang] = { name: translated.name };
|
||||
} catch (err) {
|
||||
console.warn(`Category translation to ${lang} failed:`, err);
|
||||
}
|
||||
}
|
||||
if (Object.keys(translations).length > 0) {
|
||||
await supabase.from("menu_categories").update({ translations }).eq("id", categoryId);
|
||||
}
|
||||
}
|
||||
|
||||
const createCategorySchema = z.object({
|
||||
name: z.string().min(1).max(120),
|
||||
@@ -47,6 +75,8 @@ export const menuCategoriesRoutes: FastifyPluginAsync = async (app) => {
|
||||
return reply.code(500).send({ message: "failed to create category" });
|
||||
}
|
||||
|
||||
void translateNewCategoryInBackground(access.restaurantId, data.id, data.name);
|
||||
|
||||
return reply.code(201).send(data);
|
||||
});
|
||||
|
||||
|
||||
@@ -3,6 +3,40 @@ import { z } from "zod";
|
||||
import { requireAuth } from "../lib/auth.js";
|
||||
import { getCategoryIfMember, getItemIfMember } from "../lib/access.js";
|
||||
import { supabase } from "../lib/supabase.js";
|
||||
import { translateMenu } from "../lib/translator.js";
|
||||
|
||||
async function translateNewItemInBackground(
|
||||
restaurantId: string,
|
||||
itemId: string,
|
||||
name: string,
|
||||
description: string | null,
|
||||
) {
|
||||
if (!supabase) return;
|
||||
const { data: restaurant } = await supabase
|
||||
.from("restaurants")
|
||||
.select("enabled_languages")
|
||||
.eq("id", restaurantId)
|
||||
.single();
|
||||
const extraLanguages = (restaurant?.enabled_languages ?? []).filter((l: string) => l !== "tr");
|
||||
if (extraLanguages.length === 0) return;
|
||||
|
||||
const translations: Record<string, { name: string; description: string | null }> = {};
|
||||
for (const lang of extraLanguages) {
|
||||
try {
|
||||
const [translatedCat] = await translateMenu(
|
||||
[{ id: "single", name: "-", items: [{ id: itemId, name, description }] }],
|
||||
lang,
|
||||
);
|
||||
const translatedItem = translatedCat?.items[0];
|
||||
if (translatedItem) translations[lang] = { name: translatedItem.name, description: translatedItem.description };
|
||||
} catch (err) {
|
||||
console.warn(`Item translation to ${lang} failed:`, err);
|
||||
}
|
||||
}
|
||||
if (Object.keys(translations).length > 0) {
|
||||
await supabase.from("menu_items").update({ translations }).eq("id", itemId);
|
||||
}
|
||||
}
|
||||
|
||||
const createItemSchema = z.object({
|
||||
name: z.string().min(1).max(160),
|
||||
@@ -53,6 +87,8 @@ export const menuItemsRoutes: FastifyPluginAsync = async (app) => {
|
||||
return reply.code(500).send({ message: "failed to create item" });
|
||||
}
|
||||
|
||||
void translateNewItemInBackground(access.restaurantId, data.id, data.name, data.description);
|
||||
|
||||
return reply.code(201).send(data);
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
import type { FastifyInstance } from "fastify";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { type QrStandPreset } from "@menulio/shared";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const DATA_FILE = path.resolve(__dirname, "../data/qr-templates.json");
|
||||
|
||||
export interface DynamicQrTemplate extends QrStandPreset {
|
||||
id: string;
|
||||
isActive: boolean;
|
||||
bgType?: "gradient" | "image" | "svg";
|
||||
bgImageUrl?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
defaultQrConfig?: {
|
||||
xPercent: number;
|
||||
yPercent: number;
|
||||
size: number;
|
||||
borderRadius: number;
|
||||
borderColor: string;
|
||||
bgBoxColor: string;
|
||||
};
|
||||
defaultLayers?: Array<{
|
||||
id: string;
|
||||
text: string;
|
||||
xPercent?: number;
|
||||
yPercent: number;
|
||||
fontSize: number;
|
||||
fontFamily?: string;
|
||||
color: string;
|
||||
fontWeight?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
async function loadTemplatesFromDisk(): Promise<DynamicQrTemplate[]> {
|
||||
try {
|
||||
const content = await fs.readFile(DATA_FILE, "utf-8");
|
||||
const parsed = JSON.parse(content);
|
||||
return parsed.map((p: any) => ({
|
||||
...p,
|
||||
id: p.key,
|
||||
isActive: p.isActive ?? true,
|
||||
createdAt: p.createdAt || new Date().toISOString(),
|
||||
updatedAt: p.updatedAt || new Date().toISOString(),
|
||||
defaultQrConfig: p.defaultQrConfig || p.qrConfig,
|
||||
defaultLayers: p.defaultLayers || p.layers,
|
||||
}));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function saveTemplatesToDisk(templates: DynamicQrTemplate[]): Promise<void> {
|
||||
try {
|
||||
await fs.writeFile(DATA_FILE, JSON.stringify(templates, null, 2), "utf-8");
|
||||
} catch (err) {
|
||||
console.error("Failed to write qr-templates to disk:", err);
|
||||
}
|
||||
}
|
||||
|
||||
export async function qrTemplatesRoutes(app: FastifyInstance) {
|
||||
// 1. Tüm Aktif Şablonları Listele (Mobil ve Web İçin)
|
||||
app.get("/qr-templates", async () => {
|
||||
const list = await loadTemplatesFromDisk();
|
||||
return {
|
||||
templates: list.filter((t) => t.isActive),
|
||||
total: list.length,
|
||||
};
|
||||
});
|
||||
|
||||
// 2. Admin Şablon Listesi (Aktif + Taslak hepsi)
|
||||
app.get("/admin/qr-templates", async () => {
|
||||
const list = await loadTemplatesFromDisk();
|
||||
return {
|
||||
templates: list,
|
||||
total: list.length,
|
||||
};
|
||||
});
|
||||
|
||||
// 3. Yeni Şablon Ekle veya Mevcut Olanı Güncelle
|
||||
app.post<{ Body: Partial<DynamicQrTemplate> }>("/admin/qr-templates", async (req, reply) => {
|
||||
const body = req.body;
|
||||
if (!body.name) {
|
||||
return reply.status(400).send({ error: "Şablon adı zorunludur." });
|
||||
}
|
||||
|
||||
const key = body.key || `qr-${Date.now()}`;
|
||||
const list = await loadTemplatesFromDisk();
|
||||
const existingIndex = list.findIndex((t) => t.id === key || t.key === key);
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const newTemplate: DynamicQrTemplate = {
|
||||
key,
|
||||
id: key,
|
||||
name: body.name,
|
||||
categoryName: body.categoryName || "Özel Şablon",
|
||||
svgTemplateUrl: body.svgTemplateUrl || `/templates/${key}.svg`,
|
||||
aspectRatio: body.aspectRatio || "1/1",
|
||||
bgType: body.bgType || "gradient",
|
||||
bgImageUrl: body.bgImageUrl,
|
||||
bgGradient: body.bgGradient || "radial-gradient(circle at 50% 50%, #132034 0%, #0b0d13 100%)",
|
||||
plaqueColor: body.plaqueColor || "#132034",
|
||||
borderAccent: body.borderAccent || "#D4AF37",
|
||||
accentText: body.accentText || "#D4AF37",
|
||||
headerText: body.headerText || "#FFFFFF",
|
||||
subText: body.subText || "#E5C378",
|
||||
dividerColor: body.dividerColor || "rgba(212, 175, 55, 0.3)",
|
||||
isActive: body.isActive !== undefined ? body.isActive : true,
|
||||
createdAt: existingIndex >= 0 ? list[existingIndex].createdAt : now,
|
||||
updatedAt: now,
|
||||
defaultQrConfig: body.defaultQrConfig || (body as any).qrConfig || {
|
||||
xPercent: 50,
|
||||
yPercent: 38,
|
||||
size: 115,
|
||||
borderRadius: 14,
|
||||
borderColor: body.borderAccent || "#D4AF37",
|
||||
bgBoxColor: "#FFFFFF",
|
||||
},
|
||||
defaultLayers: body.defaultLayers || (body as any).layers || [
|
||||
{
|
||||
id: "rName",
|
||||
text: "{{RESTAURANT_NAME}}",
|
||||
yPercent: 16,
|
||||
fontSize: 18,
|
||||
color: body.accentText || "#D4AF37",
|
||||
fontWeight: "800",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
list[existingIndex] = newTemplate;
|
||||
} else {
|
||||
list.unshift(newTemplate);
|
||||
}
|
||||
|
||||
await saveTemplatesToDisk(list);
|
||||
return { success: true, template: newTemplate };
|
||||
});
|
||||
|
||||
// 4. Şablon Sil
|
||||
app.delete<{ Params: { id: string } }>("/admin/qr-templates/:id", async (req, reply) => {
|
||||
const { id } = req.params;
|
||||
const list = await loadTemplatesFromDisk();
|
||||
const filtered = list.filter((t) => t.id !== id && t.key !== id);
|
||||
|
||||
if (filtered.length === list.length) {
|
||||
return reply.status(404).send({ error: "Şablon bulunamadı." });
|
||||
}
|
||||
|
||||
await saveTemplatesToDisk(filtered);
|
||||
return { success: true, deletedId: id };
|
||||
});
|
||||
}
|
||||
@@ -16,6 +16,7 @@ const createRestaurantSchema = z.object({
|
||||
const updateRestaurantSchema = z.object({
|
||||
name: z.string().min(1).max(120).optional(),
|
||||
logoUrl: z.string().nullish(),
|
||||
coverUrl: z.string().nullish(),
|
||||
phone: z.string().max(30).nullish(),
|
||||
address: z.string().max(500).nullish(),
|
||||
slug: z.string().min(2).max(100).optional(),
|
||||
@@ -108,7 +109,7 @@ export const restaurantsRoutes: FastifyPluginAsync = async (app) => {
|
||||
return reply.code(400).send({ message: "invalid body", issues: parsed.error.issues });
|
||||
}
|
||||
|
||||
const { name, logoUrl, phone, address, slug } = parsed.data;
|
||||
const { name, logoUrl, coverUrl, phone, address, slug } = parsed.data;
|
||||
|
||||
let formattedSlug: string | undefined = undefined;
|
||||
if (slug !== undefined) {
|
||||
@@ -145,6 +146,7 @@ export const restaurantsRoutes: FastifyPluginAsync = async (app) => {
|
||||
.update({
|
||||
...(name !== undefined ? { name } : {}),
|
||||
...(logoUrl !== undefined ? { logo_url: logoUrl } : {}),
|
||||
...(coverUrl !== undefined ? { cover_url: coverUrl } : {}),
|
||||
...(phone !== undefined ? { phone } : {}),
|
||||
...(address !== undefined ? { address } : {}),
|
||||
...(formattedSlug !== undefined ? { slug: formattedSlug } : {}),
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/icon.png",
|
||||
"userInterfaceStyle": "automatic",
|
||||
"newArchEnabled": false,
|
||||
"newArchEnabled": true,
|
||||
"platforms": ["ios", "android"],
|
||||
"splash": {
|
||||
"image": "./assets/splash.png",
|
||||
"resizeMode": "contain",
|
||||
@@ -17,6 +18,7 @@
|
||||
"ios": {
|
||||
"supportsTablet": false,
|
||||
"bundleIdentifier": "com.ayristech.menulio",
|
||||
"buildNumber": "21",
|
||||
"icon": "./assets/icon.png",
|
||||
"infoPlist": {
|
||||
"NSCameraUsageDescription": "Menünüzün fotoğrafını çekerek AI ile dijitalleştirmek için kamera izni gereklidir.",
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 357 KiB After Width: | Height: | Size: 4.5 KiB |
@@ -0,0 +1,71 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 650" width="400" height="650">
|
||||
<defs>
|
||||
<!-- Define a linear gradient for gold effect -->
|
||||
<linearGradient id="goldGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#D4AF37"/>
|
||||
<stop offset="100%" stop-color="#FFD700"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Background Canvas -->
|
||||
<rect id="bg-canvas" fill="#451A03" width="400" height="650"/>
|
||||
<!-- Main Menu Card Panel -->
|
||||
<rect id="main-card" x="20" y="20" width="360" height="610" rx="24" fill="#000000" stroke="url(#goldGradient)" stroke-width="2"/>
|
||||
<!-- Layer for Icons/Graphics -->
|
||||
<g id="layer-icons">
|
||||
<!-- Top decorative line -->
|
||||
<line x1="80" y1="165" x2="320" y2="165" stroke="url(#goldGradient)" stroke-width="1" fill="#D97706"/>
|
||||
<!-- Bottom decorative line -->
|
||||
<line x1="80" y1="535" x2="320" y2="535" stroke="url(#goldGradient)" stroke-width="1" fill="#B45309"/>
|
||||
<!-- Subtle corner accents (L-shapes) -->
|
||||
<path d="M40 40 L40 70 M40 40 L70 40" stroke="url(#goldGradient)" stroke-width="1.5" stroke-linecap="round" fill="#D97706"/>
|
||||
<path d="M360 40 L360 70 M360 40 L330 40" stroke="url(#goldGradient)" stroke-width="1.5" stroke-linecap="round" fill="#B45309"/>
|
||||
<path d="M40 610 L40 580 M40 610 L70 610" stroke="url(#goldGradient)" stroke-width="1.5" stroke-linecap="round" fill="#D97706"/>
|
||||
<path d="M360 610 L360 580 M360 610 L330 610" stroke="url(#goldGradient)" stroke-width="1.5" stroke-linecap="round" fill="#B45309"/>
|
||||
<!-- Minimalist Fork and Knife Line Art -->
|
||||
<g transform="translate(0, 15)">
|
||||
<!-- Fork -->
|
||||
<path d="M185 550 L185 560 M188 550 L188 560 M191 550 L191 560 M188 560 L188 565" stroke="url(#goldGradient)" stroke-width="1.5" stroke-linecap="round" fill="#D97706"/>
|
||||
<!-- Knife -->
|
||||
<path d="M205 550 L205 560 L208 560 L208 552 C208 550 206.5 549 205 549 Z M206.5 560 L206.5 565" stroke="url(#goldGradient)" stroke-width="1.5" stroke-linecap="round" fill="#B45309"/>
|
||||
</g>
|
||||
</g>
|
||||
<!-- QR Placeholder -->
|
||||
<g id="qr-placeholder" transform="translate(90, 215)">
|
||||
<rect width="220" height="220" rx="20" fill="#FFFFFF" stroke="#E4E4E7" stroke-width="2"/>
|
||||
<rect x="12" y="12" width="196" height="196" rx="14" fill="#FAFAFA" stroke="#E4E4E7" stroke-dasharray="4 3"/>
|
||||
<text x="110" y="115" font-family="Inter, sans-serif" font-size="12" font-weight="600" fill="#71717A" text-anchor="middle">
|
||||
QR KOD ALANI
|
||||
</text>
|
||||
</g>
|
||||
<!-- Layer for Texts -->
|
||||
<g id="layer-texts" fill="#c10101">
|
||||
<!-- Table Info badge -->
|
||||
<rect x="140" y="40" width="120" height="30" rx="15" fill="url(#goldGradient)"/>
|
||||
<text x="200" y="60" font-family="Inter, sans-serif" font-size="14" font-weight="600" fill="#1A1A1D" text-anchor="middle">
|
||||
MASA NO: 01
|
||||
</text>
|
||||
<!-- Restaurant Name -->
|
||||
<text x="200" y="120" font-family="Inter, sans-serif" font-size="26" font-weight="700" fill="url(#goldGradient)" text-anchor="middle">
|
||||
Gourmet Bistro
|
||||
</text>
|
||||
<!-- Tagline / Subtitle -->
|
||||
<text x="200" y="145" font-family="Inter, sans-serif" font-size="12" font-weight="400" fill="#E4E4E7" text-anchor="middle">
|
||||
Temassız Dijital Menü
|
||||
</text>
|
||||
<!-- "MENÜYÜ GÖRMEK İÇİN OKUTUN" -->
|
||||
<text x="200" y="480" font-family="Inter, sans-serif" font-size="16" font-weight="700" fill="url(#goldGradient)" text-anchor="middle">
|
||||
MENÜYÜ GÖRMEK İÇİN OKUTUN
|
||||
</text>
|
||||
<!-- Instruction note -->
|
||||
<text x="200" y="505" font-family="Inter, sans-serif" font-size="11" font-weight="400" fill="#A1A1AA" text-anchor="middle">
|
||||
Kameranızı QR Koda Odaklayın
|
||||
</text>
|
||||
<!-- WiFi & Social Media footer info -->
|
||||
<text x="200" y="595" font-family="Inter, sans-serif" font-size="11" font-weight="400" fill="#A1A1AA" text-anchor="middle">
|
||||
Ücretsiz Wi-Fi: GourmetBistro_Guest
|
||||
</text>
|
||||
<text x="200" y="610" font-family="Inter, sans-serif" font-size="11" font-weight="400" fill="#A1A1AA" text-anchor="middle">
|
||||
Bizi Takip Edin: @GourmetBistro
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1,285 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 400 650"
|
||||
width="400"
|
||||
height="650"
|
||||
>
|
||||
<g id="layer-background">
|
||||
<rect
|
||||
id="bg-canvas"
|
||||
x="0"
|
||||
y="0"
|
||||
width="400"
|
||||
height="650"
|
||||
fill="#0B0E14"
|
||||
/>
|
||||
</g>
|
||||
|
||||
<g id="layer-card">
|
||||
<!-- Ana Lüks Kart Arka Planı (Gece Yarısı Laciverti & Kömür) -->
|
||||
<rect
|
||||
id="main-card"
|
||||
x="20"
|
||||
y="20"
|
||||
width="360"
|
||||
height="610"
|
||||
rx="24"
|
||||
fill="#121721"
|
||||
stroke="#D4AF37"
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
|
||||
<!-- İç Çift Altın Bordür Hattı -->
|
||||
<rect
|
||||
id="inner-border"
|
||||
x="28"
|
||||
y="28"
|
||||
width="344"
|
||||
height="594"
|
||||
rx="18"
|
||||
fill="none"
|
||||
stroke="#C5A059"
|
||||
stroke-width="0.8"
|
||||
stroke-opacity="0.4"
|
||||
/>
|
||||
<rect
|
||||
x="32"
|
||||
y="32"
|
||||
width="336"
|
||||
height="586"
|
||||
rx="14"
|
||||
fill="none"
|
||||
stroke="#C5A059"
|
||||
stroke-width="0.5"
|
||||
stroke-opacity="0.2"
|
||||
/>
|
||||
</g>
|
||||
|
||||
<g id="layer-icons">
|
||||
<!-- Üst Köşe Geometrik Art-Deco Motifleri -->
|
||||
<path
|
||||
d="M 38 48 L 48 38 M 38 38 L 52 38 M 38 38 L 38 52"
|
||||
stroke="#D4AF37"
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
stroke-opacity="0.6"
|
||||
fill="none"
|
||||
/>
|
||||
<path
|
||||
d="M 362 48 L 352 38 M 362 38 L 348 38 M 362 38 L 362 52"
|
||||
stroke="#D4AF37"
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
stroke-opacity="0.6"
|
||||
fill="none"
|
||||
/>
|
||||
|
||||
<!-- Üst Masa Numarası Kapsülü -->
|
||||
<rect
|
||||
x="148"
|
||||
y="44"
|
||||
width="104"
|
||||
height="22"
|
||||
rx="11"
|
||||
fill="#1A2230"
|
||||
stroke="#D4AF37"
|
||||
stroke-width="0.8"
|
||||
/>
|
||||
<circle cx="158" cy="55" r="1.5" fill="#D4AF37" />
|
||||
<circle cx="242" cy="55" r="1.5" fill="#D4AF37" />
|
||||
|
||||
<!-- Merkezi Gastronomi & Yıldız Amblemi -->
|
||||
<g transform="translate(200, 84)">
|
||||
<circle cx="0" cy="0" r="11" fill="none" stroke="#D4AF37" stroke-width="0.8" stroke-dasharray="2 2" />
|
||||
<polygon points="0,-7 2,-2 7,0 2,2 0,7 -2,2 -7,0 -2,-2" fill="#D4AF37" />
|
||||
</g>
|
||||
|
||||
<!-- Zarif Tipografik Ayırıcı -->
|
||||
<line x1="75" y1="172" x2="175" y2="172" stroke="#D4AF37" stroke-width="0.75" stroke-opacity="0.5" />
|
||||
<polygon points="200,169.5 202.5,172 200,174.5 197.5,172" fill="#D4AF37" />
|
||||
<line x1="225" y1="172" x2="325" y2="172" stroke="#D4AF37" stroke-width="0.75" stroke-opacity="0.5" />
|
||||
|
||||
<!-- QR Alanı Hedef Odak Çerçeveleri -->
|
||||
<path d="M 80 205 L 80 195 L 90 195" fill="none" stroke="#D4AF37" stroke-width="2" stroke-linecap="round" />
|
||||
<path d="M 320 205 L 320 195 L 310 195" fill="none" stroke="#D4AF37" stroke-width="2" stroke-linecap="round" />
|
||||
<path d="M 80 445 L 80 455 L 90 455" fill="none" stroke="#D4AF37" stroke-width="2" stroke-linecap="round" />
|
||||
<path d="M 320 445 L 320 455 L 310 455" fill="none" stroke="#D4AF37" stroke-width="2" stroke-linecap="round" />
|
||||
|
||||
<!-- Alt Bilgi Kartı Paneli -->
|
||||
<rect
|
||||
x="36"
|
||||
y="520"
|
||||
width="328"
|
||||
height="84"
|
||||
rx="14"
|
||||
fill="#171F2C"
|
||||
stroke="#D4AF37"
|
||||
stroke-width="0.8"
|
||||
stroke-opacity="0.4"
|
||||
/>
|
||||
<line x1="200" y1="530" x2="200" y2="594" stroke="#D4AF37" stroke-width="0.5" stroke-opacity="0.25" />
|
||||
|
||||
<!-- Minimal WiFi Vektörü -->
|
||||
<g transform="translate(62, 545)">
|
||||
<path d="M 0 4 A 12 12 0 0 1 18 4" fill="none" stroke="#D4AF37" stroke-width="1.4" stroke-linecap="round" />
|
||||
<path d="M 3.5 8 A 7 7 0 0 1 14.5 8" fill="none" stroke="#D4AF37" stroke-width="1.4" stroke-linecap="round" />
|
||||
<circle cx="9" cy="12" r="1.2" fill="#D4AF37" />
|
||||
</g>
|
||||
|
||||
<!-- Minimal Instagram / Web Vektörü -->
|
||||
<g transform="translate(224, 545)">
|
||||
<rect x="0" y="0" width="15" height="15" rx="4" fill="none" stroke="#D4AF37" stroke-width="1.4" />
|
||||
<circle cx="7.5" cy="7.5" r="3.2" fill="none" stroke="#D4AF37" stroke-width="1.1" />
|
||||
<circle cx="11.5" cy="3.5" r="0.75" fill="#D4AF37" />
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<g id="qr-placeholder" transform="translate(90, 215)">
|
||||
<rect
|
||||
width="220"
|
||||
height="220"
|
||||
rx="20"
|
||||
fill="#FFFFFF"
|
||||
stroke="#E4E4E7"
|
||||
stroke-width="2"
|
||||
/>
|
||||
|
||||
<rect
|
||||
x="12"
|
||||
y="12"
|
||||
width="196"
|
||||
height="196"
|
||||
rx="14"
|
||||
fill="#FAFAFA"
|
||||
stroke="#E4E4E7"
|
||||
stroke-dasharray="4 3"
|
||||
/>
|
||||
|
||||
<text
|
||||
x="110"
|
||||
y="115"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="12"
|
||||
font-weight="600"
|
||||
fill="#71717A"
|
||||
text-anchor="middle"
|
||||
>QR KOD ALANI</text>
|
||||
</g>
|
||||
|
||||
<g id="layer-texts">
|
||||
<!-- Masa Numarası -->
|
||||
<text
|
||||
x="200"
|
||||
y="59"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="9"
|
||||
font-weight="700"
|
||||
letter-spacing="2.5"
|
||||
fill="#E5C378"
|
||||
text-anchor="middle"
|
||||
>TABLE • 01</text>
|
||||
|
||||
<!-- Restoran Logosu & İsmi -->
|
||||
<text
|
||||
x="200"
|
||||
y="126"
|
||||
font-family="'Playfair Display', 'Cinzel', Georgia, serif"
|
||||
font-size="28"
|
||||
font-weight="700"
|
||||
letter-spacing="1.5"
|
||||
fill="#F9F6F0"
|
||||
text-anchor="middle"
|
||||
>L’ATELIER BISTRO</text>
|
||||
|
||||
<!-- Fransızca / Uluslararası Alt Başlık -->
|
||||
<text
|
||||
x="200"
|
||||
y="152"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="8.5"
|
||||
font-weight="600"
|
||||
letter-spacing="4"
|
||||
fill="#D4AF37"
|
||||
text-anchor="middle"
|
||||
>FINE DINING & COCKTAILS</text>
|
||||
|
||||
<!-- Çağrı Metni / Talimat -->
|
||||
<text
|
||||
x="200"
|
||||
y="480"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="11.5"
|
||||
font-weight="700"
|
||||
letter-spacing="2"
|
||||
fill="#F9F6F0"
|
||||
text-anchor="middle"
|
||||
>DİJİTAL MENÜ İÇİN OKUTUN</text>
|
||||
|
||||
<text
|
||||
x="200"
|
||||
y="498"
|
||||
font-family="'Playfair Display', Georgia, serif"
|
||||
font-size="10.5"
|
||||
font-style="italic"
|
||||
font-weight="400"
|
||||
letter-spacing="0.5"
|
||||
fill="#94A3B8"
|
||||
text-anchor="middle"
|
||||
>Kameranızı QR koda yaklaştırınız</text>
|
||||
|
||||
<!-- Sol Sütun: Yüksek Hızlı WiFi Bilgileri -->
|
||||
<text
|
||||
x="126"
|
||||
y="550"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="9"
|
||||
font-weight="700"
|
||||
letter-spacing="1.2"
|
||||
fill="#E5C378"
|
||||
text-anchor="middle"
|
||||
>GUEST WIFI</text>
|
||||
|
||||
<text
|
||||
x="126"
|
||||
y="567"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="8.5"
|
||||
font-weight="500"
|
||||
fill="#CBD5E1"
|
||||
text-anchor="middle"
|
||||
>Ağ: Atelier-Guest</text>
|
||||
|
||||
<text
|
||||
x="126"
|
||||
y="582"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="8.5"
|
||||
font-weight="500"
|
||||
fill="#CBD5E1"
|
||||
text-anchor="middle"
|
||||
>Şifre: bistro2026</text>
|
||||
|
||||
<!-- Sağ Sütun: Sosyal Medya & Rezervasyon -->
|
||||
<text
|
||||
x="286"
|
||||
y="550"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="8.5"
|
||||
font-weight="700"
|
||||
letter-spacing="1.2"
|
||||
fill="#E5C378"
|
||||
text-anchor="middle"
|
||||
>SOSYAL MEDYA</text>
|
||||
|
||||
<text
|
||||
x="286"
|
||||
y="575"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="10.5"
|
||||
font-weight="600"
|
||||
letter-spacing="0.5"
|
||||
fill="#F9F6F0"
|
||||
text-anchor="middle"
|
||||
>@latelier.bistro</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.1 KiB |
@@ -0,0 +1,275 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 400 650"
|
||||
width="400"
|
||||
height="650"
|
||||
>
|
||||
<g id="layer-background">
|
||||
<!-- Koyu Doku / Kontrast Arka Plan -->
|
||||
<rect
|
||||
id="bg-canvas"
|
||||
x="0"
|
||||
y="0"
|
||||
width="400"
|
||||
height="650"
|
||||
fill="#252C36"
|
||||
/>
|
||||
</g>
|
||||
|
||||
<g id="layer-card">
|
||||
<!-- Sıcak Parşömen / Vintage Krem Ana Kart -->
|
||||
<rect
|
||||
id="main-card"
|
||||
x="20"
|
||||
y="20"
|
||||
width="360"
|
||||
height="610"
|
||||
rx="20"
|
||||
fill="#F6EFE2"
|
||||
stroke="#3A2312"
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
<!-- Çift İnce Vintage Çerçeve -->
|
||||
<rect
|
||||
x="28"
|
||||
y="28"
|
||||
width="344"
|
||||
height="594"
|
||||
rx="14"
|
||||
fill="none"
|
||||
stroke="#8C6239"
|
||||
stroke-width="1"
|
||||
stroke-opacity="0.4"
|
||||
/>
|
||||
<rect
|
||||
x="32"
|
||||
y="32"
|
||||
width="336"
|
||||
height="586"
|
||||
rx="10"
|
||||
fill="none"
|
||||
stroke="#8C6239"
|
||||
stroke-width="0.5"
|
||||
stroke-opacity="0.25"
|
||||
/>
|
||||
</g>
|
||||
|
||||
<g id="layer-icons">
|
||||
<!-- Sol Üst: Botanik Kahve / Çay Dalı -->
|
||||
<g transform="translate(36, 36) scale(0.75)" stroke="#3A2312" stroke-width="1.4" fill="none" stroke-linecap="round">
|
||||
<path d="M 0 0 C 15 15 25 35 30 65" />
|
||||
<path d="M 10 12 C 5 2 20 -2 22 8 C 22 18 10 12 10 12 Z" fill="#EADCC7" />
|
||||
<path d="M 18 27 C 12 20 28 14 32 23 C 32 32 18 27 18 27 Z" fill="#EADCC7" />
|
||||
<path d="M 24 45 C 32 38 45 48 38 56 C 30 62 24 45 24 45 Z" fill="#EADCC7" />
|
||||
</g>
|
||||
|
||||
<!-- Sağ Üst: Kuşbakışı Latte Art Fincanı -->
|
||||
<g transform="translate(340, 52)">
|
||||
<!-- Tabak -->
|
||||
<circle cx="0" cy="0" r="32" fill="#EADCC7" stroke="#3A2312" stroke-width="1.2" />
|
||||
<circle cx="0" cy="0" r="26" fill="#F6EFE2" stroke="#8C6239" stroke-width="0.8" stroke-dasharray="2 2" />
|
||||
<!-- Fincan Kenarı & Kahve -->
|
||||
<circle cx="0" cy="0" r="21" fill="#7A4B26" stroke="#3A2312" stroke-width="1.4" />
|
||||
<!-- Latte Art / Rosetta Yaprakları -->
|
||||
<path d="M 0 10 C -8 4 -4 -6 0 -12 C 4 -6 8 4 0 10 Z" fill="#F6EFE2" />
|
||||
<path d="M 0 4 C -5 0 -2 -5 0 -8 C 2 -5 5 0 0 4 Z" fill="#7A4B26" />
|
||||
<circle cx="0" cy="-14" r="1.8" fill="#F6EFE2" />
|
||||
</g>
|
||||
|
||||
<!-- Sol Orta: Küçük Kahve Fincanı & Tabağı -->
|
||||
<g transform="translate(48, 250)">
|
||||
<ellipse cx="0" cy="0" rx="26" ry="26" fill="#EADCC7" stroke="#3A2312" stroke-width="1" />
|
||||
<circle cx="0" cy="0" r="17" fill="#6A3B18" stroke="#3A2312" stroke-width="1.2" />
|
||||
<!-- Basit Kalp Latte Deseni -->
|
||||
<path d="M -5 -3 C -8 -7 0 -10 0 -4 C 0 -10 8 -7 5 -3 C 2 3 0 7 0 7 C 0 7 -2 3 -5 -3 Z" fill="#F6EFE2" />
|
||||
<!-- Fincan Kulpu -->
|
||||
<path d="M 17 -4 C 23 -4 23 4 17 4" fill="none" stroke="#3A2312" stroke-width="1.5" />
|
||||
</g>
|
||||
|
||||
<!-- Sağ Orta: Dikey Yaprak İllüstrasyonu -->
|
||||
<g transform="translate(345, 230) scale(0.7)" stroke="#3A2312" stroke-width="1.3" fill="none">
|
||||
<path d="M 10 0 C 0 30 -5 70 5 110" />
|
||||
<path d="M 6 25 C 18 15 28 28 14 36 C 6 36 6 25 6 25 Z" fill="#EADCC7" />
|
||||
<path d="M 2 60 C -12 48 -20 62 -8 70 C 2 70 2 60 2 60 Z" fill="#EADCC7" />
|
||||
<path d="M 5 88 C 18 78 24 92 12 100 C 5 100 5 88 5 88 Z" fill="#EADCC7" />
|
||||
</g>
|
||||
|
||||
<!-- Üst Başlık Yan Süsleme Çizgileri -->
|
||||
<path d="M 142 66 C 148 64 154 68 160 66" fill="none" stroke="#8C6239" stroke-width="1.2" stroke-linecap="round" />
|
||||
<path d="M 240 66 C 246 68 252 64 258 66" fill="none" stroke="#8C6239" stroke-width="1.2" stroke-linecap="round" />
|
||||
|
||||
<!-- Sol Alt: Çekirdek ve Yaprak Grubu -->
|
||||
<g transform="translate(42, 530) scale(0.65)" stroke="#3A2312" stroke-width="1.4" fill="none">
|
||||
<path d="M 0 110 C 20 70 15 30 0 0" />
|
||||
<path d="M 8 30 C 24 15 35 30 18 42 Z" fill="#EADCC7" />
|
||||
<path d="M 12 70 C 30 55 40 72 20 84 Z" fill="#EADCC7" />
|
||||
</g>
|
||||
|
||||
<!-- Kahve Çekirdekleri Motifleri -->
|
||||
<!-- Sol Alt Çekirdek 1 -->
|
||||
<g transform="translate(108, 595) rotate(-20) scale(0.8)">
|
||||
<ellipse cx="0" cy="0" rx="9" ry="6.5" fill="#CBB296" stroke="#3A2312" stroke-width="1.2" />
|
||||
<path d="M -7 0 Q 0 3 0 0 Q 0 -3 7 0" fill="none" stroke="#3A2312" stroke-width="1.2" />
|
||||
</g>
|
||||
<!-- Sol Alt Çekirdek 2 -->
|
||||
<g transform="translate(128, 584) rotate(35) scale(0.8)">
|
||||
<ellipse cx="0" cy="0" rx="9" ry="6.5" fill="#CBB296" stroke="#3A2312" stroke-width="1.2" />
|
||||
<path d="M -7 0 Q 0 3 0 0 Q 0 -3 7 0" fill="none" stroke="#3A2312" stroke-width="1.2" />
|
||||
</g>
|
||||
<!-- Sağ Orta Çekirdek -->
|
||||
<g transform="translate(336, 360) rotate(-45) scale(0.7)">
|
||||
<ellipse cx="0" cy="0" rx="9" ry="6.5" fill="#CBB296" stroke="#3A2312" stroke-width="1.2" />
|
||||
<path d="M -7 0 Q 0 3 0 0 Q 0 -3 7 0" fill="none" stroke="#3A2312" stroke-width="1.2" />
|
||||
</g>
|
||||
|
||||
<!-- Sağ Alt: Sıcak Kahve Fincanı İllüstrasyonu -->
|
||||
<g transform="translate(325, 590)">
|
||||
<!-- Tabak -->
|
||||
<ellipse cx="0" cy="18" rx="28" ry="7" fill="#EADCC7" stroke="#3A2312" stroke-width="1.2" />
|
||||
<!-- Fincan Gövdesi -->
|
||||
<path d="M -18 2 C -18 16 18 16 18 2 Z" fill="#F6EFE2" stroke="#3A2312" stroke-width="1.4" />
|
||||
<ellipse cx="0" cy="2" rx="18" ry="4" fill="#6A3B18" stroke="#3A2312" stroke-width="1.2" />
|
||||
<!-- Kulp -->
|
||||
<path d="M 16 4 C 24 4 24 12 16 14" fill="none" stroke="#3A2312" stroke-width="1.3" />
|
||||
<!-- Buhar Çizgileri -->
|
||||
<path d="M -6 -4 C -8 -9 -4 -12 -6 -17" fill="none" stroke="#8C6239" stroke-width="1" stroke-linecap="round" />
|
||||
<path d="M 2 -5 C 0 -10 4 -13 2 -19" fill="none" stroke="#8C6239" stroke-width="1" stroke-linecap="round" />
|
||||
</g>
|
||||
|
||||
<!-- QR Alanı İnce Köşe Süsleri -->
|
||||
<path d="M 80 205 C 80 195 80 195 90 195" fill="none" stroke="#3A2312" stroke-width="2" stroke-linecap="round" />
|
||||
<path d="M 320 205 C 320 195 320 195 310 195" fill="none" stroke="#3A2312" stroke-width="2" stroke-linecap="round" />
|
||||
<path d="M 80 445 C 80 455 80 455 90 455" fill="none" stroke="#3A2312" stroke-width="2" stroke-linecap="round" />
|
||||
<path d="M 320 445 C 320 455 320 455 310 455" fill="none" stroke="#3A2312" stroke-width="2" stroke-linecap="round" />
|
||||
|
||||
<!-- Alt Est. Bölümü Çizgileri -->
|
||||
<line x1="145" y1="608" x2="175" y2="608" stroke="#8C6239" stroke-width="0.8" />
|
||||
<line x1="225" y1="608" x2="255" y2="608" stroke="#8C6239" stroke-width="0.8" />
|
||||
</g>
|
||||
|
||||
<g id="qr-placeholder" transform="translate(90, 215)">
|
||||
<!-- QR Kod Arka Planı (Hafif Sıcak Ton) -->
|
||||
<rect
|
||||
width="220"
|
||||
height="220"
|
||||
rx="20"
|
||||
fill="#FBF8F2"
|
||||
stroke="#D8C7B0"
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
<rect
|
||||
x="10"
|
||||
y="10"
|
||||
width="200"
|
||||
height="200"
|
||||
rx="14"
|
||||
fill="#F5EFE6"
|
||||
stroke="#D8C7B0"
|
||||
stroke-dasharray="4 3"
|
||||
/>
|
||||
<!-- QR Merkezine Kahve Çekirdeği İkonu Rozeti -->
|
||||
<circle cx="110" cy="98" r="18" fill="#FBF8F2" stroke="#8C6239" stroke-width="1" />
|
||||
<g transform="translate(110, 98) scale(0.65)">
|
||||
<ellipse cx="-5" cy="2" rx="7" ry="5" fill="#3A2312" transform="rotate(-30)" />
|
||||
<ellipse cx="5" cy="0" rx="7" ry="5" fill="#3A2312" transform="rotate(40)" />
|
||||
</g>
|
||||
<text
|
||||
x="110"
|
||||
y="132"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="11"
|
||||
font-weight="700"
|
||||
letter-spacing="1"
|
||||
fill="#8C6239"
|
||||
text-anchor="middle"
|
||||
>QR KOD ALANI</text>
|
||||
</g>
|
||||
|
||||
<g id="layer-texts">
|
||||
<!-- Üst Başlık Takısı -->
|
||||
<text
|
||||
x="200"
|
||||
y="70"
|
||||
font-family="'Playfair Display', Georgia, serif"
|
||||
font-size="14"
|
||||
font-style="italic"
|
||||
font-weight="700"
|
||||
letter-spacing="4"
|
||||
fill="#3A2312"
|
||||
text-anchor="middle"
|
||||
>THE</text>
|
||||
|
||||
<!-- Ana Tipografi: El Yazısı / Retro Tabela Havası -->
|
||||
<text
|
||||
x="200"
|
||||
y="114"
|
||||
font-family="'Playfair Display', 'Brush Script MT', 'Great Vibes', Georgia, cursive, serif"
|
||||
font-size="34"
|
||||
font-weight="800"
|
||||
letter-spacing="0.5"
|
||||
fill="#2C1A0E"
|
||||
text-anchor="middle"
|
||||
>The Daily Grind</text>
|
||||
|
||||
<!-- Alt Kategori Başlığı -->
|
||||
<text
|
||||
x="200"
|
||||
y="146"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="13"
|
||||
font-weight="800"
|
||||
letter-spacing="3"
|
||||
fill="#3A2312"
|
||||
text-anchor="middle"
|
||||
>COFFEE & KITCHEN</text>
|
||||
|
||||
<!-- Büyük ve Belirgin Çağrı Metni -->
|
||||
<text
|
||||
x="200"
|
||||
y="488"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="19"
|
||||
font-weight="900"
|
||||
letter-spacing="1.5"
|
||||
fill="#2C1A0E"
|
||||
text-anchor="middle"
|
||||
>SCAN TO VIEW MENU</text>
|
||||
|
||||
<!-- Restoran & WiFi Bilgi Bandı -->
|
||||
<text
|
||||
x="200"
|
||||
y="520"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="10.5"
|
||||
font-weight="700"
|
||||
letter-spacing="0.5"
|
||||
fill="#3A2312"
|
||||
text-anchor="middle"
|
||||
>DAILY GRIND CAFE | WIFI: DAILY_GRIND_GUEST</text>
|
||||
|
||||
<!-- Şifre ve Masa Detayı -->
|
||||
<text
|
||||
x="200"
|
||||
y="542"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="9"
|
||||
font-weight="600"
|
||||
letter-spacing="1"
|
||||
fill="#8C6239"
|
||||
text-anchor="middle"
|
||||
>ŞİFRE: coffee2026 • MASA NO: 04</text>
|
||||
|
||||
<!-- Kuruluş / Est. Yılı -->
|
||||
<text
|
||||
x="200"
|
||||
y="611"
|
||||
font-family="'Playfair Display', Georgia, serif"
|
||||
font-size="11"
|
||||
font-style="italic"
|
||||
font-weight="700"
|
||||
letter-spacing="2"
|
||||
fill="#3A2312"
|
||||
text-anchor="middle"
|
||||
>EST. 2018</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 278 KiB |
|
After Width: | Height: | Size: 666 KiB |
|
After Width: | Height: | Size: 516 KiB |
@@ -15,6 +15,13 @@
|
||||
"distribution": "internal",
|
||||
"ios": {
|
||||
"image": "latest"
|
||||
},
|
||||
"env": {
|
||||
"EXPO_PUBLIC_SUPABASE_URL": "https://fwahdopsyvryvjaqande.supabase.co",
|
||||
"EXPO_PUBLIC_SUPABASE_ANON_KEY": "sb_publishable_9YsK7YU7xfzxJQsz3x5iMA_LHCqUN3b",
|
||||
"EXPO_PUBLIC_API_URL": "https://api.menul.io",
|
||||
"EXPO_PUBLIC_WEB_URL": "https://menul.io",
|
||||
"EXPO_PUBLIC_REVENUECAT_API_KEY": "appl_uQIvGUuvEthCNheZtcRfVixVhFy"
|
||||
}
|
||||
},
|
||||
"production": {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# OSX
|
||||
#
|
||||
.DS_Store
|
||||
|
||||
# Xcode
|
||||
#
|
||||
build/
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata
|
||||
*.xccheckout
|
||||
*.moved-aside
|
||||
DerivedData
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.xcuserstate
|
||||
project.xcworkspace
|
||||
.xcode.env.local
|
||||
|
||||
# Bundle artifacts
|
||||
*.jsbundle
|
||||
|
||||
# CocoaPods
|
||||
/Pods/
|
||||
@@ -0,0 +1,11 @@
|
||||
# This `.xcode.env` file is versioned and is used to source the environment
|
||||
# used when running script phases inside Xcode.
|
||||
# To customize your local environment, you can create an `.xcode.env.local`
|
||||
# file that is not versioned.
|
||||
|
||||
# NODE_BINARY variable contains the PATH to the node executable.
|
||||
#
|
||||
# Customize the NODE_BINARY variable here.
|
||||
# For example, to use nvm with brew, add the following line
|
||||
# . "$(brew --prefix nvm)/nvm.sh" --no-use
|
||||
export NODE_BINARY=$(command -v node)
|
||||
@@ -0,0 +1,544 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
05BAC76EB2F44404BCDAFE0E /* noop-file.swift in Sources */ = {isa = PBXBuildFile; fileRef = 920486E7D8564009BA09A00A /* noop-file.swift */; };
|
||||
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; };
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
||||
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
|
||||
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; };
|
||||
47FCCC6B41070FB7061A2F1B /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 4F738668B5B302410E775CFC /* PrivacyInfo.xcprivacy */; };
|
||||
96905EF65AED1B983A6B3ABC /* libPods-Menulio.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-Menulio.a */; };
|
||||
B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */; };
|
||||
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
13B07F961A680F5B00A75B9A /* Menulio.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Menulio.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Menulio/AppDelegate.h; sourceTree = "<group>"; };
|
||||
13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = Menulio/AppDelegate.mm; sourceTree = "<group>"; };
|
||||
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Menulio/Images.xcassets; sourceTree = "<group>"; };
|
||||
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Menulio/Info.plist; sourceTree = "<group>"; };
|
||||
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Menulio/main.m; sourceTree = "<group>"; };
|
||||
4F738668B5B302410E775CFC /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Menulio/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
58EEBF8E8E6FB1BC6CAF49B5 /* libPods-Menulio.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Menulio.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
6C2E3173556A471DD304B334 /* Pods-Menulio.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Menulio.debug.xcconfig"; path = "Target Support Files/Pods-Menulio/Pods-Menulio.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
7A4D352CD337FB3A3BF06240 /* Pods-Menulio.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Menulio.release.xcconfig"; path = "Target Support Files/Pods-Menulio/Pods-Menulio.release.xcconfig"; sourceTree = "<group>"; };
|
||||
920486E7D8564009BA09A00A /* noop-file.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; name = "noop-file.swift"; path = "Menulio/noop-file.swift"; sourceTree = "<group>"; };
|
||||
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = Menulio/SplashScreen.storyboard; sourceTree = "<group>"; };
|
||||
BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; };
|
||||
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
|
||||
F5CD4FEA57634883A4542F6A /* Menulio-Bridging-Header.h */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.c.h; name = "Menulio-Bridging-Header.h"; path = "Menulio/Menulio-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-Menulio/ExpoModulesProvider.swift"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
96905EF65AED1B983A6B3ABC /* libPods-Menulio.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
13B07FAE1A68108700A75B9A /* Menulio */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BB2F792B24A3F905000567C9 /* Supporting */,
|
||||
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
|
||||
13B07FB01A68108700A75B9A /* AppDelegate.mm */,
|
||||
13B07FB51A68108700A75B9A /* Images.xcassets */,
|
||||
13B07FB61A68108700A75B9A /* Info.plist */,
|
||||
13B07FB71A68108700A75B9A /* main.m */,
|
||||
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */,
|
||||
920486E7D8564009BA09A00A /* noop-file.swift */,
|
||||
F5CD4FEA57634883A4542F6A /* Menulio-Bridging-Header.h */,
|
||||
4F738668B5B302410E775CFC /* PrivacyInfo.xcprivacy */,
|
||||
);
|
||||
name = Menulio;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
|
||||
58EEBF8E8E6FB1BC6CAF49B5 /* libPods-Menulio.a */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
name = Libraries;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
83CBB9F61A601CBA00E9B192 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
13B07FAE1A68108700A75B9A /* Menulio */,
|
||||
832341AE1AAA6A7D00B99B32 /* Libraries */,
|
||||
83CBBA001A601CBA00E9B192 /* Products */,
|
||||
2D16E6871FA4F8E400B85C8A /* Frameworks */,
|
||||
D65327D7A22EEC0BE12398D9 /* Pods */,
|
||||
D7E4C46ADA2E9064B798F356 /* ExpoModulesProviders */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
usesTabs = 0;
|
||||
};
|
||||
83CBBA001A601CBA00E9B192 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
13B07F961A680F5B00A75B9A /* Menulio.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
92DBD88DE9BF7D494EA9DA96 /* Menulio */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */,
|
||||
);
|
||||
name = Menulio;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BB2F792B24A3F905000567C9 /* Supporting */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BB2F792C24A3F905000567C9 /* Expo.plist */,
|
||||
);
|
||||
name = Supporting;
|
||||
path = Menulio/Supporting;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D65327D7A22EEC0BE12398D9 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6C2E3173556A471DD304B334 /* Pods-Menulio.debug.xcconfig */,
|
||||
7A4D352CD337FB3A3BF06240 /* Pods-Menulio.release.xcconfig */,
|
||||
);
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D7E4C46ADA2E9064B798F356 /* ExpoModulesProviders */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
92DBD88DE9BF7D494EA9DA96 /* Menulio */,
|
||||
);
|
||||
name = ExpoModulesProviders;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
13B07F861A680F5B00A75B9A /* Menulio */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Menulio" */;
|
||||
buildPhases = (
|
||||
08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */,
|
||||
5C48BE09ECBB9F203B7F2ED2 /* [Expo] Configure project */,
|
||||
13B07F871A680F5B00A75B9A /* Sources */,
|
||||
13B07F8C1A680F5B00A75B9A /* Frameworks */,
|
||||
13B07F8E1A680F5B00A75B9A /* Resources */,
|
||||
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
|
||||
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */,
|
||||
EFB4C3DB6A514B0ADA511E60 /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = Menulio;
|
||||
productName = Menulio;
|
||||
productReference = 13B07F961A680F5B00A75B9A /* Menulio.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
83CBB9F71A601CBA00E9B192 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1130;
|
||||
TargetAttributes = {
|
||||
13B07F861A680F5B00A75B9A = {
|
||||
LastSwiftMigration = 1250;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "Menulio" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 83CBB9F61A601CBA00E9B192;
|
||||
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
13B07F861A680F5B00A75B9A /* Menulio */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
13B07F8E1A680F5B00A75B9A /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */,
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
|
||||
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */,
|
||||
47FCCC6B41070FB7061A2F1B /* PrivacyInfo.xcprivacy in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Bundle React Native code and images";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios absolute | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n# Source .xcode.env.updates if it exists to allow\n# SKIP_BUNDLING to be unset if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.updates\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.updates\"\nfi\n# Source local changes to allow overrides\n# if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n\n";
|
||||
};
|
||||
08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-Menulio-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
5C48BE09ECBB9F203B7F2ED2 /* [Expo] Configure project */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[Expo] Configure project";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-Menulio/expo-configure-project.sh\"\n";
|
||||
};
|
||||
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Menulio/Pods-Menulio-resources.sh",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/ExpoConstants_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/PurchasesHybridCommon/PurchasesHybridCommon.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/RCT-Folly/RCT-Folly_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/RNCAsyncStorage/RNCAsyncStorage_resources.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/React-Core_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact/React-cxxreact_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/RevenueCat/RevenueCat.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/boost/boost_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/glog/glog_privacy.bundle",
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoConstants_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoFileSystem_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/PurchasesHybridCommon.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCT-Folly_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCAsyncStorage_resources.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-Core_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-cxxreact_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RevenueCat.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/boost_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/glog_privacy.bundle",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Menulio/Pods-Menulio-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
EFB4C3DB6A514B0ADA511E60 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Menulio/Pods-Menulio-frameworks.sh",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Menulio/Pods-Menulio-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
13B07F871A680F5B00A75B9A /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */,
|
||||
13B07FC11A68108700A75B9A /* main.m in Sources */,
|
||||
B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */,
|
||||
05BAC76EB2F44404BCDAFE0E /* noop-file.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
13B07F941A680F5B00A75B9A /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 6C2E3173556A471DD304B334 /* Pods-Menulio.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Menulio/Menulio.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 21;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"$(inherited)",
|
||||
"FB_SONARKIT_ENABLED=1",
|
||||
);
|
||||
INFOPLIST_FILE = Menulio/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 1.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
"-lc++",
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.ayristech.menulio;
|
||||
PRODUCT_NAME = Menulio;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Menulio/Menulio-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
13B07F951A680F5B00A75B9A /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7A4D352CD337FB3A3BF06240 /* Pods-Menulio.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Menulio/Menulio.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 21;
|
||||
INFOPLIST_FILE = Menulio/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 1.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
"-lc++",
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.ayristech.menulio;
|
||||
PRODUCT_NAME = Menulio;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Menulio/Menulio-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
83CBBA201A601CBA00E9B192 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
|
||||
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
" ",
|
||||
);
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../node_modules/.pnpm/react-native@0.76.9_@babel+core@7.29.7_supports-color@8.1.1__@babel+preset-env@7.29.7_@_10f0e3ffd93936a63eed56e417b084b2/node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
|
||||
USE_HERMES = true;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
83CBBA211A601CBA00E9B192 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = YES;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
|
||||
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
" ",
|
||||
);
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../node_modules/.pnpm/react-native@0.76.9_@babel+core@7.29.7_supports-color@8.1.1__@babel+preset-env@7.29.7_@_10f0e3ffd93936a63eed56e417b084b2/node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
USE_HERMES = true;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Menulio" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
13B07F941A680F5B00A75B9A /* Debug */,
|
||||
13B07F951A680F5B00A75B9A /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "Menulio" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
83CBBA201A601CBA00E9B192 /* Debug */,
|
||||
83CBBA211A601CBA00E9B192 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1130"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||
BuildableName = "Menulio.app"
|
||||
BlueprintName = "Menulio"
|
||||
ReferencedContainer = "container:Menulio.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "00E356ED1AD99517003FC87E"
|
||||
BuildableName = "MenulioTests.xctest"
|
||||
BlueprintName = "MenulioTests"
|
||||
ReferencedContainer = "container:Menulio.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||
BuildableName = "Menulio.app"
|
||||
BlueprintName = "Menulio"
|
||||
ReferencedContainer = "container:Menulio.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||
BuildableName = "Menulio.app"
|
||||
BlueprintName = "Menulio"
|
||||
ReferencedContainer = "container:Menulio.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Menulio.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,7 @@
|
||||
#import <RCTAppDelegate.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <Expo/Expo.h>
|
||||
|
||||
@interface AppDelegate : EXAppDelegateWrapper
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,62 @@
|
||||
#import "AppDelegate.h"
|
||||
|
||||
#import <React/RCTBundleURLProvider.h>
|
||||
#import <React/RCTLinkingManager.h>
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
self.moduleName = @"main";
|
||||
|
||||
// You can add your custom initial props in the dictionary below.
|
||||
// They will be passed down to the ViewController used by React Native.
|
||||
self.initialProps = @{};
|
||||
|
||||
return [super application:application didFinishLaunchingWithOptions:launchOptions];
|
||||
}
|
||||
|
||||
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
|
||||
{
|
||||
return [self bundleURL];
|
||||
}
|
||||
|
||||
- (NSURL *)bundleURL
|
||||
{
|
||||
#if DEBUG
|
||||
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@".expo/.virtual-metro-entry"];
|
||||
#else
|
||||
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
|
||||
#endif
|
||||
}
|
||||
|
||||
// Linking API
|
||||
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
|
||||
return [super application:application openURL:url options:options] || [RCTLinkingManager application:application openURL:url options:options];
|
||||
}
|
||||
|
||||
// Universal Links
|
||||
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
|
||||
BOOL result = [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler];
|
||||
return [super application:application continueUserActivity:userActivity restorationHandler:restorationHandler] || result;
|
||||
}
|
||||
|
||||
// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
|
||||
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
|
||||
{
|
||||
return [super application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
|
||||
}
|
||||
|
||||
// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
|
||||
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
|
||||
{
|
||||
return [super application:application didFailToRegisterForRemoteNotificationsWithError:error];
|
||||
}
|
||||
|
||||
// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
|
||||
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
|
||||
{
|
||||
return [super application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
|
||||
}
|
||||
|
||||
@end
|
||||
|
After Width: | Height: | Size: 546 KiB |
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"filename": "App-Icon-1024x1024@1x.png",
|
||||
"idiom": "universal",
|
||||
"platform": "ios",
|
||||
"size": "1024x1024"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"version": 1,
|
||||
"author": "expo"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "expo"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"colors": [
|
||||
{
|
||||
"color": {
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.0666666666666667",
|
||||
"green": "0.0509803921568627",
|
||||
"red": "0.0352941176470588"
|
||||
},
|
||||
"color-space": "srgb"
|
||||
},
|
||||
"idiom": "universal"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"version": 1,
|
||||
"author": "expo"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"idiom": "universal",
|
||||
"filename": "image.png",
|
||||
"scale": "1x"
|
||||
},
|
||||
{
|
||||
"idiom": "universal",
|
||||
"filename": "image@2x.png",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"idiom": "universal",
|
||||
"filename": "image@3x.png",
|
||||
"scale": "3x"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"version": 1,
|
||||
"author": "expo"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 489 KiB |
|
After Width: | Height: | Size: 489 KiB |
|
After Width: | Height: | Size: 489 KiB |
@@ -0,0 +1,87 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Menulio</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.0.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>menulio</string>
|
||||
<string>com.ayristech.menulio</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>21</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>12.0</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<false/>
|
||||
<key>NSAllowsLocalNetworking</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Menünüzün fotoğrafını çekerek AI ile dijitalleştirmek için kamera izni gereklidir.</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Allow $(PRODUCT_NAME) to access your microphone</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>Menü fotoğraflarınızı ve restoran logonuzu yüklemek için galeri izni gereklidir.</string>
|
||||
<key>NSUserActivityTypes</key>
|
||||
<array>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>SplashScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UIRequiresFullScreen</key>
|
||||
<false/>
|
||||
<key>UIStatusBarStyle</key>
|
||||
<string>UIStatusBarStyleDefault</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIUserInterfaceStyle</key>
|
||||
<string>Automatic</string>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,3 @@
|
||||
//
|
||||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||
//
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict/>
|
||||
</plist>
|
||||
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>CA92.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>0A2A.1</string>
|
||||
<string>3B52.1</string>
|
||||
<string>C617.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>E174.1</string>
|
||||
<string>85F4.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>35F9.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array/>
|
||||
<key>NSPrivacyTracking</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="EXPO-VIEWCONTROLLER-1">
|
||||
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22685"/>
|
||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<scene sceneID="EXPO-SCENE-1">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="SplashScreenViewController" id="EXPO-VIEWCONTROLLER-1" sceneMemberID="viewController">
|
||||
<view key="view" userInteractionEnabled="NO" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="EXPO-ContainerView" userLabel="ContainerView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<imageView id="EXPO-SplashScreen" userLabel="SplashScreenLogo" image="SplashScreenLogo" contentMode="scaleAspectFit" clipsSubviews="true" userInteractionEnabled="false" translatesAutoresizingMaskIntoConstraints="false">
|
||||
<rect key="frame" x="0" y="0" width="414" height="736"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="Rmq-lb-GrQ"/>
|
||||
<constraints>
|
||||
<constraint firstItem="EXPO-SplashScreen" firstAttribute="top" secondItem="EXPO-ContainerView" secondAttribute="top" id="83fcb9b545b870ba44c24f0feeb116490c499c52"/>
|
||||
<constraint firstItem="EXPO-SplashScreen" firstAttribute="leading" secondItem="EXPO-ContainerView" secondAttribute="leading" id="61d16215e44b98e39d0a2c74fdbfaaa22601b12c"/>
|
||||
<constraint firstItem="EXPO-SplashScreen" firstAttribute="trailing" secondItem="EXPO-ContainerView" secondAttribute="trailing" id="f934da460e9ab5acae3ad9987d5b676a108796c1"/>
|
||||
<constraint firstItem="EXPO-SplashScreen" firstAttribute="bottom" secondItem="EXPO-ContainerView" secondAttribute="bottom" id="d6a0be88096b36fb132659aa90203d39139deda9"/>
|
||||
</constraints>
|
||||
<color key="backgroundColor" name="SplashScreenBackground"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="EXPO-PLACEHOLDER-1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="0.0" y="0.0"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="SplashScreenLogo" width="414" height="736"/>
|
||||
<namedColor name="SplashScreenBackground">
|
||||
<color alpha="1.000" blue="0.0666666666666667" green="0.0509803921568627" red="0.0352941176470588" customColorSpace="sRGB" colorSpace="custom"/>
|
||||
</namedColor>
|
||||
</resources>
|
||||
</document>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>EXUpdatesCheckOnLaunch</key>
|
||||
<string>ALWAYS</string>
|
||||
<key>EXUpdatesEnabled</key>
|
||||
<false/>
|
||||
<key>EXUpdatesLaunchWaitMs</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,10 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "AppDelegate.h"
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
@autoreleasepool {
|
||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
//
|
||||
// @generated
|
||||
// A blank Swift file must be created for native modules with Swift files to work correctly.
|
||||
//
|
||||
@@ -0,0 +1,66 @@
|
||||
require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking")
|
||||
require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods")
|
||||
|
||||
require 'json'
|
||||
podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {}
|
||||
|
||||
ENV['RCT_NEW_ARCH_ENABLED'] = podfile_properties['newArchEnabled'] == 'true' ? '1' : '0'
|
||||
ENV['EX_DEV_CLIENT_NETWORK_INSPECTOR'] = podfile_properties['EX_DEV_CLIENT_NETWORK_INSPECTOR']
|
||||
|
||||
platform :ios, podfile_properties['ios.deploymentTarget'] || '15.1'
|
||||
install! 'cocoapods',
|
||||
:deterministic_uuids => false
|
||||
|
||||
prepare_react_native_project!
|
||||
|
||||
target 'Menulio' do
|
||||
use_expo_modules!
|
||||
|
||||
if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1'
|
||||
config_command = ['node', '-e', "process.argv=['', '', 'config'];require('@react-native-community/cli').run()"];
|
||||
else
|
||||
config_command = [
|
||||
'node',
|
||||
'--no-warnings',
|
||||
'--eval',
|
||||
'require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'expo/package.json\')] }))(process.argv.slice(1))',
|
||||
'react-native-config',
|
||||
'--json',
|
||||
'--platform',
|
||||
'ios'
|
||||
]
|
||||
end
|
||||
|
||||
config = use_native_modules!(config_command)
|
||||
|
||||
use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks']
|
||||
use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS']
|
||||
|
||||
use_react_native!(
|
||||
:path => config[:reactNativePath],
|
||||
:hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes',
|
||||
# An absolute path to your application root.
|
||||
:app_path => "#{Pod::Config.instance.installation_root}/..",
|
||||
:privacy_file_aggregation_enabled => podfile_properties['apple.privacyManifestAggregationEnabled'] != 'false',
|
||||
)
|
||||
|
||||
post_install do |installer|
|
||||
react_native_post_install(
|
||||
installer,
|
||||
config[:reactNativePath],
|
||||
:mac_catalyst_enabled => false,
|
||||
:ccache_enabled => podfile_properties['apple.ccacheEnabled'] == 'true',
|
||||
)
|
||||
|
||||
# This is necessary for Xcode 14, because it signs resource bundles by default
|
||||
# when building for devices.
|
||||
installer.target_installation_results.pod_target_installation_results
|
||||
.each do |pod_name, target_installation_result|
|
||||
target_installation_result.resource_bundle_targets.each do |resource_bundle_target|
|
||||
resource_bundle_target.build_configurations.each do |config|
|
||||
config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"expo.jsEngine": "hermes",
|
||||
"EX_DEV_CLIENT_NETWORK_INSPECTOR": "true",
|
||||
"newArchEnabled": "true"
|
||||
}
|
||||
@@ -25,9 +25,9 @@
|
||||
"expo-haptics": "^14.0.1",
|
||||
"expo-image-picker": "~16.0.6",
|
||||
"expo-linking": "~7.0.5",
|
||||
"expo-print": "^57.0.1",
|
||||
"expo-print": "~14.0.3",
|
||||
"expo-router": "~4.0.0",
|
||||
"expo-sharing": "^57.0.13",
|
||||
"expo-sharing": "~13.0.1",
|
||||
"expo-splash-screen": "~0.29.24",
|
||||
"expo-status-bar": "~2.0.0",
|
||||
"react": "18.3.1",
|
||||
@@ -35,8 +35,9 @@
|
||||
"react-native-purchases": "^8.2.0",
|
||||
"react-native-safe-area-context": "4.12.0",
|
||||
"react-native-screens": "~4.4.0",
|
||||
"react-native-svg": "15.8.0",
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
"react-native-view-shot": "^5.1.1"
|
||||
"react-native-view-shot": "~4.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
|
||||
@@ -4,7 +4,7 @@ import { StatusBar } from "expo-status-bar";
|
||||
export default function RootLayout() {
|
||||
return (
|
||||
<>
|
||||
<StatusBar style="dark" />
|
||||
<StatusBar style="light" />
|
||||
<Stack screenOptions={{ headerShown: false }} />
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -26,10 +26,20 @@ interface RestaurantDetail {
|
||||
name: string;
|
||||
slug: string;
|
||||
logo_url: string | null;
|
||||
cover_url: string | null;
|
||||
phone: string | null;
|
||||
address: string | null;
|
||||
enabled_languages?: string[];
|
||||
}
|
||||
|
||||
const SUPPORTED_LANGUAGES: { code: string; label: string; flag: string }[] = [
|
||||
{ code: "en", label: "İngilizce", flag: "🇬🇧" },
|
||||
{ code: "de", label: "Almanca", flag: "🇩🇪" },
|
||||
{ code: "ar", label: "Arapça", flag: "🇸🇦" },
|
||||
{ code: "ru", label: "Rusça", flag: "🇷🇺" },
|
||||
{ code: "fr", label: "Fransızca", flag: "🇫🇷" },
|
||||
];
|
||||
|
||||
export interface TxtRecord {
|
||||
name: string;
|
||||
value: string;
|
||||
@@ -79,6 +89,7 @@ export default function AccountScreen() {
|
||||
const [phone, setPhone] = useState("");
|
||||
const [address, setAddress] = useState("");
|
||||
const [logoUrl, setLogoUrl] = useState<string | null>(null);
|
||||
const [coverUrl, setCoverUrl] = useState<string | null>(null);
|
||||
const [slug, setSlug] = useState("");
|
||||
const [savingRest, setSavingRest] = useState(false);
|
||||
const [deletingRestaurant, setDeletingRestaurant] = useState(false);
|
||||
@@ -87,6 +98,10 @@ export default function AccountScreen() {
|
||||
const [selectedTheme, setSelectedTheme] = useState("elegant");
|
||||
const [savingTheme, setSavingTheme] = useState(false);
|
||||
|
||||
// Language / AI Translation state
|
||||
const [enabledLanguages, setEnabledLanguages] = useState<string[]>(["tr"]);
|
||||
const [updatingLanguage, setUpdatingLanguage] = useState<string | null>(null);
|
||||
|
||||
// Custom Domain state
|
||||
const [domains, setDomains] = useState<DomainRow[]>([]);
|
||||
const [newDomainInput, setNewDomainInput] = useState("");
|
||||
@@ -117,7 +132,9 @@ export default function AccountScreen() {
|
||||
setPhone(rest.phone ?? "");
|
||||
setAddress(rest.address ?? "");
|
||||
setLogoUrl(rest.logo_url ?? null);
|
||||
setCoverUrl(rest.cover_url ?? null);
|
||||
setSlug(rest.slug);
|
||||
setEnabledLanguages(rest.enabled_languages ?? ["tr"]);
|
||||
|
||||
// Fetch theme
|
||||
const themeRes = await api.get<{ themeKey: string }>(`/restaurants/${cached.restaurantId}/theme`).catch(() => ({ themeKey: "elegant" }));
|
||||
@@ -169,6 +186,29 @@ export default function AccountScreen() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handlePickCover() {
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => {});
|
||||
const permission = await ImagePicker.requestMediaLibraryPermissionsAsync();
|
||||
if (!permission.granted) {
|
||||
Alert.alert("İzin Gerekli", "Kapak fotoğrafı seçebilmek için fotoğraf galerisi izni gereklidir.");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await ImagePicker.launchImageLibraryAsync({
|
||||
mediaTypes: ["images"],
|
||||
allowsEditing: true,
|
||||
aspect: [16, 9],
|
||||
quality: 0.6,
|
||||
base64: true,
|
||||
});
|
||||
|
||||
if (!result.canceled && result.assets[0]) {
|
||||
const asset = result.assets[0];
|
||||
const newCover = asset.base64 ? `data:image/jpeg;base64,${asset.base64}` : asset.uri;
|
||||
setCoverUrl(newCover);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSaveRestaurant() {
|
||||
if (!active || !name.trim()) return;
|
||||
setSavingRest(true);
|
||||
@@ -179,6 +219,7 @@ export default function AccountScreen() {
|
||||
phone: phone.trim() || null,
|
||||
address: address.trim() || null,
|
||||
logoUrl: logoUrl || null,
|
||||
coverUrl: coverUrl || null,
|
||||
slug: slug.trim() || undefined,
|
||||
});
|
||||
|
||||
@@ -214,6 +255,54 @@ export default function AccountScreen() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAddLanguage(code: string, label: string) {
|
||||
if (!active || updatingLanguage) return;
|
||||
setUpdatingLanguage(code);
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium).catch(() => {});
|
||||
try {
|
||||
const res = await api.post<{ enabledLanguages: string[] }>(
|
||||
`/restaurants/${active.restaurantId}/languages`,
|
||||
{ code },
|
||||
);
|
||||
setEnabledLanguages(res.enabledLanguages);
|
||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {});
|
||||
Alert.alert("Menü Çevrildi 🎉", `Menünüz yapay zeka ile ${label} diline çevrildi.`);
|
||||
} catch (err) {
|
||||
Alert.alert("Hata", err instanceof Error ? err.message : "Dil eklenemedi.");
|
||||
} finally {
|
||||
setUpdatingLanguage(null);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRemoveLanguage(code: string, label: string) {
|
||||
if (!active || updatingLanguage) return;
|
||||
Alert.alert(
|
||||
`${label} Dilini Kaldır`,
|
||||
"Bu dil menüde artık gösterilmeyecek. Çeviriler saklanacağı için dili tekrar eklerseniz yeniden çevrilmesi gerekmez.",
|
||||
[
|
||||
{ text: "Vazgeç", style: "cancel" },
|
||||
{
|
||||
text: "Kaldır",
|
||||
style: "destructive",
|
||||
onPress: async () => {
|
||||
setUpdatingLanguage(code);
|
||||
try {
|
||||
const res = await api.delete<{ enabledLanguages: string[] }>(
|
||||
`/restaurants/${active.restaurantId}/languages/${code}`,
|
||||
);
|
||||
setEnabledLanguages(res.enabledLanguages);
|
||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {});
|
||||
} catch (err) {
|
||||
Alert.alert("Hata", err instanceof Error ? err.message : "Dil kaldırılamadı.");
|
||||
} finally {
|
||||
setUpdatingLanguage(null);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
async function handleAddCustomDomain() {
|
||||
if (!active || !newDomainInput.trim()) {
|
||||
Alert.alert("Eksik Bilgi", "Lütfen bağlamak istediğiniz alan adını girin (Örn: menu.kebapciahmet.com)");
|
||||
@@ -605,6 +694,94 @@ export default function AccountScreen() {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Language / AI Translation Section */}
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: "#FFFFFF",
|
||||
borderRadius: 20,
|
||||
padding: 20,
|
||||
borderWidth: 1,
|
||||
borderColor: "#E7E5E4",
|
||||
gap: 14,
|
||||
}}
|
||||
>
|
||||
<View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
|
||||
<Ionicons name="language-outline" size={20} color="#C8A96B" />
|
||||
<Text style={{ fontSize: 16, fontWeight: "800", color: "#1C1917" }}>
|
||||
Diller (Yapay Zeka Çevirisi)
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={{ fontSize: 12, color: "#78716C", lineHeight: 18 }}>
|
||||
Menünüzü yapay zeka ile otomatik olarak çevirin. Müşterileriniz menüde istedikleri dili seçebilir.
|
||||
</Text>
|
||||
|
||||
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8 }}>
|
||||
{/* Turkish — always on, base language */}
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 6,
|
||||
backgroundColor: "#1C1917",
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 10,
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontSize: 13 }}>🇹🇷</Text>
|
||||
<Text style={{ fontSize: 12, fontWeight: "700", color: "#FFFFFF" }}>Türkçe</Text>
|
||||
</View>
|
||||
|
||||
{SUPPORTED_LANGUAGES.map((lang) => {
|
||||
const isEnabled = enabledLanguages.includes(lang.code);
|
||||
const isUpdating = updatingLanguage === lang.code;
|
||||
return (
|
||||
<Pressable
|
||||
key={lang.code}
|
||||
onPress={() =>
|
||||
isEnabled
|
||||
? handleRemoveLanguage(lang.code, lang.label)
|
||||
: handleAddLanguage(lang.code, lang.label)
|
||||
}
|
||||
disabled={isUpdating}
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 6,
|
||||
backgroundColor: isEnabled ? "#FDF8F0" : "#FAF8F5",
|
||||
borderWidth: 1.5,
|
||||
borderColor: isEnabled ? "#C8A96B" : "#E7E5E4",
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 10,
|
||||
opacity: isUpdating ? 0.6 : 1,
|
||||
}}
|
||||
>
|
||||
{isUpdating ? (
|
||||
<ActivityIndicator size="small" color="#C8A96B" />
|
||||
) : (
|
||||
<Text style={{ fontSize: 13 }}>{lang.flag}</Text>
|
||||
)}
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 12,
|
||||
fontWeight: "700",
|
||||
color: isEnabled ? "#926E27" : "#57534E",
|
||||
}}
|
||||
>
|
||||
{lang.label}
|
||||
</Text>
|
||||
{isEnabled ? (
|
||||
<Ionicons name="close-circle" size={14} color="#C8A96B" />
|
||||
) : (
|
||||
<Ionicons name="add-circle-outline" size={14} color="#A8A29E" />
|
||||
)}
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Custom Domain Section */}
|
||||
<View
|
||||
style={{
|
||||
@@ -902,6 +1079,53 @@ export default function AccountScreen() {
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Cover Photo Picker */}
|
||||
<View>
|
||||
<Text style={{ fontSize: 12, fontWeight: "700", color: "#78716C", marginBottom: 6 }}>
|
||||
Kapak Fotoğrafı (Menü Üst Görseli)
|
||||
</Text>
|
||||
<Pressable
|
||||
onPress={handlePickCover}
|
||||
style={{
|
||||
width: "100%",
|
||||
aspectRatio: 16 / 9,
|
||||
borderRadius: 14,
|
||||
overflow: "hidden",
|
||||
backgroundColor: "#1C1917",
|
||||
borderWidth: 1.5,
|
||||
borderColor: "#D6D3D1",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{coverUrl ? (
|
||||
<Image source={{ uri: coverUrl }} style={{ width: "100%", height: "100%" }} resizeMode="cover" />
|
||||
) : (
|
||||
<View style={{ alignItems: "center", gap: 6 }}>
|
||||
<Ionicons name="image-outline" size={28} color="#C8A96B" />
|
||||
<Text style={{ color: "#C8A96B", fontSize: 12, fontWeight: "700" }}>
|
||||
Kapak Fotoğrafı Ekle
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
backgroundColor: "rgba(0,0,0,0.6)",
|
||||
alignItems: "center",
|
||||
paddingVertical: 6,
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: "#FFFFFF", fontSize: 11, fontWeight: "700" }}>
|
||||
📷 {coverUrl ? "Değiştir" : "Fotoğraf Seç"}
|
||||
</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
{/* Restaurant Name */}
|
||||
<View>
|
||||
<Text style={{ fontSize: 12, fontWeight: "700", color: "#78716C", marginBottom: 6 }}>
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
import React, { useRef } from "react";
|
||||
import {
|
||||
PanResponder,
|
||||
Text,
|
||||
View,
|
||||
} from "react-native";
|
||||
import * as Haptics from "expo-haptics";
|
||||
import { type CanvasTextLayer, getCanvasFontFamily } from "./types";
|
||||
|
||||
interface CanvasLayerItemProps {
|
||||
layer: CanvasTextLayer;
|
||||
isSelected: boolean;
|
||||
onSelect: (id: string) => void;
|
||||
onEdit: (layer: CanvasTextLayer) => void;
|
||||
onUpdatePosition: (id: string, x: number, y: number) => void;
|
||||
onDelete: (id: string) => void;
|
||||
onDragStart?: () => void;
|
||||
onDragMove?: (rawX: number, rawY: number, itemWidth: number, itemHeight: number) => { snappedX: number; snappedY: number };
|
||||
onDragEnd?: (finalX: number, finalY: number, itemWidth: number, itemHeight: number) => void;
|
||||
canvasWidth: number;
|
||||
canvasHeight: number;
|
||||
}
|
||||
|
||||
export const CurvedTextNative: React.FC<{
|
||||
text: string;
|
||||
fontSize: number;
|
||||
color: string;
|
||||
fontFamily: string;
|
||||
fontWeight?: any;
|
||||
letterSpacing?: number;
|
||||
curveDegree: number;
|
||||
hasShadow?: boolean;
|
||||
}> = ({
|
||||
text,
|
||||
fontSize,
|
||||
color,
|
||||
fontFamily,
|
||||
fontWeight = "800",
|
||||
letterSpacing = 0.5,
|
||||
curveDegree,
|
||||
hasShadow,
|
||||
}) => {
|
||||
const isUp = curveDegree > 0;
|
||||
const absDegree = Math.abs(curveDegree);
|
||||
const radius = Math.max(40, 420 - absDegree * 3.5);
|
||||
const charWidth = fontSize * 0.52 + (letterSpacing || 0);
|
||||
const numChars = text.length;
|
||||
const arcLength = numChars * charWidth;
|
||||
const totalAngleRad = arcLength / radius;
|
||||
const stepRad = numChars > 1 ? totalAngleRad / (numChars - 1) : 0;
|
||||
const startAngleRad = -totalAngleRad / 2;
|
||||
|
||||
const maxOffsetY = Math.abs(-radius * (Math.cos(totalAngleRad / 2) - 1));
|
||||
const containerHeight = fontSize + maxOffsetY + 12;
|
||||
const containerWidth = Math.max(numChars * charWidth, radius * 2 * Math.sin(totalAngleRad / 2)) + 16;
|
||||
|
||||
return (
|
||||
<View style={{ width: containerWidth, height: containerHeight, alignItems: "center", justifyContent: "center" }}>
|
||||
{text.split("").map((ch, i) => {
|
||||
const angleRad = startAngleRad + i * stepRad;
|
||||
const angleDeg = angleRad * (180 / Math.PI);
|
||||
const charX = radius * Math.sin(angleRad);
|
||||
const charY = isUp
|
||||
? -radius * (Math.cos(angleRad) - 1)
|
||||
: radius * (Math.cos(angleRad) - 1);
|
||||
const charRotate = isUp ? angleDeg : -angleDeg;
|
||||
|
||||
return (
|
||||
<Text
|
||||
key={i}
|
||||
style={{
|
||||
position: "absolute",
|
||||
fontSize,
|
||||
color,
|
||||
fontFamily,
|
||||
fontWeight,
|
||||
letterSpacing,
|
||||
textShadowColor: hasShadow ? "rgba(0,0,0,0.8)" : "transparent",
|
||||
textShadowOffset: { width: 0, height: 1 },
|
||||
textShadowRadius: 3,
|
||||
transform: [
|
||||
{ translateX: charX },
|
||||
{ translateY: charY },
|
||||
{ rotate: `${charRotate}deg` },
|
||||
],
|
||||
}}
|
||||
>
|
||||
{ch === " " ? "\u00A0" : ch}
|
||||
</Text>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export const CanvasLayerItem: React.FC<CanvasLayerItemProps> = ({
|
||||
layer,
|
||||
isSelected,
|
||||
onSelect,
|
||||
onEdit,
|
||||
onUpdatePosition,
|
||||
onDelete,
|
||||
onDragStart,
|
||||
onDragMove,
|
||||
onDragEnd,
|
||||
canvasWidth,
|
||||
canvasHeight,
|
||||
}) => {
|
||||
const layerRef = useRef(layer);
|
||||
layerRef.current = layer;
|
||||
|
||||
const canvasWidthRef = useRef(canvasWidth);
|
||||
canvasWidthRef.current = canvasWidth;
|
||||
|
||||
const canvasHeightRef = useRef(canvasHeight);
|
||||
canvasHeightRef.current = canvasHeight;
|
||||
|
||||
const panStartRef = useRef({ x: layer.x, y: layer.y });
|
||||
const layoutSizeRef = useRef({ width: 70, height: 26 });
|
||||
const currentPosRef = useRef({ x: layer.x, y: layer.y });
|
||||
const lastTapRef = useRef<number>(0);
|
||||
const isDraggingRef = useRef(false);
|
||||
|
||||
const panResponder = useRef(
|
||||
PanResponder.create({
|
||||
onStartShouldSetPanResponder: () => true,
|
||||
onStartShouldSetPanResponderCapture: () => true,
|
||||
onMoveShouldSetPanResponder: (_evt, gestureState) =>
|
||||
Math.abs(gestureState.dx) > 1 || Math.abs(gestureState.dy) > 1,
|
||||
onMoveShouldSetPanResponderCapture: (_evt, gestureState) =>
|
||||
Math.abs(gestureState.dx) > 1 || Math.abs(gestureState.dy) > 1,
|
||||
onPanResponderTerminationRequest: () => false,
|
||||
|
||||
onPanResponderGrant: () => {
|
||||
const now = Date.now();
|
||||
const DOUBLE_TAP_DELAY = 300;
|
||||
const curLayer = layerRef.current;
|
||||
if (now - lastTapRef.current < DOUBLE_TAP_DELAY) {
|
||||
try {
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
|
||||
} catch {}
|
||||
onEdit(curLayer);
|
||||
} else {
|
||||
onSelect(curLayer.id);
|
||||
}
|
||||
lastTapRef.current = now;
|
||||
|
||||
panStartRef.current = { x: curLayer.x, y: curLayer.y };
|
||||
currentPosRef.current = { x: curLayer.x, y: curLayer.y };
|
||||
isDraggingRef.current = false;
|
||||
onDragStart?.();
|
||||
},
|
||||
|
||||
onPanResponderMove: (_evt, gestureState) => {
|
||||
if (!isDraggingRef.current && (Math.abs(gestureState.dx) > 2 || Math.abs(gestureState.dy) > 2)) {
|
||||
isDraggingRef.current = true;
|
||||
}
|
||||
|
||||
const curLayer = layerRef.current;
|
||||
const itemW = layoutSizeRef.current.width;
|
||||
const itemH = layoutSizeRef.current.height;
|
||||
const cWidth = canvasWidthRef.current;
|
||||
const cHeight = canvasHeightRef.current;
|
||||
|
||||
const maxX = Math.max(0, cWidth - itemW);
|
||||
const maxY = Math.max(0, cHeight - itemH);
|
||||
const rawX = Math.max(0, Math.min(maxX, panStartRef.current.x + gestureState.dx));
|
||||
const rawY = Math.max(0, Math.min(maxY, panStartRef.current.y + gestureState.dy));
|
||||
|
||||
if (onDragMove) {
|
||||
const { snappedX, snappedY } = onDragMove(rawX, rawY, itemW, itemH);
|
||||
currentPosRef.current = { x: snappedX, y: snappedY };
|
||||
onUpdatePosition(curLayer.id, snappedX, snappedY);
|
||||
} else {
|
||||
const sx = Math.round(rawX);
|
||||
const sy = Math.round(rawY);
|
||||
currentPosRef.current = { x: sx, y: sy };
|
||||
onUpdatePosition(curLayer.id, sx, sy);
|
||||
}
|
||||
},
|
||||
|
||||
onPanResponderRelease: () => {
|
||||
const itemW = layoutSizeRef.current.width;
|
||||
const itemH = layoutSizeRef.current.height;
|
||||
onDragEnd?.(currentPosRef.current.x, currentPosRef.current.y, itemW, itemH);
|
||||
},
|
||||
|
||||
onPanResponderTerminate: () => {
|
||||
const itemW = layoutSizeRef.current.width;
|
||||
const itemH = layoutSizeRef.current.height;
|
||||
onDragEnd?.(currentPosRef.current.x, currentPosRef.current.y, itemW, itemH);
|
||||
},
|
||||
})
|
||||
).current;
|
||||
|
||||
// Instagram Badge arka plan stili
|
||||
const renderBackgroundStyle = () => {
|
||||
switch (layer.bgStyle) {
|
||||
case "solid":
|
||||
return {
|
||||
backgroundColor: layer.bgBoxColor || "rgba(18, 18, 21, 0.92)",
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 5,
|
||||
borderRadius: 8,
|
||||
};
|
||||
case "soft":
|
||||
return {
|
||||
backgroundColor: "rgba(255, 255, 255, 0.18)",
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 5,
|
||||
borderRadius: 8,
|
||||
borderWidth: 1,
|
||||
borderColor: "rgba(255, 255, 255, 0.25)",
|
||||
};
|
||||
case "outline":
|
||||
return {
|
||||
backgroundColor: "transparent",
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 8,
|
||||
borderWidth: 1.5,
|
||||
borderColor: layer.color || "#D4AF37",
|
||||
};
|
||||
case "none":
|
||||
default:
|
||||
return {
|
||||
backgroundColor: "transparent",
|
||||
paddingHorizontal: 4,
|
||||
paddingVertical: 2,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View
|
||||
{...panResponder.panHandlers}
|
||||
accessible={true}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={`Metin: ${layer.text}`}
|
||||
accessibilityHint="Metni düzenlemek için çift dokunun, taşımak için sürükleyin"
|
||||
accessibilityState={{ selected: isSelected }}
|
||||
onLayout={(e) => {
|
||||
const { width, height } = e.nativeEvent.layout;
|
||||
layoutSizeRef.current = { width, height };
|
||||
}}
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: layer.x,
|
||||
top: layer.y,
|
||||
transform: [{ rotate: `${layer.rotation || 0}deg` }],
|
||||
zIndex: isSelected ? 90 : 20,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
borderWidth: isSelected ? 1.5 : 0,
|
||||
borderColor: isSelected ? "#D4AF37" : "transparent",
|
||||
borderStyle: isSelected ? "dashed" : "solid",
|
||||
borderRadius: 10,
|
||||
padding: 2,
|
||||
}}
|
||||
>
|
||||
<View style={renderBackgroundStyle()}>
|
||||
{layer.curveDegree && layer.curveDegree !== 0 ? (
|
||||
<CurvedTextNative
|
||||
text={layer.text}
|
||||
fontSize={layer.fontSize}
|
||||
color={layer.color}
|
||||
fontFamily={getCanvasFontFamily(layer.fontId)}
|
||||
fontWeight={layer.fontWeight || "bold"}
|
||||
letterSpacing={layer.letterSpacing ?? 0.5}
|
||||
curveDegree={layer.curveDegree}
|
||||
hasShadow={layer.hasShadow}
|
||||
/>
|
||||
) : (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: layer.fontSize,
|
||||
color: layer.color,
|
||||
fontFamily: getCanvasFontFamily(layer.fontId),
|
||||
fontWeight: layer.fontWeight || "bold",
|
||||
textAlign: layer.textAlign || "center",
|
||||
letterSpacing: layer.letterSpacing ?? 0.5,
|
||||
textShadowColor: layer.hasShadow ? "rgba(0,0,0,0.8)" : "transparent",
|
||||
textShadowOffset: { width: 0, height: 1 },
|
||||
textShadowRadius: 3,
|
||||
}}
|
||||
>
|
||||
{layer.text}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,176 @@
|
||||
import React, { useRef } from "react";
|
||||
import {
|
||||
Image,
|
||||
PanResponder,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { type CanvasQrConfig } from "./types";
|
||||
|
||||
interface CanvasQrItemProps {
|
||||
qrConfig: CanvasQrConfig;
|
||||
qrBase64?: string;
|
||||
isSelected: boolean;
|
||||
onSelect: () => void;
|
||||
onUpdatePosition: (x: number, y: number) => void;
|
||||
onDragStart?: () => void;
|
||||
onDragMove?: (rawX: number, rawY: number, itemWidth: number, itemHeight: number) => { snappedX: number; snappedY: number };
|
||||
onDragEnd?: (finalX: number, finalY: number, itemWidth: number, itemHeight: number) => void;
|
||||
canvasWidth: number;
|
||||
canvasHeight: number;
|
||||
}
|
||||
|
||||
export const CanvasQrItem: React.FC<CanvasQrItemProps> = ({
|
||||
qrConfig,
|
||||
qrBase64,
|
||||
isSelected,
|
||||
onSelect,
|
||||
onUpdatePosition,
|
||||
onDragStart,
|
||||
onDragMove,
|
||||
onDragEnd,
|
||||
canvasWidth,
|
||||
canvasHeight,
|
||||
}) => {
|
||||
// Stale closure önlemi için ref'ler
|
||||
const qrConfigRef = useRef(qrConfig);
|
||||
qrConfigRef.current = qrConfig;
|
||||
|
||||
const canvasWidthRef = useRef(canvasWidth);
|
||||
canvasWidthRef.current = canvasWidth;
|
||||
|
||||
const canvasHeightRef = useRef(canvasHeight);
|
||||
canvasHeightRef.current = canvasHeight;
|
||||
|
||||
const panStartRef = useRef({ x: qrConfig.x, y: qrConfig.y });
|
||||
const currentPosRef = useRef({ x: qrConfig.x, y: qrConfig.y });
|
||||
|
||||
const panResponder = useRef(
|
||||
PanResponder.create({
|
||||
onStartShouldSetPanResponder: () => true,
|
||||
onStartShouldSetPanResponderCapture: () => true,
|
||||
onMoveShouldSetPanResponder: (_evt, gestureState) =>
|
||||
Math.abs(gestureState.dx) > 1 || Math.abs(gestureState.dy) > 1,
|
||||
onMoveShouldSetPanResponderCapture: (_evt, gestureState) =>
|
||||
Math.abs(gestureState.dx) > 1 || Math.abs(gestureState.dy) > 1,
|
||||
onPanResponderTerminationRequest: () => false,
|
||||
|
||||
onPanResponderGrant: () => {
|
||||
onSelect();
|
||||
const cur = qrConfigRef.current;
|
||||
panStartRef.current = { x: cur.x, y: cur.y };
|
||||
currentPosRef.current = { x: cur.x, y: cur.y };
|
||||
onDragStart?.();
|
||||
},
|
||||
|
||||
onPanResponderMove: (_evt, gestureState) => {
|
||||
const cur = qrConfigRef.current;
|
||||
const cWidth = canvasWidthRef.current;
|
||||
const cHeight = canvasHeightRef.current;
|
||||
|
||||
const maxX = Math.max(0, cWidth - cur.size);
|
||||
const maxY = Math.max(0, cHeight - cur.size);
|
||||
|
||||
const rawX = Math.max(0, Math.min(maxX, panStartRef.current.x + gestureState.dx));
|
||||
const rawY = Math.max(0, Math.min(maxY, panStartRef.current.y + gestureState.dy));
|
||||
|
||||
if (onDragMove) {
|
||||
const { snappedX, snappedY } = onDragMove(rawX, rawY, cur.size, cur.size);
|
||||
currentPosRef.current = { x: snappedX, y: snappedY };
|
||||
onUpdatePosition(snappedX, snappedY);
|
||||
} else {
|
||||
const sx = Math.round(rawX);
|
||||
const sy = Math.round(rawY);
|
||||
currentPosRef.current = { x: sx, y: sy };
|
||||
onUpdatePosition(sx, sy);
|
||||
}
|
||||
},
|
||||
|
||||
onPanResponderRelease: () => {
|
||||
const cur = qrConfigRef.current;
|
||||
onDragEnd?.(currentPosRef.current.x, currentPosRef.current.y, cur.size, cur.size);
|
||||
},
|
||||
|
||||
onPanResponderTerminate: () => {
|
||||
const cur = qrConfigRef.current;
|
||||
onDragEnd?.(currentPosRef.current.x, currentPosRef.current.y, cur.size, cur.size);
|
||||
},
|
||||
})
|
||||
).current;
|
||||
|
||||
return (
|
||||
<View
|
||||
{...panResponder.panHandlers}
|
||||
accessible={true}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="QR Kod Alanı"
|
||||
accessibilityHint="QR kodunu seçmek, boyutunu ayarlamak veya stant üzerinde taşımak için dokunun"
|
||||
accessibilityState={{ selected: isSelected }}
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: qrConfig.x,
|
||||
top: qrConfig.y,
|
||||
width: qrConfig.size,
|
||||
height: qrConfig.size,
|
||||
zIndex: isSelected ? 80 : 15,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
backgroundColor: qrConfig.bgBoxColor || "#FFFFFF",
|
||||
borderRadius: qrConfig.borderRadius,
|
||||
borderWidth: isSelected ? 2 : qrConfig.borderWidth,
|
||||
borderColor: isSelected ? "#D4AF37" : qrConfig.borderColor,
|
||||
borderStyle: isSelected ? "dashed" : "solid",
|
||||
padding: qrConfig.padding,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
shadowColor: "#000",
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.25,
|
||||
shadowRadius: 6,
|
||||
elevation: 4,
|
||||
}}
|
||||
>
|
||||
{qrBase64 ? (
|
||||
<Image
|
||||
source={{ uri: `data:image/png;base64,${qrBase64}` }}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
borderRadius: Math.max(0, qrConfig.borderRadius - 4),
|
||||
}}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
) : (
|
||||
<View style={{ alignItems: "center", justifyContent: "center", width: "100%", height: "100%" }}>
|
||||
<Ionicons name="qr-code" size={Math.round(qrConfig.size * 0.7)} color="#0C0C0E" />
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Seçili İken Taşıma Rozeti */}
|
||||
{isSelected && (
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: -8,
|
||||
right: -8,
|
||||
backgroundColor: "#D4AF37",
|
||||
width: 22,
|
||||
height: 22,
|
||||
borderRadius: 11,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
borderWidth: 1.5,
|
||||
borderColor: "#0C0C0E",
|
||||
}}
|
||||
>
|
||||
<Ionicons name="move" size={12} color="#0C0C0E" />
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,709 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
KeyboardAvoidingView,
|
||||
Modal,
|
||||
Platform,
|
||||
Pressable,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import * as Haptics from "expo-haptics";
|
||||
import {
|
||||
type CanvasTextLayer,
|
||||
CANVAS_FONT_OPTIONS,
|
||||
CANVAS_COLOR_PALETTE,
|
||||
getCanvasFontFamily,
|
||||
} from "./types";
|
||||
|
||||
interface InstagramTextEditorModalProps {
|
||||
visible: boolean;
|
||||
layer: CanvasTextLayer | null;
|
||||
onSave: (updated: Partial<CanvasTextLayer>) => void;
|
||||
onClose: () => void;
|
||||
onDelete?: (id: string) => void;
|
||||
}
|
||||
|
||||
export const InstagramTextEditorModal: React.FC<InstagramTextEditorModalProps> = ({
|
||||
visible,
|
||||
layer,
|
||||
onSave,
|
||||
onClose,
|
||||
onDelete,
|
||||
}) => {
|
||||
const insets = useSafeAreaInsets();
|
||||
const inputRef = useRef<TextInput>(null);
|
||||
|
||||
const [text, setText] = useState("");
|
||||
const [fontId, setFontId] = useState("sans");
|
||||
const [color, setColor] = useState("#FFFFFF");
|
||||
const [fontSize, setFontSize] = useState(16);
|
||||
const [textAlign, setTextAlign] = useState<"left" | "center" | "right">("center");
|
||||
const [bgStyle, setBgStyle] = useState<"none" | "solid" | "soft" | "outline">("none");
|
||||
const [bgBoxColor, setBgBoxColor] = useState<string | undefined>(undefined);
|
||||
const [curveDegree, setCurveDegree] = useState<number>(0);
|
||||
|
||||
// Modal açıldığında layer verilerini yükle
|
||||
useEffect(() => {
|
||||
if (layer && visible) {
|
||||
setText(layer.text || "");
|
||||
setFontId(layer.fontId || "sans");
|
||||
setColor(layer.color || "#FFFFFF");
|
||||
setFontSize(layer.fontSize || 16);
|
||||
setTextAlign(layer.textAlign || "center");
|
||||
setCurveDegree(layer.curveDegree || 0);
|
||||
if (layer.bgBoxColor) {
|
||||
if (layer.bgBoxColor.includes("0.2") || layer.bgBoxColor.includes("0.3")) {
|
||||
setBgStyle("soft");
|
||||
} else {
|
||||
setBgStyle("solid");
|
||||
}
|
||||
setBgBoxColor(layer.bgBoxColor);
|
||||
} else {
|
||||
setBgStyle("none");
|
||||
setBgBoxColor(undefined);
|
||||
}
|
||||
|
||||
// Auto focus text input
|
||||
setTimeout(() => {
|
||||
inputRef.current?.focus();
|
||||
}, 150);
|
||||
}
|
||||
}, [layer, visible]);
|
||||
|
||||
// Kavis Modunu Değiştir (Düz (0) -> Yukarı Kavis (40) -> Ters Kavis (-40) -> Düz)
|
||||
const handleToggleCurveDegree = () => {
|
||||
try {
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
||||
} catch {}
|
||||
setCurveDegree((prev) => {
|
||||
if (prev === 0) return 40;
|
||||
if (prev === 40) return 60;
|
||||
if (prev === 60) return -40;
|
||||
return 0;
|
||||
});
|
||||
};
|
||||
|
||||
// Arka plan pill stilini sırayla değiştir (Instagram "A" butonu mantığı)
|
||||
const handleToggleBgStyle = () => {
|
||||
try {
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
||||
} catch {}
|
||||
|
||||
if (bgStyle === "none") {
|
||||
setBgStyle("solid");
|
||||
// Solid: seçili rengi kutu yap, metin rengini zıt yap
|
||||
if (color === "#FFFFFF" || color === "#F6EFE3") {
|
||||
setBgBoxColor("rgba(18, 18, 21, 0.92)");
|
||||
} else {
|
||||
setBgBoxColor("rgba(212, 175, 55, 0.92)");
|
||||
}
|
||||
} else if (bgStyle === "solid") {
|
||||
setBgStyle("soft");
|
||||
setBgBoxColor("rgba(255, 255, 255, 0.22)");
|
||||
} else if (bgStyle === "soft") {
|
||||
setBgStyle("outline");
|
||||
setBgBoxColor(undefined);
|
||||
} else {
|
||||
setBgStyle("none");
|
||||
setBgBoxColor(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
// Metin Hizalama Değiştir (Sol -> Orta -> Sağ)
|
||||
const handleToggleAlign = () => {
|
||||
try {
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
||||
} catch {}
|
||||
setTextAlign((prev) => {
|
||||
if (prev === "center") return "left";
|
||||
if (prev === "left") return "right";
|
||||
return "center";
|
||||
});
|
||||
};
|
||||
|
||||
// Renk Değiştir
|
||||
const handleSelectColor = (selectedColor: string) => {
|
||||
try {
|
||||
Haptics.selectionAsync();
|
||||
} catch {}
|
||||
setColor(selectedColor);
|
||||
if (bgStyle === "solid") {
|
||||
if (selectedColor === "#FFFFFF" || selectedColor === "#F6EFE3") {
|
||||
setBgBoxColor("rgba(18, 18, 21, 0.92)");
|
||||
} else if (selectedColor === "#121215") {
|
||||
setBgBoxColor("rgba(255, 255, 255, 0.92)");
|
||||
} else {
|
||||
setBgBoxColor(selectedColor);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Font Değiştir
|
||||
const handleSelectFont = (fId: string) => {
|
||||
try {
|
||||
Haptics.selectionAsync();
|
||||
} catch {}
|
||||
setFontId(fId);
|
||||
};
|
||||
|
||||
// Bitti / Kaydet
|
||||
const handleDone = () => {
|
||||
try {
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
|
||||
} catch {}
|
||||
|
||||
let finalBg = bgBoxColor;
|
||||
if (bgStyle === "none" || bgStyle === "outline") {
|
||||
finalBg = undefined;
|
||||
}
|
||||
|
||||
onSave({
|
||||
text: text.trim() ? text : "Metin",
|
||||
fontId,
|
||||
color,
|
||||
fontSize,
|
||||
textAlign,
|
||||
bgBoxColor: finalBg,
|
||||
bgStyle,
|
||||
curveDegree,
|
||||
hasShadow: bgStyle === "outline" || (!finalBg && color !== "#121215"),
|
||||
});
|
||||
onClose();
|
||||
};
|
||||
|
||||
const fontFamily = getCanvasFontFamily(fontId);
|
||||
|
||||
if (!visible || !layer) return null;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
transparent
|
||||
animationType="fade"
|
||||
statusBarTranslucent
|
||||
onRequestClose={onClose}
|
||||
>
|
||||
<View style={styles.overlayBackdrop}>
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||||
style={[styles.container, { paddingTop: insets.top + 8, paddingBottom: insets.bottom + 12 }]}
|
||||
>
|
||||
{/* ÜST BAR - INSTAGRAM STORY KONTROLLERİ */}
|
||||
<View style={styles.topBar}>
|
||||
{/* Sol: İptal / Kapat */}
|
||||
<Pressable
|
||||
onPress={onClose}
|
||||
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
||||
style={styles.iconButton}
|
||||
>
|
||||
<Ionicons name="close" size={24} color="#FFFFFF" />
|
||||
</Pressable>
|
||||
|
||||
{/* Orta Araçlar: Hizalama & Arka Plan Rozeti */}
|
||||
<View style={styles.centerTools}>
|
||||
{/* Hizalama Butonu */}
|
||||
<Pressable
|
||||
onPress={handleToggleAlign}
|
||||
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
||||
style={styles.toolPill}
|
||||
>
|
||||
<Ionicons
|
||||
name={
|
||||
textAlign === "left"
|
||||
? "reorder-three-outline"
|
||||
: textAlign === "right"
|
||||
? "reorder-three-outline"
|
||||
: "reorder-two-outline"
|
||||
}
|
||||
size={20}
|
||||
color="#FFFFFF"
|
||||
/>
|
||||
</Pressable>
|
||||
|
||||
{/* Instagram "A" Arka Plan Butonu */}
|
||||
<Pressable
|
||||
onPress={handleToggleBgStyle}
|
||||
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
||||
style={[
|
||||
styles.toolPill,
|
||||
bgStyle !== "none" && { backgroundColor: "#FFFFFF" },
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.bgPillText,
|
||||
bgStyle !== "none" && { color: "#0C0C0E", fontWeight: "900" },
|
||||
]}
|
||||
>
|
||||
A
|
||||
</Text>
|
||||
{bgStyle === "soft" && (
|
||||
<View style={styles.softIndicator} />
|
||||
)}
|
||||
{bgStyle === "outline" && (
|
||||
<View style={styles.outlineIndicator} />
|
||||
)}
|
||||
</Pressable>
|
||||
|
||||
{/* Kavis (Yay / Eğri Yazı) Toggle Butonu */}
|
||||
<Pressable
|
||||
onPress={handleToggleCurveDegree}
|
||||
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
||||
style={[
|
||||
styles.toolPill,
|
||||
curveDegree !== 0 && { backgroundColor: "#D4AF37" },
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.bgPillText,
|
||||
{ fontSize: 14 },
|
||||
curveDegree !== 0 ? { color: "#0C0C0E", fontWeight: "900" } : { color: "#FFFFFF" },
|
||||
]}
|
||||
>
|
||||
{curveDegree > 0 ? "◠" : curveDegree < 0 ? "◡" : "⌒"}
|
||||
</Text>
|
||||
</Pressable>
|
||||
|
||||
{/* Katmanı Sil (Varsa) */}
|
||||
{onDelete && (
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
try {
|
||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning);
|
||||
} catch {}
|
||||
onDelete(layer.id);
|
||||
onClose();
|
||||
}}
|
||||
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
||||
style={[styles.toolPill, { backgroundColor: "rgba(239, 68, 68, 0.2)" }]}
|
||||
>
|
||||
<Ionicons name="trash-outline" size={17} color="#EF4444" />
|
||||
</Pressable>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Sağ: BİTTİ (Done) Butonu */}
|
||||
<Pressable
|
||||
onPress={handleDone}
|
||||
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
||||
style={styles.doneButton}
|
||||
>
|
||||
<Text style={styles.doneButtonText}>Bitti</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
{/* ORTA BÖLÜM: SOL BOYUT SLIDER & CANLI METİN ALANI */}
|
||||
<View style={styles.editorBody}>
|
||||
{/* Sol: Dikey Boyut Stepper / Gösterge */}
|
||||
<View style={styles.sizeControlCol}>
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
try { Haptics.selectionAsync(); } catch {}
|
||||
setFontSize((s) => Math.min(48, s + 2));
|
||||
}}
|
||||
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
||||
style={styles.sizeStepBtn}
|
||||
>
|
||||
<Ionicons name="add" size={16} color="#FFFFFF" />
|
||||
</Pressable>
|
||||
|
||||
<View style={styles.sizeTrack}>
|
||||
<View
|
||||
style={[
|
||||
styles.sizeTrackFill,
|
||||
{ height: `${Math.min(100, Math.max(10, ((fontSize - 8) / 40) * 100))}%` },
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
try { Haptics.selectionAsync(); } catch {}
|
||||
setFontSize((s) => Math.max(8, s - 2));
|
||||
}}
|
||||
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
||||
style={styles.sizeStepBtn}
|
||||
>
|
||||
<Ionicons name="remove" size={16} color="#FFFFFF" />
|
||||
</Pressable>
|
||||
|
||||
<Text style={styles.sizeLabel}>{fontSize}pt</Text>
|
||||
</View>
|
||||
|
||||
{/* Orta: Canlı İnstagram Yazı Alanı */}
|
||||
<View style={styles.textInputArea}>
|
||||
<View
|
||||
style={[
|
||||
styles.textPillWrapper,
|
||||
bgStyle === "solid" && {
|
||||
backgroundColor: bgBoxColor || "#D4AF37",
|
||||
borderRadius: 14,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
},
|
||||
bgStyle === "soft" && {
|
||||
backgroundColor: "rgba(255, 255, 255, 0.22)",
|
||||
borderRadius: 14,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
borderWidth: 1,
|
||||
borderColor: "rgba(255, 255, 255, 0.3)",
|
||||
},
|
||||
bgStyle === "outline" && {
|
||||
borderWidth: 1.5,
|
||||
borderColor: color,
|
||||
borderRadius: 14,
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 6,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<TextInput
|
||||
ref={inputRef}
|
||||
value={text}
|
||||
onChangeText={setText}
|
||||
multiline
|
||||
placeholder="Yazmaya başla..."
|
||||
placeholderTextColor="rgba(255,255,255,0.4)"
|
||||
textAlign={textAlign}
|
||||
autoCapitalize="sentences"
|
||||
style={[
|
||||
styles.textInput,
|
||||
{
|
||||
fontFamily,
|
||||
fontSize,
|
||||
color:
|
||||
bgStyle === "solid" && (bgBoxColor === "#FFFFFF" || bgBoxColor === "#F6EFE3")
|
||||
? "#0C0C0E"
|
||||
: color,
|
||||
textAlign,
|
||||
letterSpacing: fontId === "serif" ? 1.5 : 0.5,
|
||||
textShadowColor: bgStyle === "outline" ? color : "rgba(0,0,0,0.7)",
|
||||
textShadowOffset: { width: 0, height: 1 },
|
||||
textShadowRadius: bgStyle === "outline" ? 8 : 4,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* ALT BÖLÜM 0: KAVİS HASSAS AYAR BARI */}
|
||||
<View style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", paddingHorizontal: 16, paddingVertical: 4, gap: 10 }}>
|
||||
<Text style={{ color: "#A1A1AA", fontSize: 11, fontWeight: "700" }}>KAVİS:</Text>
|
||||
<View style={{ flexDirection: "row", alignItems: "center", backgroundColor: "rgba(255,255,255,0.1)", borderRadius: 14, paddingHorizontal: 6, paddingVertical: 3, gap: 8 }}>
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
try { Haptics.selectionAsync(); } catch {}
|
||||
setCurveDegree((deg) => Math.max(-100, deg - 5));
|
||||
}}
|
||||
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
||||
style={{ width: 26, height: 26, borderRadius: 13, backgroundColor: "rgba(255,255,255,0.15)", alignItems: "center", justifyContent: "center" }}
|
||||
>
|
||||
<Ionicons name="remove" size={15} color="#FFFFFF" />
|
||||
</Pressable>
|
||||
|
||||
<Text style={{ color: "#D4AF37", fontSize: 13, fontWeight: "900", minWidth: 36, textAlign: "center" }}>
|
||||
{curveDegree}°
|
||||
</Text>
|
||||
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
try { Haptics.selectionAsync(); } catch {}
|
||||
setCurveDegree((deg) => Math.min(100, deg + 5));
|
||||
}}
|
||||
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
||||
style={{ width: 26, height: 26, borderRadius: 13, backgroundColor: "rgba(255,255,255,0.15)", alignItems: "center", justifyContent: "center" }}
|
||||
>
|
||||
<Ionicons name="add" size={15} color="#FFFFFF" />
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ gap: 6 }}>
|
||||
{[
|
||||
{ label: "Düz (0°)", deg: 0 },
|
||||
{ label: "25° ◠", deg: 25 },
|
||||
{ label: "45° ◠", deg: 45 },
|
||||
{ label: "65° ◠", deg: 65 },
|
||||
{ label: "-35° ◡", deg: -35 },
|
||||
].map((item) => {
|
||||
const isActive = curveDegree === item.deg;
|
||||
return (
|
||||
<Pressable
|
||||
key={item.deg}
|
||||
onPress={() => {
|
||||
try { Haptics.selectionAsync(); } catch {}
|
||||
setCurveDegree(item.deg);
|
||||
}}
|
||||
style={{
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 5,
|
||||
borderRadius: 12,
|
||||
backgroundColor: isActive ? "#D4AF37" : "rgba(255,255,255,0.1)",
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: isActive ? "#0C0C0E" : "#FFFFFF", fontSize: 11, fontWeight: isActive ? "800" : "600" }}>
|
||||
{item.label}
|
||||
</Text>
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
{/* ALT BÖLÜM 1: RENK SEÇİCİ BALONCUKLAR */}
|
||||
<View style={styles.colorPickerBar}>
|
||||
<ScrollView
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={styles.colorScrollContent}
|
||||
>
|
||||
{CANVAS_COLOR_PALETTE.map((c) => {
|
||||
const isSelected = color === c.color;
|
||||
return (
|
||||
<Pressable
|
||||
key={c.id}
|
||||
onPress={() => handleSelectColor(c.color)}
|
||||
style={[
|
||||
styles.colorCircleWrapper,
|
||||
isSelected && { borderColor: "#FFFFFF", transform: [{ scale: 1.15 }] },
|
||||
]}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.colorCircle,
|
||||
{ backgroundColor: c.color },
|
||||
c.color === "#121215" && { borderWidth: 1, borderColor: "rgba(255,255,255,0.4)" },
|
||||
]}
|
||||
/>
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
{/* ALT BÖLÜM 2: INSTAGRAM FONT STİLLERİ CAROUSEL */}
|
||||
<View style={styles.fontPickerBar}>
|
||||
<ScrollView
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={styles.fontScrollContent}
|
||||
>
|
||||
{CANVAS_FONT_OPTIONS.map((f) => {
|
||||
const isSelected = fontId === f.id;
|
||||
return (
|
||||
<Pressable
|
||||
key={f.id}
|
||||
onPress={() => handleSelectFont(f.id)}
|
||||
style={[
|
||||
styles.fontPill,
|
||||
isSelected && styles.fontPillActive,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.fontPillText,
|
||||
{ fontFamily: f.fontFamily },
|
||||
isSelected && styles.fontPillTextActive,
|
||||
]}
|
||||
>
|
||||
{f.label}
|
||||
</Text>
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
overlayBackdrop: {
|
||||
flex: 1,
|
||||
backgroundColor: "rgba(10, 10, 14, 0.94)",
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: "space-between",
|
||||
},
|
||||
topBar: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
paddingHorizontal: 16,
|
||||
height: 52,
|
||||
zIndex: 10,
|
||||
},
|
||||
iconButton: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 20,
|
||||
backgroundColor: "rgba(255, 255, 255, 0.12)",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
centerTools: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 10,
|
||||
},
|
||||
toolPill: {
|
||||
width: 38,
|
||||
height: 38,
|
||||
borderRadius: 19,
|
||||
backgroundColor: "rgba(255, 255, 255, 0.12)",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
position: "relative",
|
||||
},
|
||||
bgPillText: {
|
||||
color: "#FFFFFF",
|
||||
fontSize: 16,
|
||||
fontWeight: "700",
|
||||
},
|
||||
softIndicator: {
|
||||
position: "absolute",
|
||||
bottom: 4,
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: 3,
|
||||
backgroundColor: "#60A5FA",
|
||||
},
|
||||
outlineIndicator: {
|
||||
position: "absolute",
|
||||
bottom: 4,
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: 3,
|
||||
backgroundColor: "#F59E0B",
|
||||
},
|
||||
doneButton: {
|
||||
backgroundColor: "#FFFFFF",
|
||||
paddingHorizontal: 18,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 20,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
doneButtonText: {
|
||||
color: "#0C0C0E",
|
||||
fontSize: 14,
|
||||
fontWeight: "800",
|
||||
},
|
||||
editorBody: {
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
sizeControlCol: {
|
||||
width: 36,
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
},
|
||||
sizeStepBtn: {
|
||||
width: 30,
|
||||
height: 30,
|
||||
borderRadius: 15,
|
||||
backgroundColor: "rgba(255, 255, 255, 0.15)",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
sizeTrack: {
|
||||
width: 4,
|
||||
height: 120,
|
||||
backgroundColor: "rgba(255, 255, 255, 0.18)",
|
||||
borderRadius: 2,
|
||||
justifyContent: "flex-end",
|
||||
overflow: "hidden",
|
||||
},
|
||||
sizeTrackFill: {
|
||||
width: "100%",
|
||||
backgroundColor: "#D4AF37",
|
||||
borderRadius: 2,
|
||||
},
|
||||
sizeLabel: {
|
||||
color: "rgba(255, 255, 255, 0.6)",
|
||||
fontSize: 10,
|
||||
fontWeight: "700",
|
||||
},
|
||||
textInputArea: {
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 12,
|
||||
},
|
||||
textPillWrapper: {
|
||||
maxWidth: "100%",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
textInput: {
|
||||
minWidth: 140,
|
||||
maxWidth: "100%",
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
fontWeight: "800",
|
||||
},
|
||||
colorPickerBar: {
|
||||
paddingVertical: 10,
|
||||
},
|
||||
colorScrollContent: {
|
||||
paddingHorizontal: 20,
|
||||
gap: 12,
|
||||
alignItems: "center",
|
||||
},
|
||||
colorCircleWrapper: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 16,
|
||||
borderWidth: 2,
|
||||
borderColor: "transparent",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
colorCircle: {
|
||||
width: 24,
|
||||
height: 24,
|
||||
borderRadius: 12,
|
||||
},
|
||||
fontPickerBar: {
|
||||
paddingVertical: 8,
|
||||
},
|
||||
fontScrollContent: {
|
||||
paddingHorizontal: 16,
|
||||
gap: 8,
|
||||
alignItems: "center",
|
||||
},
|
||||
fontPill: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 18,
|
||||
backgroundColor: "rgba(255, 255, 255, 0.08)",
|
||||
borderWidth: 1,
|
||||
borderColor: "rgba(255, 255, 255, 0.12)",
|
||||
},
|
||||
fontPillActive: {
|
||||
backgroundColor: "#FFFFFF",
|
||||
borderColor: "#FFFFFF",
|
||||
},
|
||||
fontPillText: {
|
||||
color: "rgba(255, 255, 255, 0.8)",
|
||||
fontSize: 12,
|
||||
fontWeight: "700",
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
fontPillTextActive: {
|
||||
color: "#0C0C0E",
|
||||
fontWeight: "900",
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
export * from "./types";
|
||||
export * from "./CanvasLayerItem";
|
||||
export * from "./CanvasQrItem";
|
||||
export * from "./InstagramTextEditorModal";
|
||||
export * from "./QrCanvasStudio";
|
||||
@@ -0,0 +1,309 @@
|
||||
import { Platform } from "react-native";
|
||||
|
||||
export interface CanvasTextLayer {
|
||||
id: string;
|
||||
text: string;
|
||||
x: number; // px from canvas left
|
||||
y: number; // px from canvas top
|
||||
rotation: number; // degrees (-180 to 180)
|
||||
fontSize: number;
|
||||
fontId: string;
|
||||
color: string;
|
||||
bgBoxColor?: string; // Instagram badge kutusu
|
||||
bgStyle?: "none" | "solid" | "soft" | "outline";
|
||||
textAlign?: "left" | "center" | "right";
|
||||
hasShadow?: boolean;
|
||||
letterSpacing?: number;
|
||||
fontWeight?: "normal" | "600" | "700" | "800" | "900";
|
||||
curveDegree?: number; // -100 (Aşağı Kavis ◡) ile +100 (Yukarı Kavis ◠) arası kavis derecesi
|
||||
}
|
||||
|
||||
export interface CanvasQrConfig {
|
||||
x: number;
|
||||
y: number;
|
||||
size: number;
|
||||
borderRadius: number;
|
||||
borderWidth: number;
|
||||
borderColor: string;
|
||||
padding: number;
|
||||
bgBoxColor: string;
|
||||
}
|
||||
|
||||
export const CANVAS_FONT_OPTIONS = [
|
||||
{
|
||||
id: "sans",
|
||||
label: "MODERN",
|
||||
fontFamily: Platform.OS === "ios" ? "System" : "sans-serif",
|
||||
},
|
||||
{
|
||||
id: "serif",
|
||||
label: "ZARİF",
|
||||
fontFamily: Platform.OS === "ios" ? "Georgia" : "serif",
|
||||
},
|
||||
{
|
||||
id: "times",
|
||||
label: "KLASİK",
|
||||
fontFamily: Platform.OS === "ios" ? "Times New Roman" : "serif",
|
||||
},
|
||||
{
|
||||
id: "typewriter",
|
||||
label: "DAKTİLO",
|
||||
fontFamily: Platform.OS === "ios" ? "Courier" : "monospace",
|
||||
},
|
||||
{
|
||||
id: "palatino",
|
||||
label: "LÜKS",
|
||||
fontFamily: Platform.OS === "ios" ? "Palatino" : "serif",
|
||||
},
|
||||
{
|
||||
id: "trebuchet",
|
||||
label: "DİNAMİK",
|
||||
fontFamily: Platform.OS === "ios" ? "Trebuchet MS" : "sans-serif",
|
||||
},
|
||||
{
|
||||
id: "heavy",
|
||||
label: "GÜÇLÜ",
|
||||
fontFamily: Platform.OS === "ios" ? "Impact" : "sans-serif-black",
|
||||
},
|
||||
];
|
||||
|
||||
export function getCanvasFontFamily(fontId?: string): string {
|
||||
if (!fontId) return Platform.OS === "ios" ? "System" : "sans-serif";
|
||||
const found = CANVAS_FONT_OPTIONS.find((f) => f.id === fontId);
|
||||
if (found) {
|
||||
return found.fontFamily;
|
||||
}
|
||||
if (fontId.includes(",")) {
|
||||
const first = fontId.split(",")[0].trim().replace(/['"]/g, "");
|
||||
return first || (Platform.OS === "ios" ? "System" : "sans-serif");
|
||||
}
|
||||
return fontId;
|
||||
}
|
||||
|
||||
export const CANVAS_COLOR_PALETTE = [
|
||||
{ id: "gold", color: "#D4AF37", label: "Altın" },
|
||||
{ id: "white", color: "#FFFFFF", label: "Beyaz" },
|
||||
{ id: "black", color: "#121215", label: "Siyah" },
|
||||
{ id: "cream", color: "#F6EFE3", label: "Krem" },
|
||||
{ id: "emerald", color: "#10B981", label: "Yeşil" },
|
||||
{ id: "crimson", color: "#EF4444", label: "Kırmızı" },
|
||||
{ id: "amber", color: "#F59E0B", label: "Amber" },
|
||||
{ id: "rose", color: "#EC4899", label: "Pembe" },
|
||||
{ id: "indigo", color: "#6366F1", label: "Lila" },
|
||||
{ id: "cyan", color: "#06B6D4", label: "Turkuaz" },
|
||||
];
|
||||
|
||||
export const INSTAGRAM_BADGE_STYLES = [
|
||||
{ id: "none", label: "Düz", bgBoxColor: undefined },
|
||||
{ id: "dark", label: "Koyu", bgBoxColor: "rgba(18, 18, 21, 0.88)" },
|
||||
{ id: "gold", label: "Altın", bgBoxColor: "rgba(212, 175, 55, 0.92)" },
|
||||
{ id: "white", label: "Beyaz", bgBoxColor: "rgba(255, 255, 255, 0.92)" },
|
||||
{ id: "glass", label: "Cam", bgBoxColor: "rgba(255, 255, 255, 0.2)" },
|
||||
];
|
||||
|
||||
export const QR_SIZE_PRESETS = [
|
||||
{ label: "Kompakt", size: 85 },
|
||||
{ label: "Standart", size: 115 },
|
||||
{ label: "Büyük", size: 145 },
|
||||
{ label: "Geniş", size: 175 },
|
||||
];
|
||||
|
||||
export const QR_RADIUS_PRESETS = [
|
||||
{ label: "Düz", radius: 0 },
|
||||
{ label: "Hafif", radius: 10 },
|
||||
{ label: "Yuvarlak", radius: 18 },
|
||||
{ label: "Oval", radius: 28 },
|
||||
];
|
||||
|
||||
export const QR_BORDER_COLORS = [
|
||||
{ id: "gold", color: "#D4AF37", label: "Altın" },
|
||||
{ id: "black", color: "#121215", label: "Siyah" },
|
||||
{ id: "white", color: "#FFFFFF", label: "Beyaz" },
|
||||
{ id: "none", color: "transparent", label: "Yok" },
|
||||
];
|
||||
|
||||
export function getDefaultQrConfigForTemplate(
|
||||
templateKey: string,
|
||||
canvasWidth: number,
|
||||
canvasHeight: number
|
||||
): CanvasQrConfig {
|
||||
if (templateKey === "qr-1" || templateKey === "qr-8" || templateKey === "qr-9") {
|
||||
const size = 110;
|
||||
return {
|
||||
x: Math.round((canvasWidth - size) / 2),
|
||||
y: 102,
|
||||
size,
|
||||
borderRadius: 14,
|
||||
borderWidth: 1.5,
|
||||
borderColor: "#D4AF37",
|
||||
padding: 6,
|
||||
bgBoxColor: "#FFFFFF",
|
||||
};
|
||||
}
|
||||
|
||||
// Standart Dikey Şablonlar (qr-2, qr-3, qr-4, qr-5, qr-6)
|
||||
const size = 125;
|
||||
return {
|
||||
x: Math.round((canvasWidth - size) / 2),
|
||||
y: Math.round(canvasHeight * 0.36),
|
||||
size,
|
||||
borderRadius: 14,
|
||||
borderWidth: 1.5,
|
||||
borderColor: "#D4AF37",
|
||||
padding: 6,
|
||||
bgBoxColor: "#FFFFFF",
|
||||
};
|
||||
}
|
||||
|
||||
// Her şablon için başlangıç varsayılan katmanları (1:1 veya standart orana göre)
|
||||
export function getDefaultLayersForTemplate(templateKey: string, restaurantName = "Gourmet Bistro"): CanvasTextLayer[] {
|
||||
if (templateKey === "qr-1") {
|
||||
return [
|
||||
{
|
||||
id: "rName",
|
||||
text: restaurantName.toUpperCase(),
|
||||
x: 40,
|
||||
y: 50,
|
||||
rotation: 0,
|
||||
fontSize: 18,
|
||||
fontId: "serif",
|
||||
color: "#D4AF37",
|
||||
fontWeight: "800",
|
||||
letterSpacing: 2,
|
||||
textAlign: "center",
|
||||
},
|
||||
{
|
||||
id: "sub",
|
||||
text: "RESTAURANT & BAR",
|
||||
x: 65,
|
||||
y: 78,
|
||||
rotation: 0,
|
||||
fontSize: 9,
|
||||
fontId: "sans",
|
||||
color: "#E5C378",
|
||||
fontWeight: "600",
|
||||
letterSpacing: 1.5,
|
||||
textAlign: "center",
|
||||
},
|
||||
{
|
||||
id: "cta",
|
||||
text: "MENÜYÜ GÖRMEK İÇİN OKUTUN",
|
||||
x: 35,
|
||||
y: 236,
|
||||
rotation: 0,
|
||||
fontSize: 10,
|
||||
fontId: "serif",
|
||||
color: "#D4AF37",
|
||||
fontWeight: "700",
|
||||
letterSpacing: 1,
|
||||
textAlign: "center",
|
||||
},
|
||||
{
|
||||
id: "table",
|
||||
text: "MASA NO: 01 • WiFi: Bistro",
|
||||
x: 45,
|
||||
y: 265,
|
||||
rotation: 0,
|
||||
fontSize: 9,
|
||||
fontId: "sans",
|
||||
color: "#C5A059",
|
||||
fontWeight: "600",
|
||||
textAlign: "center",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (templateKey === "qr-8") {
|
||||
return [
|
||||
{
|
||||
id: "rName",
|
||||
text: "EXPLORE OUR MENU",
|
||||
x: 35,
|
||||
y: 35,
|
||||
rotation: 0,
|
||||
fontSize: 16,
|
||||
fontId: "sans",
|
||||
color: "#FFFFFF",
|
||||
fontWeight: "900",
|
||||
letterSpacing: 1,
|
||||
textAlign: "center",
|
||||
},
|
||||
{
|
||||
id: "sub",
|
||||
text: "THE DAILY SPECIALS",
|
||||
x: 50,
|
||||
y: 68,
|
||||
rotation: -4,
|
||||
fontSize: 13,
|
||||
fontId: "sans",
|
||||
color: "#D1A752",
|
||||
fontWeight: "800",
|
||||
textAlign: "center",
|
||||
},
|
||||
{
|
||||
id: "cta",
|
||||
text: "SCAN TO VIEW\nFULL MENU & SPECIALS",
|
||||
x: 55,
|
||||
y: 215,
|
||||
rotation: 0,
|
||||
fontSize: 9.5,
|
||||
fontId: "sans",
|
||||
color: "#FFFFFF",
|
||||
fontWeight: "800",
|
||||
textAlign: "center",
|
||||
},
|
||||
{
|
||||
id: "wifi",
|
||||
text: "SCAN TO ORDER & BROWSE",
|
||||
x: 55,
|
||||
y: 258,
|
||||
rotation: 0,
|
||||
fontSize: 9,
|
||||
fontId: "sans",
|
||||
color: "#D1A752",
|
||||
fontWeight: "800",
|
||||
textAlign: "center",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// Varsayılan
|
||||
return [
|
||||
{
|
||||
id: "rName",
|
||||
text: restaurantName,
|
||||
x: 50,
|
||||
y: 45,
|
||||
rotation: 0,
|
||||
fontSize: 17,
|
||||
fontId: "serif",
|
||||
color: "#D4AF37",
|
||||
fontWeight: "800",
|
||||
textAlign: "center",
|
||||
},
|
||||
{
|
||||
id: "sub",
|
||||
text: "SCAN TO ORDER",
|
||||
x: 75,
|
||||
y: 75,
|
||||
rotation: 0,
|
||||
fontSize: 10,
|
||||
fontId: "sans",
|
||||
color: "#FFFFFF",
|
||||
fontWeight: "700",
|
||||
textAlign: "center",
|
||||
},
|
||||
{
|
||||
id: "cta",
|
||||
text: "DİJİTAL MENÜ",
|
||||
x: 75,
|
||||
y: 245,
|
||||
rotation: 0,
|
||||
fontSize: 11,
|
||||
fontId: "serif",
|
||||
color: "#D4AF37",
|
||||
fontWeight: "800",
|
||||
textAlign: "center",
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
import * as React from "react";
|
||||
import Svg, {
|
||||
Path,
|
||||
Rect,
|
||||
Circle,
|
||||
G,
|
||||
Ellipse,
|
||||
Text,
|
||||
Image,
|
||||
} from "react-native-svg";
|
||||
import { type QrTemplateProps, getFontFamily, renderSvgLines } from "./common";
|
||||
|
||||
const QrTemplate2 = ({
|
||||
qrBase64,
|
||||
fields,
|
||||
hideQr,
|
||||
...props
|
||||
}: QrTemplateProps) => {
|
||||
const rName = fields?.restaurantName;
|
||||
const sub = fields?.subtitle;
|
||||
const table = fields?.tableNo;
|
||||
const cta = fields?.ctaText;
|
||||
const wifi = fields?.wifiText;
|
||||
const social = fields?.socialText;
|
||||
|
||||
const rNameFont = getFontFamily(rName?.fontId, "Georgia, 'Times New Roman', serif");
|
||||
const subFont = getFontFamily(sub?.fontId, "System, -apple-system, Roboto, sans-serif");
|
||||
const ctaFont = getFontFamily(cta?.fontId, "Georgia, 'Times New Roman', serif");
|
||||
const infoFont = getFontFamily(wifi?.fontId, "System, -apple-system, Roboto, sans-serif");
|
||||
|
||||
return (
|
||||
<Svg viewBox="0 0 400 650" width={300} height={487.5} {...props}>
|
||||
{/* Arka Plan */}
|
||||
<Path fill="#252c36" d="M0 0h400v650H0z" />
|
||||
|
||||
{/* Ana Kart */}
|
||||
<Rect
|
||||
width={360}
|
||||
height={610}
|
||||
x={20}
|
||||
y={20}
|
||||
fill="#f6efe2"
|
||||
stroke="#3a2312"
|
||||
strokeWidth={1.5}
|
||||
rx={20}
|
||||
/>
|
||||
<Rect
|
||||
width={344}
|
||||
height={594}
|
||||
x={28}
|
||||
y={28}
|
||||
fill="none"
|
||||
stroke="#8c6239"
|
||||
strokeOpacity={0.4}
|
||||
rx={14}
|
||||
/>
|
||||
<Rect
|
||||
width={336}
|
||||
height={586}
|
||||
x={32}
|
||||
y={32}
|
||||
fill="none"
|
||||
stroke="#8c6239"
|
||||
strokeOpacity={0.25}
|
||||
strokeWidth={0.5}
|
||||
rx={10}
|
||||
/>
|
||||
|
||||
{/* Sol Üst Botanik Dal */}
|
||||
<G fill="none" stroke="#3a2312" strokeLinecap="round" strokeWidth={1.05}>
|
||||
<Path d="M36 36c11.25 11.25 18.75 26.25 22.5 48.75" />
|
||||
<Path
|
||||
fill="#eadcc7"
|
||||
d="M43.5 45c-3.75-7.5 7.5-10.5 9-3 0 7.5-9 3-9 3Zm6 11.25C45 51 57 46.5 60 53.25c0 6.75-10.5 3-10.5 3Zm4.5 13.5C60 64.5 69.75 72 64.5 78c-6 4.5-10.5-8.25-10.5-8.25Z"
|
||||
/>
|
||||
</G>
|
||||
|
||||
{/* Sağ Üst Kahve İkonu */}
|
||||
<G transform="translate(340 52)">
|
||||
<Circle r={32} fill="#eadcc7" stroke="#3a2312" strokeWidth={1.2} />
|
||||
<Circle
|
||||
r={26}
|
||||
fill="#f6efe2"
|
||||
stroke="#8c6239"
|
||||
strokeDasharray="2 2"
|
||||
strokeWidth={0.8}
|
||||
/>
|
||||
<Circle r={21} fill="#7a4b26" stroke="#3a2312" strokeWidth={1.4} />
|
||||
<Path fill="#f6efe2" d="M0 10C-8 4-4-6 0-12 4-6 8 4 0 10" />
|
||||
<Path fill="#7a4b26" d="M0 4C-5 0-2-5 0-8 2-5 5 0 0 4" />
|
||||
<Circle cy={-14} r={1.8} fill="#f6efe2" />
|
||||
</G>
|
||||
|
||||
{/* Sol Orta Kahve Bardağı */}
|
||||
<G transform="translate(48 250)">
|
||||
<Circle r={26} fill="#eadcc7" stroke="#3a2312" />
|
||||
<Circle r={17} fill="#6a3b18" stroke="#3a2312" strokeWidth={1.2} />
|
||||
<Path fill="#f6efe2" d="M-5-3c-3-4 5-7 5-1 0-6 8-3 5 1L0 7z" />
|
||||
<Path fill="none" stroke="#3a2312" strokeWidth={1.5} d="M17-4c6 0 6 8 0 8" />
|
||||
</G>
|
||||
|
||||
{/* Sağ Dal */}
|
||||
<G fill="none" stroke="#3a2312" strokeWidth={0.91}>
|
||||
<Path d="M352 230c-7 21-10.5 49-3.5 77" />
|
||||
<Path
|
||||
fill="#eadcc7"
|
||||
d="M349.2 247.5c8.4-7 15.4 2.1 5.6 7.7-5.6 0-5.6-7.7-5.6-7.7Zm-2.8 24.5c-9.8-8.4-15.4 1.4-7 7 7 0 7-7 7-7Zm2.1 19.6c9.1-7 13.3 2.8 4.9 8.4-4.9 0-4.9-8.4-4.9-8.4Z"
|
||||
/>
|
||||
</G>
|
||||
|
||||
{/* Dekoratif Çizgi */}
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#8c6239"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={1.2}
|
||||
d="M142 66c6-2 12 2 18 0m80 0c6 2 12-2 18 0"
|
||||
/>
|
||||
|
||||
{/* Sol Alt Dal */}
|
||||
<G fill="none" stroke="#3a2312" strokeWidth={0.91}>
|
||||
<Path d="M42 601.5c13-26 9.75-52 0-71.5" />
|
||||
<Path
|
||||
fill="#eadcc7"
|
||||
d="M47.2 549.5c10.4-9.75 17.55 0 6.5 7.8Zm2.6 26c11.7-9.75 18.2 1.3 5.2 9.1Z"
|
||||
/>
|
||||
</G>
|
||||
|
||||
{/* Kahve Çekirdekleri */}
|
||||
<G stroke="#3a2312" strokeWidth={1.2} transform="rotate(-20 1741.206 -8.75) scale(.8)">
|
||||
<Ellipse fill="#cbb296" rx={9} ry={6.5} />
|
||||
<Path fill="none" d="M-7 0q7 3 7 0t7 0" />
|
||||
</G>
|
||||
<G stroke="#3a2312" strokeWidth={1.2} transform="rotate(35 -862.106 494.982) scale(.8)">
|
||||
<Ellipse fill="#cbb296" rx={9} ry={6.5} />
|
||||
<Path fill="none" d="M-7 0q7 3 7 0t7 0" />
|
||||
</G>
|
||||
<G stroke="#3a2312" strokeWidth={1.2} transform="rotate(-45 602.558 -225.588) scale(.7)">
|
||||
<Ellipse fill="#cbb296" rx={9} ry={6.5} />
|
||||
<Path fill="none" d="M-7 0q7 3 7 0t7 0" />
|
||||
</G>
|
||||
|
||||
{/* Sağ Alt Fincan */}
|
||||
<G transform="translate(325 590)">
|
||||
<Ellipse cy={18} fill="#eadcc7" stroke="#3a2312" strokeWidth={1.2} rx={28} ry={7} />
|
||||
<Path fill="#f6efe2" stroke="#3a2312" strokeWidth={1.4} d="M-18 2c0 14 36 14 36 0Z" />
|
||||
<Ellipse cy={2} fill="#6a3b18" stroke="#3a2312" strokeWidth={1.2} rx={18} ry={4} />
|
||||
<Path fill="none" stroke="#3a2312" strokeWidth={1.3} d="M16 4c8 0 8 8 0 10" />
|
||||
<Path fill="none" stroke="#8c6239" strokeLinecap="round" d="M-6-4c-2-5 2-8 0-13M2-5c-2-5 2-8 0-14" />
|
||||
</G>
|
||||
|
||||
{/* QR Köşe Çerçeveleri */}
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#3a2312"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={2}
|
||||
d="M80 205c0-10 0-10 10-10m230 10c0-10 0-10-10-10M80 445c0 10 0 10 10 10m230-10c0 10 0 10-10 10"
|
||||
/>
|
||||
<Path stroke="#8c6239" strokeWidth={0.8} d="M145 608h30m50 0h30" />
|
||||
|
||||
{/* QR Placeholder / Gerçek QR */}
|
||||
{!hideQr && (
|
||||
<G transform="translate(90 215)">
|
||||
<Rect width={220} height={220} fill="#fbf8f2" stroke="#d8c7b0" strokeWidth={1.5} rx={20} />
|
||||
{qrBase64 ? (
|
||||
<Image
|
||||
href={`data:image/png;base64,${qrBase64}`}
|
||||
x={10}
|
||||
y={10}
|
||||
width={200}
|
||||
height={200}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<Rect
|
||||
width={200}
|
||||
height={200}
|
||||
x={10}
|
||||
y={10}
|
||||
fill="#f5efe6"
|
||||
stroke="#d8c7b0"
|
||||
strokeDasharray="4 3"
|
||||
rx={14}
|
||||
/>
|
||||
<Circle cx={110} cy={98} r={18} fill="#fbf8f2" stroke="#8c6239" />
|
||||
<G fill="#3a2312" transform="matrix(.65 0 0 .65 110 98)">
|
||||
<Ellipse cx={-5} cy={2} rx={7} ry={5} transform="rotate(-30)" />
|
||||
<Ellipse cx={5} rx={7} ry={5} transform="rotate(40)" />
|
||||
</G>
|
||||
<Text
|
||||
x={110}
|
||||
y={132}
|
||||
fill="#8c6239"
|
||||
fontFamily="System, sans-serif"
|
||||
fontSize={11}
|
||||
fontWeight={700}
|
||||
letterSpacing={1}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"QR KOD ALANI"}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</G>
|
||||
)}
|
||||
|
||||
{/* Metinler (Çok satırlı destekli) */}
|
||||
<G textAnchor="middle">
|
||||
<Text
|
||||
x={200}
|
||||
y={68}
|
||||
fill="#3a2312"
|
||||
fontFamily={rNameFont}
|
||||
fontSize={14}
|
||||
fontStyle="italic"
|
||||
fontWeight={700}
|
||||
letterSpacing={4}
|
||||
>
|
||||
{"THE"}
|
||||
</Text>
|
||||
|
||||
{/* Restoran Adı */}
|
||||
{rName?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: rName?.text || "The Daily Grind",
|
||||
x: 200,
|
||||
y: 110,
|
||||
offsetY: rName?.offsetY,
|
||||
fill: "#2c1a0e",
|
||||
fontFamily: rNameFont,
|
||||
fontSize: rName?.fontSize || 28,
|
||||
fontWeight: "bold",
|
||||
letterSpacing: 0.5,
|
||||
})}
|
||||
|
||||
{/* Slogan */}
|
||||
{sub?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: sub?.text || "COFFEE & KITCHEN",
|
||||
x: 200,
|
||||
y: 144,
|
||||
offsetY: sub?.offsetY,
|
||||
fill: "#3a2312",
|
||||
fontFamily: subFont,
|
||||
fontSize: sub?.fontSize || 12,
|
||||
fontWeight: 700,
|
||||
letterSpacing: 2.5,
|
||||
})}
|
||||
|
||||
{/* CTA */}
|
||||
{cta?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: cta?.text || "MENÜYÜ GÖRMEK İÇİN OKUTUN",
|
||||
x: 200,
|
||||
y: 486,
|
||||
offsetY: cta?.offsetY,
|
||||
fill: "#2c1a0e",
|
||||
fontFamily: ctaFont,
|
||||
fontSize: cta?.fontSize || 15,
|
||||
fontWeight: "bold",
|
||||
letterSpacing: 1.2,
|
||||
})}
|
||||
|
||||
{/* WiFi */}
|
||||
{wifi?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: wifi?.text || "WiFi: DailyGrind | Şifre: coffee2026",
|
||||
x: 200,
|
||||
y: 520,
|
||||
offsetY: wifi?.offsetY,
|
||||
fill: "#3a2312",
|
||||
fontFamily: infoFont,
|
||||
fontSize: wifi?.fontSize || 10.5,
|
||||
fontWeight: 700,
|
||||
letterSpacing: 0.5,
|
||||
})}
|
||||
|
||||
{/* Masa No & Sosyal */}
|
||||
{(table?.visible !== false || social?.visible !== false) &&
|
||||
renderSvgLines({
|
||||
text: [
|
||||
table?.visible !== false && table?.text ? table.text : "",
|
||||
social?.visible !== false && social?.text ? social.text : "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" • "),
|
||||
x: 200,
|
||||
y: 544,
|
||||
offsetY: table?.offsetY || social?.offsetY,
|
||||
fill: "#8c6239",
|
||||
fontFamily: infoFont,
|
||||
fontSize: table?.fontSize || social?.fontSize || 10,
|
||||
fontWeight: 600,
|
||||
letterSpacing: 1,
|
||||
})}
|
||||
|
||||
|
||||
<Text
|
||||
x={200}
|
||||
y={611}
|
||||
fill="#3a2312"
|
||||
fontFamily={rNameFont}
|
||||
fontSize={11}
|
||||
fontStyle="italic"
|
||||
fontWeight={700}
|
||||
letterSpacing={2}
|
||||
>
|
||||
{"EST. 2026"}
|
||||
</Text>
|
||||
</G>
|
||||
</Svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default QrTemplate2;
|
||||
@@ -0,0 +1,357 @@
|
||||
import * as React from "react";
|
||||
import Svg, {
|
||||
Defs,
|
||||
LinearGradient,
|
||||
Stop,
|
||||
Pattern,
|
||||
Path,
|
||||
G,
|
||||
Rect,
|
||||
Ellipse,
|
||||
Circle,
|
||||
Text,
|
||||
Image,
|
||||
} from "react-native-svg";
|
||||
import { type QrTemplateProps, getFontFamily, renderSvgLines } from "./common";
|
||||
|
||||
const QrTemplate3 = ({
|
||||
qrBase64,
|
||||
fields,
|
||||
hideQr,
|
||||
...props
|
||||
}: QrTemplateProps) => {
|
||||
const rName = fields?.restaurantName;
|
||||
const sub = fields?.subtitle;
|
||||
const table = fields?.tableNo;
|
||||
const cta = fields?.ctaText;
|
||||
const wifi = fields?.wifiText;
|
||||
const social = fields?.socialText;
|
||||
|
||||
const rNameFont = getFontFamily(rName?.fontId, "Georgia, 'Playfair Display', serif");
|
||||
const subFont = getFontFamily(sub?.fontId, "System, -apple-system, Roboto, sans-serif");
|
||||
const ctaFont = getFontFamily(cta?.fontId, "Georgia, 'Playfair Display', serif");
|
||||
const infoFont = getFontFamily(wifi?.fontId, "System, -apple-system, Roboto, sans-serif");
|
||||
|
||||
return (
|
||||
<Svg viewBox="0 0 400 650" width={300} height={487.5} {...props}>
|
||||
<Defs>
|
||||
<LinearGradient id="card-grad-3" x1="0%" x2="0%" y1="0%" y2="100%">
|
||||
<Stop offset="0%" stopColor="#fdfbf5" />
|
||||
<Stop offset="50%" stopColor="#faf4e6" />
|
||||
<Stop offset="100%" stopColor="#f7eed8" />
|
||||
</LinearGradient>
|
||||
<LinearGradient id="oven-fire-3" x1="0%" x2="0%" y1="100%" y2="0%">
|
||||
<Stop offset="0%" stopColor="#c0392b" />
|
||||
<Stop offset="50%" stopColor="#e67e22" />
|
||||
<Stop offset="100%" stopColor="#f1c40f" />
|
||||
</LinearGradient>
|
||||
<LinearGradient id="wine-grad-3" x1="0%" x2="0%" y1="0%" y2="100%">
|
||||
<Stop offset="0%" stopColor="#7b1123" />
|
||||
<Stop offset="100%" stopColor="#4a0512" />
|
||||
</LinearGradient>
|
||||
<Pattern
|
||||
id="checkered-border-3"
|
||||
width={20}
|
||||
height={20}
|
||||
patternUnits="userSpaceOnUse"
|
||||
>
|
||||
<Path fill="#b92b27" d="M0 0h20v20H0z" />
|
||||
<Path fill="#fdf8ee" d="M0 0h10v10H0zm10 10h10v10H10z" />
|
||||
</Pattern>
|
||||
</Defs>
|
||||
|
||||
{/* Arka Plan */}
|
||||
<Path fill="url(#checkered-border-3)" d="M0 0h400v650H0z" />
|
||||
|
||||
{/* Kart */}
|
||||
<G id="layer-card">
|
||||
<Rect
|
||||
width={356}
|
||||
height={606}
|
||||
x={22}
|
||||
y={22}
|
||||
fill="url(#card-grad-3)"
|
||||
stroke="#b92b27"
|
||||
strokeWidth={2}
|
||||
rx={20}
|
||||
/>
|
||||
<Rect
|
||||
width={344}
|
||||
height={594}
|
||||
x={28}
|
||||
y={28}
|
||||
fill="none"
|
||||
stroke="#8c6239"
|
||||
strokeOpacity={0.5}
|
||||
strokeWidth={0.8}
|
||||
rx={15}
|
||||
/>
|
||||
{table?.visible !== false && table?.text ? (
|
||||
<Rect
|
||||
width={140}
|
||||
height={24}
|
||||
x={130}
|
||||
y={34}
|
||||
fill="#faf4e6"
|
||||
stroke="#8c6239"
|
||||
strokeWidth={1.2}
|
||||
rx={12}
|
||||
/>
|
||||
) : null}
|
||||
</G>
|
||||
|
||||
{/* İkonlar */}
|
||||
<G id="layer-icons">
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c5a880"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={1.5}
|
||||
d="M68 88q12-13 28-2 6 5 10 2m226 0q-12-13-28-2-6 5-10 2"
|
||||
/>
|
||||
{/* Pizza Fırını */}
|
||||
<G id="pizza-oven" transform="translate(200 192)">
|
||||
<Rect
|
||||
width={88}
|
||||
height={6}
|
||||
x={-44}
|
||||
fill="#d7ccc8"
|
||||
stroke="#5d4037"
|
||||
rx={2}
|
||||
/>
|
||||
<Path
|
||||
fill="#d7ccc8"
|
||||
stroke="#5d4037"
|
||||
strokeWidth={1.5}
|
||||
d="M-40 0a40 38 0 0 1 80 0Z"
|
||||
/>
|
||||
<Path stroke="#8d6e63" d="m-30-25 7 6m6-15 4 8M0-38v9m17-5-4 8m17 1-7 6" />
|
||||
<Path fill="#3e2723" stroke="#271c19" d="M-28 0a28 26 0 0 1 56 0Z" />
|
||||
<Path fill="url(#oven-fire-3)" d="M-18 0q3-16 12-10 6-12 12-2 9-6 12 12Z" />
|
||||
<Path fill="#fff9c4" d="M-10 0q5-10 10-6 5-6 10 6Z" />
|
||||
</G>
|
||||
{/* Sol Malzemeler */}
|
||||
<G id="left-ingredients" transform="translate(108 185)">
|
||||
<Ellipse
|
||||
cx={-16}
|
||||
cy={6}
|
||||
fill="#d32f2f"
|
||||
stroke="#8e0000"
|
||||
strokeWidth={0.8}
|
||||
rx={14}
|
||||
ry={10}
|
||||
transform="rotate(-25 -16 6)"
|
||||
/>
|
||||
<Ellipse
|
||||
cx={-16}
|
||||
cy={6}
|
||||
fill="#e53935"
|
||||
rx={10}
|
||||
ry={6.5}
|
||||
transform="rotate(-25 -16 6)"
|
||||
/>
|
||||
<Ellipse
|
||||
cx={2}
|
||||
cy={7}
|
||||
fill="#c62828"
|
||||
stroke="#8e0000"
|
||||
strokeWidth={0.8}
|
||||
rx={13}
|
||||
ry={9}
|
||||
transform="rotate(-10 2 7)"
|
||||
/>
|
||||
<Ellipse cx={2} cy={7} fill="#e53935" rx={9} ry={5.5} transform="rotate(-10 2 7)" />
|
||||
<Circle cx={2} cy={7} r={1.5} fill="#fdd835" />
|
||||
<Circle cx={16} cy={-2} r={14} fill="#e53935" stroke="#b71c1c" strokeWidth={0.8} />
|
||||
<Ellipse cx={12} cy={-6} fill="#ff8a80" opacity={0.6} rx={4} ry={2} />
|
||||
<Path
|
||||
stroke="#2e7d32"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={1.5}
|
||||
d="M16-16v3m0 0-5 2m5-2 5 2m-5-2-3-2m3 2 3-2"
|
||||
/>
|
||||
</G>
|
||||
{/* Sağ Malzemeler */}
|
||||
<G id="right-ingredients" transform="translate(290 182)">
|
||||
<Circle cx={-14} cy={2} r={15} fill="#e53935" stroke="#b71c1c" strokeWidth={0.8} />
|
||||
<Ellipse cx={-18} cy={-3} fill="#ff8a80" opacity={0.6} rx={4} ry={2} />
|
||||
<Path
|
||||
stroke="#2e7d32"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={1.5}
|
||||
d="M-14-13v3m0 0-4 2m4-2 4 2"
|
||||
/>
|
||||
<Path fill="#2e7d32" stroke="#1b5e20" strokeWidth={0.8} d="M-2 0C8-16 26-12 24 2 22 14 6 10-2 0Z" />
|
||||
<Path fill="none" stroke="#4caf50" strokeWidth={0.7} d="M-2 0q14-4 26 2" />
|
||||
<Path fill="#388e3c" stroke="#1b5e20" strokeWidth={0.8} d="M8 6c10-6 24 4 18 16-8 6-18-4-18-16Z" />
|
||||
<Path fill="none" stroke="#81c784" strokeWidth={0.7} d="M8 6q12 6 18 16" />
|
||||
</G>
|
||||
{/* Alt Pizza Dilimi */}
|
||||
<G id="footer-pizza-slice" transform="rotate(-10 3278.565 44.969)">
|
||||
<Path fill="#deac68" stroke="#8d6e63" strokeWidth={1.2} d="m0 0 46-18c4 8 6 32 0 40Z" />
|
||||
<Path fill="#fbc02d" d="m2 0 40-15c3 7 5 25 0 33Z" />
|
||||
<Circle cx={28} cy={-5} r={5} fill="#c62828" stroke="#8e0000" strokeWidth={0.8} />
|
||||
<Circle cx={32} cy={8} r={4.5} fill="#c62828" stroke="#8e0000" strokeWidth={0.8} />
|
||||
<Circle cx={16} r={3.5} fill="#c62828" stroke="#8e0000" strokeWidth={0.8} />
|
||||
<Ellipse cx={23} cy={4} fill="#2e7d32" rx={2} ry={4} transform="rotate(30 23 4)" />
|
||||
<Ellipse cx={34} cy={-11} fill="#2e7d32" rx={2} ry={3} transform="rotate(-40 34 -11)" />
|
||||
</G>
|
||||
{/* Şarap Kadehi */}
|
||||
<G id="footer-wine-glass" transform="translate(345 562)">
|
||||
<Path fill="none" stroke="#71717a" strokeWidth={1.2} d="M-14-24C-15-8-11 4 0 6 11 4 15-8 14-24Z" />
|
||||
<Path fill="url(#wine-grad-3)" d="M-13-12C-11 0-7 4 0 5.5 7 4 11 0 13-12Z" />
|
||||
<Ellipse cy={-12} fill="#991b1b" rx={13} ry={2} />
|
||||
<Path stroke="#71717a" strokeWidth={1.4} d="M0 6v22" />
|
||||
<Ellipse cy={28} fill="#e4e4e7" stroke="#71717a" strokeWidth={1.2} rx={11} ry={2} />
|
||||
<Path fill="none" stroke="#fff" strokeLinecap="round" d="M-10-20c-2 8 1 18 6 23" opacity={0.6} />
|
||||
</G>
|
||||
{/* QR Köşe Aksanları */}
|
||||
<G id="qr-corner-accents">
|
||||
<Path fill="none" stroke="#2e7d32" strokeLinecap="round" strokeWidth={3} d="M78 227v-22h22" />
|
||||
<Path fill="none" stroke="#b92b27" strokeLinecap="round" strokeWidth={3} d="M322 227v-22h-22M78 423v22h22" />
|
||||
<Path fill="none" stroke="#2e7d32" strokeLinecap="round" strokeWidth={3} d="M322 423v22h-22" />
|
||||
</G>
|
||||
</G>
|
||||
|
||||
{/* QR Placeholder / Gerçek QR */}
|
||||
{!hideQr && (
|
||||
<G id="qr-placeholder" transform="translate(90 215)">
|
||||
<Rect width={220} height={220} fill="#fff" stroke="#e4e4e7" strokeWidth={2} rx={20} />
|
||||
{qrBase64 ? (
|
||||
<Image
|
||||
href={`data:image/png;base64,${qrBase64}`}
|
||||
x={10}
|
||||
y={10}
|
||||
width={200}
|
||||
height={200}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<Rect
|
||||
width={196}
|
||||
height={196}
|
||||
x={12}
|
||||
y={12}
|
||||
fill="#fafafa"
|
||||
stroke="#e4e4e7"
|
||||
strokeDasharray="4 3"
|
||||
rx={14}
|
||||
/>
|
||||
<Text
|
||||
x={110}
|
||||
y={115}
|
||||
fill="#71717a"
|
||||
fontFamily="System, sans-serif"
|
||||
fontSize={12}
|
||||
fontWeight={700}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"QR KOD ALANI"}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</G>
|
||||
)}
|
||||
|
||||
{/* Metinler (Çok satırlı destekli) */}
|
||||
<G id="layer-texts">
|
||||
{/* Masa No */}
|
||||
{table?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: table?.text || "MASA NO: 01",
|
||||
x: 200,
|
||||
y: 50,
|
||||
offsetY: table?.offsetY,
|
||||
fill: "#18181b",
|
||||
fontFamily: infoFont,
|
||||
fontSize: table?.fontSize || 11,
|
||||
fontWeight: "bold",
|
||||
letterSpacing: 1.2,
|
||||
})}
|
||||
|
||||
<Text
|
||||
x={200}
|
||||
y={96}
|
||||
fill="#3e2723"
|
||||
fontFamily={rNameFont}
|
||||
fontSize={24}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"PIZZERIA"}
|
||||
</Text>
|
||||
|
||||
{/* Restoran Adı */}
|
||||
{rName?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: rName?.text || "BELLA NAPOLI",
|
||||
x: 200,
|
||||
y: 130,
|
||||
offsetY: rName?.offsetY,
|
||||
fill: "#18181b",
|
||||
fontFamily: rNameFont,
|
||||
fontSize: rName?.fontSize || 26,
|
||||
fontWeight: "bold",
|
||||
letterSpacing: 1,
|
||||
})}
|
||||
|
||||
{/* Slogan (Enter ile çok satır olabilir) */}
|
||||
{sub?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: sub?.text || "AUTENTICA CUCINA ITALIANA",
|
||||
x: 200,
|
||||
y: 222,
|
||||
offsetY: sub?.offsetY,
|
||||
fill: "#2e7d32",
|
||||
fontFamily: subFont,
|
||||
fontSize: sub?.fontSize || 11.5,
|
||||
fontWeight: "bold",
|
||||
letterSpacing: 1.8,
|
||||
})}
|
||||
|
||||
{/* CTA */}
|
||||
{cta?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: cta?.text || "MENÜYÜ GÖRMEK İÇİN OKUTUN",
|
||||
x: 200,
|
||||
y: 474,
|
||||
offsetY: cta?.offsetY,
|
||||
fill: "#b92b27",
|
||||
fontFamily: ctaFont,
|
||||
fontSize: cta?.fontSize || 13,
|
||||
fontWeight: "bold",
|
||||
letterSpacing: 1.2,
|
||||
})}
|
||||
|
||||
{/* WiFi */}
|
||||
{wifi?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: wifi?.text || "WiFi: GourmetBistro | Şifre: lezzetli01",
|
||||
x: 200,
|
||||
y: 546,
|
||||
offsetY: wifi?.offsetY,
|
||||
fill: "#3e2723",
|
||||
fontFamily: infoFont,
|
||||
fontSize: wifi?.fontSize || 10.5,
|
||||
fontWeight: "bold",
|
||||
})}
|
||||
|
||||
{/* Sosyal Medya */}
|
||||
{social?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: social?.text || "BİZİ TAKİP EDİN | @gourmetbistro",
|
||||
x: 200,
|
||||
y: 570,
|
||||
offsetY: social?.offsetY,
|
||||
fill: "#b92b27",
|
||||
fontFamily: infoFont,
|
||||
fontSize: social?.fontSize || 10.5,
|
||||
fontWeight: "bold",
|
||||
letterSpacing: 0.5,
|
||||
})}
|
||||
|
||||
</G>
|
||||
</Svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default QrTemplate3;
|
||||
@@ -0,0 +1,280 @@
|
||||
import * as React from "react";
|
||||
import Svg, {
|
||||
Defs,
|
||||
G,
|
||||
Path,
|
||||
Circle,
|
||||
Use,
|
||||
Rect,
|
||||
Text,
|
||||
Image,
|
||||
} from "react-native-svg";
|
||||
import { type QrTemplateProps, getFontFamily, renderSvgLines } from "./common";
|
||||
|
||||
const QrTemplate4 = ({
|
||||
qrBase64,
|
||||
fields,
|
||||
hideQr,
|
||||
...props
|
||||
}: QrTemplateProps) => {
|
||||
const rName = fields?.restaurantName;
|
||||
const sub = fields?.subtitle;
|
||||
const table = fields?.tableNo;
|
||||
const cta = fields?.ctaText;
|
||||
const wifi = fields?.wifiText;
|
||||
const social = fields?.socialText;
|
||||
|
||||
const rNameFont = getFontFamily(rName?.fontId, "Georgia, 'Times New Roman', serif");
|
||||
const subFont = getFontFamily(sub?.fontId, "System, -apple-system, Roboto, sans-serif");
|
||||
const ctaFont = getFontFamily(cta?.fontId, "Georgia, 'Times New Roman', serif");
|
||||
const infoFont = getFontFamily(wifi?.fontId, "System, -apple-system, Roboto, sans-serif");
|
||||
|
||||
return (
|
||||
<Svg width={300} height={435} viewBox="0 0 400 580" {...props}>
|
||||
<Defs>
|
||||
<G id="b4">
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b4b"
|
||||
strokeWidth={1.3}
|
||||
d="M0-22c22-8 36 6 38 22-2 16-16 30-38 22Z"
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b4b"
|
||||
strokeWidth={0.9}
|
||||
d="M0-12c14-6 24 2 26 12-2 10-12 18-26 12Z"
|
||||
/>
|
||||
<Path stroke="#c59b4b" strokeWidth={0.8} d="M0 0h26" />
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b4b"
|
||||
strokeWidth={1.2}
|
||||
d="M0-8c16-28 34-32 46-18C36-12 20-4 0-8ZM0 8c16 28 34 32 46 18C36 12 20 4 0 8Z"
|
||||
/>
|
||||
<Circle cx={48} cy={-28} r={1.5} fill="#c59b4b" />
|
||||
<Circle cx={48} cy={28} r={1.5} fill="#c59b4b" />
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b4b"
|
||||
strokeWidth={1.1}
|
||||
d="M0-35c24-21 52-15 56-1-14 6-32 12-56 1Zm0 70c24 21 52 15 56 1-14-6-32-12-56-1Z"
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b4b"
|
||||
d="M28-44c14-20 36-10 30 2-10 6-20 4-30-2Zm0 88c14 20 36 10 30-2-10-6-20-4-30 2Z"
|
||||
/>
|
||||
<Circle cx={60} cy={-44} r={1.2} fill="#c59b4b" />
|
||||
<Circle cx={60} cy={44} r={1.2} fill="#c59b4b" />
|
||||
<Circle cx={34} cy={-58} r={1.2} fill="#c59b4b" />
|
||||
<Circle cx={34} cy={58} r={1.2} fill="#c59b4b" />
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b4b"
|
||||
strokeLinecap="round"
|
||||
d="M0-60c22-15 36-35 32-55-6 15-16 30-32 55M0 60c22 15 36 35 32 55-6-15-16-30-32-55"
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b4b"
|
||||
strokeWidth={0.9}
|
||||
d="M14-78c8-6 12 2 6 6-4 0-6-4-6-6Zm0 156c8 6 12-2 6-6-4 0-6 4-6 6Z"
|
||||
/>
|
||||
<Circle cx={32} cy={-116} r={1.5} fill="#c59b4b" />
|
||||
<Circle cx={32} cy={116} r={1.5} fill="#c59b4b" />
|
||||
</G>
|
||||
<G id="a4">
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b4b"
|
||||
strokeWidth={1.3}
|
||||
d="M18 64c0-28 18-46 46-46"
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b4b"
|
||||
strokeWidth={0.8}
|
||||
d="M24 52c0-16 12-28 28-28"
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b4b"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={0.9}
|
||||
d="M28 32c4-6 10-4 8 2-2 4-8 2-6-4s10-6 14-2"
|
||||
/>
|
||||
<Circle cx={28} cy={28} r={1.8} fill="#c59b4b" />
|
||||
<Circle cx={48} cy={18} r={1.4} fill="#c59b4b" />
|
||||
<Circle cx={18} cy={48} r={1.4} fill="#c59b4b" />
|
||||
</G>
|
||||
</Defs>
|
||||
<Path fill="#fdfdfd" d="M0 0h400v580H0z" />
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b4b"
|
||||
strokeWidth={1.2}
|
||||
d="M20 14h360v552H20z"
|
||||
/>
|
||||
<Use href="#a4" />
|
||||
<Use href="#a4" transform="matrix(-1 0 0 1 400 0)" />
|
||||
<Use href="#a4" transform="matrix(1 0 0 -1 0 580)" />
|
||||
<Use href="#a4" transform="rotate(180 200 290)" />
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b4b"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={1.2}
|
||||
d="M132 62c8-4 16 2 24-2 4-2 2-6-2-4m114 6c-8-4-16 2-24-2-4-2-2-6 2-4"
|
||||
/>
|
||||
<Use href="#b4" transform="translate(0 310)" />
|
||||
<Use href="#b4" transform="matrix(-1 0 0 1 400 310)" />
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b4b"
|
||||
strokeLinecap="square"
|
||||
strokeWidth={2.2}
|
||||
d="M114 278v-58h54m118 58v-58h-54M114 342v58h54m118-58v58h-54"
|
||||
/>
|
||||
|
||||
{/* QR Placeholder / Gerçek QR */}
|
||||
{!hideQr && (
|
||||
<G transform="translate(125 230)">
|
||||
<Rect
|
||||
width={150}
|
||||
height={150}
|
||||
fill="#fff"
|
||||
stroke="#eae5d9"
|
||||
strokeWidth={1.5}
|
||||
rx={12}
|
||||
/>
|
||||
{qrBase64 ? (
|
||||
<Image
|
||||
href={`data:image/png;base64,${qrBase64}`}
|
||||
x={8}
|
||||
y={8}
|
||||
width={134}
|
||||
height={134}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<Rect
|
||||
width={134}
|
||||
height={134}
|
||||
x={8}
|
||||
y={8}
|
||||
fill="#fafaf8"
|
||||
stroke="#c59b4b"
|
||||
strokeDasharray="4 3"
|
||||
strokeOpacity={0.35}
|
||||
rx={8}
|
||||
/>
|
||||
<Text
|
||||
x={75}
|
||||
y={79}
|
||||
fill="#c59b4b"
|
||||
fontFamily="System, sans-serif"
|
||||
fontSize={10}
|
||||
fontWeight={700}
|
||||
letterSpacing={1}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"QR KOD ALANI"}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</G>
|
||||
)}
|
||||
|
||||
{/* Metinler */}
|
||||
<G fill="#111" textAnchor="middle">
|
||||
{/* Masa No */}
|
||||
{table?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: table?.text || "MASA NO: 01",
|
||||
x: 200,
|
||||
y: 56,
|
||||
offsetY: table?.offsetY,
|
||||
fill: "#c59b4b",
|
||||
fontFamily: infoFont,
|
||||
fontSize: table?.fontSize || 11,
|
||||
fontWeight: "bold",
|
||||
letterSpacing: 1.5,
|
||||
})}
|
||||
|
||||
{/* Restoran Başlığı (Enter ile alt satıra geçebilir) */}
|
||||
{rName?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: rName?.text || "THE CRIMSON PETAL",
|
||||
x: 200,
|
||||
y: 114,
|
||||
offsetY: rName?.offsetY,
|
||||
fill: "#111111",
|
||||
fontFamily: rNameFont,
|
||||
fontSize: rName?.fontSize || 24,
|
||||
fontWeight: "bold",
|
||||
letterSpacing: 1.5,
|
||||
})}
|
||||
|
||||
{/* Slogan */}
|
||||
{sub?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: sub?.text || "FINE DINING EXPERIENCE",
|
||||
x: 200,
|
||||
y: 154,
|
||||
offsetY: sub?.offsetY,
|
||||
fill: "#71717a",
|
||||
fontFamily: subFont,
|
||||
fontSize: sub?.fontSize || 11,
|
||||
fontWeight: 600,
|
||||
letterSpacing: 1.8,
|
||||
})}
|
||||
|
||||
{/* CTA Metni */}
|
||||
{cta?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: cta?.text || "MENÜYÜ GÖRMEK İÇİN OKUTUN",
|
||||
x: 200,
|
||||
y: 440,
|
||||
offsetY: cta?.offsetY,
|
||||
fill: "#111111",
|
||||
fontFamily: ctaFont,
|
||||
fontSize: cta?.fontSize || 15,
|
||||
fontWeight: "bold",
|
||||
letterSpacing: 1,
|
||||
})}
|
||||
|
||||
{/* WiFi Bilgisi */}
|
||||
{wifi?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: wifi?.text || "WiFi: CrimsonGuest | Şifre: petal2026",
|
||||
x: 200,
|
||||
y: 488,
|
||||
offsetY: wifi?.offsetY,
|
||||
fill: "#71717a",
|
||||
fontFamily: infoFont,
|
||||
fontSize: wifi?.fontSize || 10.5,
|
||||
fontWeight: 600,
|
||||
letterSpacing: 0.5,
|
||||
})}
|
||||
|
||||
{/* Sosyal Medya */}
|
||||
{social?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: social?.text || "@thecrimsonpetal",
|
||||
x: 200,
|
||||
y: 514,
|
||||
offsetY: social?.offsetY,
|
||||
fill: "#c59b4b",
|
||||
fontFamily: infoFont,
|
||||
fontSize: social?.fontSize || 10.5,
|
||||
fontWeight: 600,
|
||||
letterSpacing: 0.5,
|
||||
})}
|
||||
</G>
|
||||
|
||||
</Svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default QrTemplate4;
|
||||
@@ -0,0 +1,188 @@
|
||||
import * as React from "react";
|
||||
import Svg, {
|
||||
Image,
|
||||
G,
|
||||
Rect,
|
||||
Path,
|
||||
Text,
|
||||
} from "react-native-svg";
|
||||
import { type QrTemplateProps, getFontFamily, renderSvgLines } from "./common";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const bgImage = require("../../../assets/templates/5.png");
|
||||
|
||||
const QrTemplate5 = ({
|
||||
qrBase64,
|
||||
fields,
|
||||
hideQr,
|
||||
...props
|
||||
}: QrTemplateProps) => {
|
||||
const rName = fields?.restaurantName;
|
||||
const sub = fields?.subtitle;
|
||||
const table = fields?.tableNo;
|
||||
const cta = fields?.ctaText;
|
||||
const wifi = fields?.wifiText;
|
||||
const social = fields?.socialText;
|
||||
|
||||
const rNameFont = getFontFamily(rName?.fontId, "System, -apple-system, Roboto, sans-serif");
|
||||
const subFont = getFontFamily(sub?.fontId, "System, -apple-system, Roboto, sans-serif");
|
||||
const ctaFont = getFontFamily(cta?.fontId, "System, -apple-system, Roboto, sans-serif");
|
||||
const infoFont = getFontFamily(wifi?.fontId, "System, -apple-system, Roboto, sans-serif");
|
||||
|
||||
return (
|
||||
<Svg width={300} height={394.7} viewBox="0 0 380 500" {...props}>
|
||||
{/* PNG Arka Plan */}
|
||||
<Image
|
||||
width={380}
|
||||
height={500}
|
||||
href={bgImage}
|
||||
preserveAspectRatio="none"
|
||||
/>
|
||||
|
||||
{/* QR Placeholder / Live QR */}
|
||||
{!hideQr && (
|
||||
<G transform="translate(108 206)">
|
||||
<Rect
|
||||
width={164}
|
||||
height={164}
|
||||
fill="#fff"
|
||||
stroke="#dce5ea"
|
||||
strokeWidth={1.2}
|
||||
rx={16}
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#003554"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={3}
|
||||
d="M6 30V12a6 6 0 0 1 6-6h18m128 24V12a6 6 0 0 0-6-6h-18M6 134v18a6 6 0 0 0 6 6h18m128-24v18a6 6 0 0 1-6 6h-18"
|
||||
/>
|
||||
{qrBase64 ? (
|
||||
<Image
|
||||
href={`data:image/png;base64,${qrBase64}`}
|
||||
x={12}
|
||||
y={12}
|
||||
width={140}
|
||||
height={140}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<Rect
|
||||
width={140}
|
||||
height={140}
|
||||
x={12}
|
||||
y={12}
|
||||
fill="#fafcfd"
|
||||
stroke="#003554"
|
||||
strokeDasharray="4 3"
|
||||
strokeOpacity={0.2}
|
||||
rx={10}
|
||||
/>
|
||||
<Text
|
||||
x={82}
|
||||
y={87}
|
||||
fill="#003554"
|
||||
fontFamily="System, sans-serif"
|
||||
fontSize={11}
|
||||
fontWeight={700}
|
||||
letterSpacing={1}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"QR KOD ALANI"}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</G>
|
||||
)}
|
||||
|
||||
{/* Metinler */}
|
||||
<G fill="#003554" textAnchor="middle">
|
||||
{/* Masa Numarası */}
|
||||
{table?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: table?.text || "MASA NO: 01",
|
||||
x: 190,
|
||||
y: 40,
|
||||
offsetY: table?.offsetY,
|
||||
fill: "#003554",
|
||||
fontFamily: infoFont,
|
||||
fontSize: table?.fontSize || 11,
|
||||
fontWeight: "bold",
|
||||
letterSpacing: 1.5,
|
||||
})}
|
||||
|
||||
{/* Restoran Başlığı */}
|
||||
{rName?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: rName?.text || "THE URBAN FORK",
|
||||
x: 190,
|
||||
y: 86,
|
||||
offsetY: rName?.offsetY,
|
||||
fill: "#003554",
|
||||
fontFamily: rNameFont,
|
||||
fontSize: rName?.fontSize || 24,
|
||||
fontWeight: "bold",
|
||||
letterSpacing: 1,
|
||||
})}
|
||||
|
||||
{/* Yönlendirme (CTA) */}
|
||||
{cta?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: cta?.text || "DİJİTAL MENÜYÜ GÖRÜNTÜLE",
|
||||
x: 190,
|
||||
y: 160,
|
||||
offsetY: cta?.offsetY,
|
||||
fill: "#003554",
|
||||
fontFamily: ctaFont,
|
||||
fontSize: cta?.fontSize || 13,
|
||||
fontWeight: "bold",
|
||||
letterSpacing: 0.5,
|
||||
})}
|
||||
|
||||
{/* Slogan (Enter ile çok satır olabilir) */}
|
||||
{sub?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: sub?.text || "FRESH FLAVORS DAILY",
|
||||
x: 190,
|
||||
y: 402,
|
||||
offsetY: sub?.offsetY,
|
||||
fill: "#003554",
|
||||
fontFamily: subFont,
|
||||
fontSize: sub?.fontSize || 13,
|
||||
fontWeight: "bold",
|
||||
letterSpacing: 0.8,
|
||||
})}
|
||||
|
||||
{/* WiFi */}
|
||||
{wifi?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: wifi?.text || "WiFi: UrbanFork | Şifre: urban2026",
|
||||
x: 190,
|
||||
y: 430,
|
||||
offsetY: wifi?.offsetY,
|
||||
fill: "#003554",
|
||||
fontFamily: infoFont,
|
||||
fontSize: wifi?.fontSize || 10,
|
||||
fontWeight: 700,
|
||||
letterSpacing: 0.2,
|
||||
})}
|
||||
|
||||
{/* Sosyal Medya */}
|
||||
{social?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: social?.text || "@theurbanfork",
|
||||
x: 190,
|
||||
y: 454,
|
||||
offsetY: social?.offsetY,
|
||||
fill: "#003554",
|
||||
fontFamily: infoFont,
|
||||
fontSize: social?.fontSize || 10,
|
||||
fontWeight: 700,
|
||||
letterSpacing: 0.2,
|
||||
})}
|
||||
</G>
|
||||
</Svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default QrTemplate5;
|
||||
@@ -0,0 +1,263 @@
|
||||
import * as React from "react";
|
||||
import Svg, {
|
||||
Defs,
|
||||
G,
|
||||
Path,
|
||||
Circle,
|
||||
Rect,
|
||||
Use,
|
||||
Text,
|
||||
Image,
|
||||
} from "react-native-svg";
|
||||
import { type QrTemplateProps, getFontFamily, renderSvgLines } from "./common";
|
||||
|
||||
const QrTemplate6 = ({
|
||||
qrBase64,
|
||||
fields,
|
||||
hideQr,
|
||||
...props
|
||||
}: QrTemplateProps) => {
|
||||
const rName = fields?.restaurantName;
|
||||
const sub = fields?.subtitle;
|
||||
const table = fields?.tableNo;
|
||||
const cta = fields?.ctaText;
|
||||
const wifi = fields?.wifiText;
|
||||
const social = fields?.socialText;
|
||||
|
||||
const rNameFont = getFontFamily(rName?.fontId, "Georgia, 'Times New Roman', serif");
|
||||
const subFont = getFontFamily(sub?.fontId, "System, -apple-system, Roboto, sans-serif");
|
||||
const ctaFont = getFontFamily(cta?.fontId, "Georgia, 'Times New Roman', serif");
|
||||
const infoFont = getFontFamily(wifi?.fontId, "System, -apple-system, Roboto, sans-serif");
|
||||
|
||||
return (
|
||||
<Svg width={300} height={435} viewBox="0 0 400 580" {...props}>
|
||||
<Defs>
|
||||
<G id="b6">
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b58"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={1.2}
|
||||
d="M0 0c10 25 12 55 5 85"
|
||||
/>
|
||||
<Path
|
||||
fill="#f4efe6"
|
||||
stroke="#c59b58"
|
||||
strokeWidth={1.1}
|
||||
d="M3 15c-11-5-15 9-1 9 4 0 4-6 1-9Z"
|
||||
/>
|
||||
<Path
|
||||
fill="#f4efe6"
|
||||
stroke="#9e4d28"
|
||||
strokeWidth={1.1}
|
||||
d="M5 22c11-6 17 6 3 10-4 0-4-6-3-10Zm2 20c-12-6-17 8-3 10 4 0 4-6 3-10Z"
|
||||
/>
|
||||
<Path
|
||||
fill="#f4efe6"
|
||||
stroke="#c59b58"
|
||||
strokeWidth={1.1}
|
||||
d="M8 50c12-6 16 8 2 12-4 0-4-7-2-12ZM6 70c-10-5-14 8-2 10 3 0 4-6 2-10Z"
|
||||
/>
|
||||
<Path
|
||||
fill="#f4efe6"
|
||||
stroke="#9e4d28"
|
||||
strokeWidth={1.1}
|
||||
d="M5 78c11-4 15 8 3 12-4 0-4-6-3-12Z"
|
||||
/>
|
||||
<Circle cx={-10} cy={18} r={1.5} fill="#c59b58" />
|
||||
<Circle cx={18} cy={46} r={1.5} fill="#9e4d28" />
|
||||
</G>
|
||||
<G id="a6">
|
||||
<Path
|
||||
fill="#a64423"
|
||||
d="M0-18C4-12 8-6 8 0c6-4 8 6 4 12 4-2 6 4 2 8S2 26 0 26s-10-2-14-6-2-10 2-8c-4-6-2-16 4-12 0-6 4-12 8-18"
|
||||
/>
|
||||
<Path
|
||||
fill="#f4efe6"
|
||||
d="M-4 4v8c0 3 2 4 4 4v8c2-8 4-9 4-12V4H3v7c0 2-1.5 3-3 3s-3-1-3-3V4Z"
|
||||
/>
|
||||
<Path stroke="#f4efe6" d="M0 4v8" />
|
||||
</G>
|
||||
</Defs>
|
||||
<Path fill="#434b54" d="M0 0h400v580H0z" />
|
||||
<Rect
|
||||
width={360}
|
||||
height={548}
|
||||
x={20}
|
||||
y={16}
|
||||
fill="#f6efe3"
|
||||
stroke="#122a42"
|
||||
strokeWidth={1.5}
|
||||
rx={16}
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#122a42"
|
||||
strokeWidth={1.2}
|
||||
d="M46 32h308a12 12 0 0 1 12 12v492a12 12 0 0 1-12 12H46a12 12 0 0 1-12-12V44a12 12 0 0 1 12-12Z"
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#122a42"
|
||||
strokeWidth={1.2}
|
||||
d="M34 46a12 12 0 0 0 12-12m320 12a12 12 0 0 1-12-12M34 534a12 12 0 0 1 12 12m320-12a12 12 0 0 0-12 12"
|
||||
/>
|
||||
<Path stroke="#c59b58" d="M34 135h332M34 500h332" />
|
||||
<Use href="#a6" transform="matrix(.85 0 0 .85 200 52)" />
|
||||
<Use href="#b6" transform="matrix(.8 0 0 .8 42 42)" />
|
||||
<Use href="#b6" transform="matrix(-.8 0 0 .8 358 42)" />
|
||||
<Use href="#b6" transform="matrix(.95 0 0 .95 40 185)" />
|
||||
<Use href="#b6" transform="matrix(-.95 0 0 .95 360 185)" />
|
||||
<Use href="#a6" transform="matrix(.65 0 0 .65 68 320)" />
|
||||
<Use href="#a6" transform="matrix(.65 0 0 .65 332 320)" />
|
||||
<Use href="#b6" transform="matrix(.95 0 0 .95 40 360)" />
|
||||
<Use href="#b6" transform="matrix(-.95 0 0 .95 360 360)" />
|
||||
|
||||
{/* QR Placeholder / Gerçek QR */}
|
||||
{!hideQr && (
|
||||
<G transform="translate(95 205)">
|
||||
<Path
|
||||
fill="#fdfbf7"
|
||||
stroke="#122a42"
|
||||
strokeWidth={3}
|
||||
d="M0 0h210v210H0z"
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b58"
|
||||
strokeWidth={1.5}
|
||||
d="M4 4h202v202H4z"
|
||||
/>
|
||||
{qrBase64 ? (
|
||||
<Image
|
||||
href={`data:image/png;base64,${qrBase64}`}
|
||||
x={10}
|
||||
y={10}
|
||||
width={190}
|
||||
height={190}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<Path
|
||||
fill="#faf8f5"
|
||||
stroke="#122a42"
|
||||
strokeDasharray="4 3"
|
||||
d="M12 12h186v186H12z"
|
||||
/>
|
||||
<Circle
|
||||
cx={105}
|
||||
cy={95}
|
||||
r={16}
|
||||
fill="#f6efe3"
|
||||
stroke="#c59b58"
|
||||
strokeWidth={1.2}
|
||||
/>
|
||||
<Use href="#a6" transform="matrix(.45 0 0 .45 105 93)" />
|
||||
<Text
|
||||
x={105}
|
||||
y={128}
|
||||
fill="#122a42"
|
||||
fontFamily="System, sans-serif"
|
||||
fontSize={11}
|
||||
fontWeight="bold"
|
||||
letterSpacing={1.5}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"QR KOD ALANI"}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</G>
|
||||
)}
|
||||
|
||||
{/* Metinler */}
|
||||
<G textAnchor="middle">
|
||||
{/* Masa Numarası */}
|
||||
{table?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: table?.text || "MASA NO: 01",
|
||||
x: 200,
|
||||
y: 36,
|
||||
offsetY: table?.offsetY,
|
||||
fill: "#122a42",
|
||||
fontFamily: infoFont,
|
||||
fontSize: table?.fontSize || 10,
|
||||
fontWeight: "bold",
|
||||
letterSpacing: 1.5,
|
||||
})}
|
||||
|
||||
{/* Restoran Başlığı */}
|
||||
{rName?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: rName?.text || "THE STONEHEARTH GRILL",
|
||||
x: 200,
|
||||
y: 84,
|
||||
offsetY: rName?.offsetY,
|
||||
fill: "#a64423",
|
||||
fontFamily: rNameFont,
|
||||
fontSize: rName?.fontSize || 19,
|
||||
fontWeight: "bold",
|
||||
letterSpacing: 1.5,
|
||||
})}
|
||||
|
||||
{/* Slogan */}
|
||||
{sub?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: sub?.text || "SCAN. ORDER. ENJOY.",
|
||||
x: 200,
|
||||
y: 118,
|
||||
offsetY: sub?.offsetY,
|
||||
fill: "#122a42",
|
||||
fontFamily: subFont,
|
||||
fontSize: sub?.fontSize || 10,
|
||||
fontWeight: "bold",
|
||||
letterSpacing: 2,
|
||||
})}
|
||||
|
||||
{/* CTA */}
|
||||
{cta?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: cta?.text || "VIEW OUR MENU & ORDER ONLINE",
|
||||
x: 200,
|
||||
y: 160,
|
||||
offsetY: cta?.offsetY,
|
||||
fill: "#122a42",
|
||||
fontFamily: ctaFont,
|
||||
fontSize: cta?.fontSize || 13,
|
||||
fontWeight: "bold",
|
||||
letterSpacing: 0.5,
|
||||
})}
|
||||
|
||||
{/* WiFi */}
|
||||
{wifi?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: wifi?.text || "FREE WIFI: StonehearthGuest | PW: Grill720",
|
||||
x: 200,
|
||||
y: 524,
|
||||
offsetY: wifi?.offsetY,
|
||||
fill: "#222",
|
||||
fontFamily: infoFont,
|
||||
fontSize: wifi?.fontSize || 9,
|
||||
fontWeight: "bold",
|
||||
letterSpacing: 0.2,
|
||||
})}
|
||||
|
||||
{/* Sosyal Medya */}
|
||||
{social?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: social?.text || "Follow Us @StonehearthGrill",
|
||||
x: 200,
|
||||
y: 540,
|
||||
offsetY: social?.offsetY,
|
||||
fill: "#222",
|
||||
fontFamily: infoFont,
|
||||
fontSize: social?.fontSize || 9,
|
||||
fontWeight: "bold",
|
||||
letterSpacing: 0.2,
|
||||
})}
|
||||
</G>
|
||||
</Svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default QrTemplate6;
|
||||
@@ -0,0 +1,212 @@
|
||||
import * as React from "react";
|
||||
import Svg, {
|
||||
G,
|
||||
Path,
|
||||
Circle,
|
||||
Rect,
|
||||
Text,
|
||||
Image,
|
||||
} from "react-native-svg";
|
||||
import { type QrTemplateProps, getFontFamily, renderSvgLines } from "./common";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const template8Bg = require("../../../assets/templates/8.jpeg");
|
||||
|
||||
const QrTemplate8 = ({
|
||||
qrBase64,
|
||||
fields,
|
||||
hideQr,
|
||||
...props
|
||||
}: QrTemplateProps) => {
|
||||
const rName = fields?.restaurantName;
|
||||
const sub = fields?.subtitle;
|
||||
const table = fields?.tableNo;
|
||||
const cta = fields?.ctaText;
|
||||
const wifi = fields?.wifiText;
|
||||
const social = fields?.socialText;
|
||||
|
||||
const rNameFont = getFontFamily(rName?.fontId, "System, -apple-system, Roboto, sans-serif");
|
||||
const subFont = getFontFamily(sub?.fontId, "System, -apple-system, Roboto, sans-serif");
|
||||
const ctaFont = getFontFamily(cta?.fontId, "System, -apple-system, Roboto, sans-serif");
|
||||
const infoFont = getFontFamily(wifi?.fontId, "System, -apple-system, Roboto, sans-serif");
|
||||
|
||||
return (
|
||||
<Svg width={300} height={300} viewBox="0 0 1024 1024" {...props}>
|
||||
<Path fill="#0c4443" d="M0 0h1024v1024H0z" />
|
||||
<Image
|
||||
width={1024}
|
||||
height={1024}
|
||||
href={template8Bg}
|
||||
preserveAspectRatio="none"
|
||||
/>
|
||||
|
||||
{/* QR Alanı (Merkez) */}
|
||||
{!hideQr && (
|
||||
<G transform="translate(348 305)">
|
||||
<Rect
|
||||
width={328}
|
||||
height={336}
|
||||
fill="#f9f5eb"
|
||||
stroke="#d1a752"
|
||||
strokeWidth={1.5}
|
||||
rx={16}
|
||||
/>
|
||||
<Rect
|
||||
width={304}
|
||||
height={312}
|
||||
x={12}
|
||||
y={12}
|
||||
fill="#faf8f2"
|
||||
stroke="#0c4443"
|
||||
strokeDasharray="6 4"
|
||||
strokeOpacity={0.3}
|
||||
strokeWidth={1.5}
|
||||
rx={12}
|
||||
/>
|
||||
{qrBase64 ? (
|
||||
<Image
|
||||
href={`data:image/png;base64,${qrBase64}`}
|
||||
x={18}
|
||||
y={18}
|
||||
width={292}
|
||||
height={300}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<Circle cx={164} cy={148} r={28} fill="#0c4443" />
|
||||
<G fill="#d1a752">
|
||||
<Path d="M164 132.6c4.4 6.6 4.4 15.4 0 24.2-4.4-8.8-4.4-17.6 0-24.2" />
|
||||
<Path d="M161.8 150.2c-8.8-4.4-13.2 4.4-6.6 8.8 6.6 0 8.8-6.6 6.6-8.8m4.4 0c8.8-4.4 13.2 4.4 6.6 8.8-6.6 0-8.8-6.6-6.6-8.8" />
|
||||
</G>
|
||||
<Text
|
||||
x={164}
|
||||
y={218}
|
||||
fill="#0c4443"
|
||||
fontFamily="System, -apple-system, Roboto, sans-serif"
|
||||
fontSize={14}
|
||||
fontWeight={800}
|
||||
letterSpacing={2}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"QR KOD ALANI"}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</G>
|
||||
)}
|
||||
|
||||
{/* Üst Çizgiler & OR Ayracı */}
|
||||
<Path
|
||||
stroke="#d1a752"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={2}
|
||||
d="M210 188h240"
|
||||
/>
|
||||
<Text
|
||||
x={512}
|
||||
y={194}
|
||||
fill="#d1a752"
|
||||
fontFamily="System, -apple-system, Roboto, sans-serif"
|
||||
fontSize={24}
|
||||
fontWeight={900}
|
||||
letterSpacing={2}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"OR"}
|
||||
</Text>
|
||||
<Path
|
||||
stroke="#d1a752"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={2}
|
||||
d="M574 188h240"
|
||||
/>
|
||||
|
||||
{/* Masa Numarası (En Üst) */}
|
||||
{table?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: table?.text || "MASA NO: 01",
|
||||
x: 512,
|
||||
y: 70,
|
||||
offsetY: table?.offsetY,
|
||||
fill: "#d1a752",
|
||||
fontFamily: infoFont,
|
||||
fontSize: table?.fontSize ? table.fontSize * 1.8 : 22,
|
||||
fontWeight: 800,
|
||||
letterSpacing: 2,
|
||||
})}
|
||||
|
||||
{/* Restoran Başlığı / Üst Başlık */}
|
||||
{rName?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: rName?.text || "EXPLORE OUR MENU",
|
||||
x: 512,
|
||||
y: 136,
|
||||
offsetY: rName?.offsetY,
|
||||
fill: "#fff",
|
||||
fontFamily: rNameFont,
|
||||
fontSize: rName?.fontSize ? rName.fontSize * 1.8 : 50,
|
||||
fontWeight: 900,
|
||||
letterSpacing: 2,
|
||||
})}
|
||||
|
||||
{/* Slogan / Alt Başlık */}
|
||||
{sub?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: sub?.text || "THE DAILY SPECIALS",
|
||||
x: 512,
|
||||
y: 242,
|
||||
offsetY: sub?.offsetY,
|
||||
fill: "#d1a752",
|
||||
fontFamily: subFont,
|
||||
fontSize: sub?.fontSize ? sub.fontSize * 1.8 : 42,
|
||||
fontWeight: 900,
|
||||
letterSpacing: 2,
|
||||
})}
|
||||
|
||||
{/* CTA Metni */}
|
||||
{cta?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: cta?.text || "SCAN TO VIEW OUR\nFULL MENU & SPECIALS",
|
||||
x: 512,
|
||||
y: 730,
|
||||
offsetY: cta?.offsetY,
|
||||
fill: "#fff",
|
||||
fontFamily: ctaFont,
|
||||
fontSize: cta?.fontSize ? cta.fontSize * 1.8 : 28,
|
||||
fontWeight: 900,
|
||||
letterSpacing: 1,
|
||||
lineHeight: 38,
|
||||
})}
|
||||
|
||||
{/* WiFi / Sipariş Bilgisi */}
|
||||
{wifi?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: wifi?.text || "SCAN TO ORDER & BROWSE",
|
||||
x: 512,
|
||||
y: 846,
|
||||
offsetY: wifi?.offsetY,
|
||||
fill: "#d1a752",
|
||||
fontFamily: infoFont,
|
||||
fontSize: wifi?.fontSize ? wifi.fontSize * 1.8 : 22,
|
||||
fontWeight: 900,
|
||||
letterSpacing: 1.5,
|
||||
})}
|
||||
|
||||
{/* Sosyal Medya */}
|
||||
{social?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: social?.text || "@menulio.app",
|
||||
x: 512,
|
||||
y: 916,
|
||||
offsetY: social?.offsetY,
|
||||
fill: "#e5c378",
|
||||
fontFamily: infoFont,
|
||||
fontSize: social?.fontSize ? social.fontSize * 1.8 : 20,
|
||||
fontWeight: 700,
|
||||
letterSpacing: 1,
|
||||
})}
|
||||
</Svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default QrTemplate8;
|
||||
@@ -0,0 +1,281 @@
|
||||
import * as React from "react";
|
||||
import Svg, {
|
||||
Defs,
|
||||
G,
|
||||
Path,
|
||||
Circle,
|
||||
Rect,
|
||||
Text,
|
||||
TextPath,
|
||||
Image,
|
||||
} from "react-native-svg";
|
||||
import { type QrTemplateProps, getFontFamily, renderSvgLines } from "./common";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const template9Bg = require("../../../assets/templates/9.jpeg");
|
||||
|
||||
const QrTemplate9 = ({
|
||||
qrBase64,
|
||||
fields,
|
||||
hideQr,
|
||||
...props
|
||||
}: QrTemplateProps) => {
|
||||
const rName = fields?.restaurantName;
|
||||
const sub = fields?.subtitle;
|
||||
const table = fields?.tableNo;
|
||||
const cta = fields?.ctaText;
|
||||
const wifi = fields?.wifiText;
|
||||
const social = fields?.socialText;
|
||||
|
||||
const rNameFont = getFontFamily(rName?.fontId, "System, -apple-system, Roboto, sans-serif");
|
||||
const subFont = getFontFamily(sub?.fontId, "System, -apple-system, Roboto, sans-serif");
|
||||
const ctaFont = getFontFamily(cta?.fontId, "System, -apple-system, Roboto, sans-serif");
|
||||
const infoFont = getFontFamily(wifi?.fontId, "System, -apple-system, Roboto, sans-serif");
|
||||
|
||||
return (
|
||||
<Svg width={300} height={300} viewBox="0 0 1024 1024" {...props}>
|
||||
<Defs>
|
||||
<Path id="arcTop9" fill="none" d="M330 260q182-115 364 0" />
|
||||
</Defs>
|
||||
|
||||
<Path fill="#f8f6f0" d="M0 0h1024v1024H0z" />
|
||||
<Image
|
||||
width={1024}
|
||||
height={1024}
|
||||
href={template9Bg}
|
||||
preserveAspectRatio="none"
|
||||
/>
|
||||
|
||||
{/* QR Alanı (Merkez) */}
|
||||
{!hideQr && (
|
||||
<G transform="translate(362 428)">
|
||||
<Rect
|
||||
width={300}
|
||||
height={300}
|
||||
fill="#fff"
|
||||
stroke="#d5cebe"
|
||||
strokeWidth={1.5}
|
||||
rx={24}
|
||||
/>
|
||||
<Rect
|
||||
width={276}
|
||||
height={276}
|
||||
x={12}
|
||||
y={12}
|
||||
fill="#fafaf7"
|
||||
stroke="#1e3246"
|
||||
strokeDasharray="6 4"
|
||||
strokeOpacity={0.35}
|
||||
strokeWidth={1.5}
|
||||
rx={16}
|
||||
/>
|
||||
|
||||
{qrBase64 ? (
|
||||
<Image
|
||||
href={`data:image/png;base64,${qrBase64}`}
|
||||
x={16}
|
||||
y={16}
|
||||
width={268}
|
||||
height={268}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<Circle
|
||||
cx={150}
|
||||
cy={132}
|
||||
r={28}
|
||||
fill="#f5efe6"
|
||||
stroke="#1e3246"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#1e3246"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={1.9}
|
||||
d="M142.4 116.8v11.4c0 3.8 5.7 3.8 5.7 0v-11.4m-2.85 0v11.4m0 3.8v15.2m9.5-30.4c4.75 3.8 4.75 15.2 0 17.1v13.3"
|
||||
/>
|
||||
<Text
|
||||
x={150}
|
||||
y={192}
|
||||
fill="#1e3246"
|
||||
fontFamily="System, -apple-system, Roboto, sans-serif"
|
||||
fontSize={14}
|
||||
fontWeight={800}
|
||||
letterSpacing={2}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"QR KOD ALANI"}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</G>
|
||||
)}
|
||||
|
||||
{/* Üst Amblem & Rozet */}
|
||||
<G stroke="#a88b58" transform="translate(290 142)">
|
||||
<Circle cx={-16} cy={-8} r={4.5} fill="none" strokeWidth={1.5} />
|
||||
<Path strokeLinecap="round" strokeWidth={1.5} d="M-16-12v8m-4-4h8" />
|
||||
<Path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={1.5}
|
||||
d="M0-12c4 0 8 6 8 12S4 12 0 12"
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={1.2}
|
||||
d="M-32 20c12 8 52 8 64 0"
|
||||
/>
|
||||
</G>
|
||||
|
||||
{/* Masa Numarası / Rozet Metni */}
|
||||
{table?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: table?.text || "TABLE MENU\nEST. 2026",
|
||||
x: 290,
|
||||
y: 164,
|
||||
offsetY: table?.offsetY,
|
||||
fill: "#1e3246",
|
||||
fontFamily: infoFont,
|
||||
fontSize: table?.fontSize ? table.fontSize * 1.3 : 15,
|
||||
fontWeight: 900,
|
||||
letterSpacing: 1,
|
||||
lineHeight: 18,
|
||||
})}
|
||||
|
||||
{/* Sol / Sağ Yönelticiler (SIAT / VIEW) */}
|
||||
<Text
|
||||
x={364}
|
||||
y={248}
|
||||
fill="#1e3246"
|
||||
fontFamily="System, -apple-system, Roboto, sans-serif"
|
||||
fontSize={23}
|
||||
fontWeight={900}
|
||||
letterSpacing={1}
|
||||
textAnchor="middle"
|
||||
transform="rotate(-26 364 248)"
|
||||
>
|
||||
{"SCAN"}
|
||||
</Text>
|
||||
<Text
|
||||
x={670}
|
||||
y={244}
|
||||
fill="#1e3246"
|
||||
fontFamily="System, -apple-system, Roboto, sans-serif"
|
||||
fontSize={25}
|
||||
fontWeight={900}
|
||||
letterSpacing={1}
|
||||
textAnchor="middle"
|
||||
transform="rotate(24 670 244)"
|
||||
>
|
||||
{"VIEW"}
|
||||
</Text>
|
||||
|
||||
{/* Kavisli Üst Yazı */}
|
||||
{sub?.visible !== false && (
|
||||
<Text
|
||||
fill="#1e3246"
|
||||
fontFamily={subFont}
|
||||
fontSize={sub?.fontSize ? sub.fontSize * 1.8 : 46}
|
||||
fontWeight={900}
|
||||
letterSpacing={2}
|
||||
>
|
||||
<TextPath href="#arcTop9" startOffset="50%" textAnchor="middle">
|
||||
{sub?.text || "DIGITAL MENU"}
|
||||
</TextPath>
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{/* Restoran Başlığı / Ana Başlık */}
|
||||
{rName?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: rName?.text || "OUR MENU",
|
||||
x: 512,
|
||||
y: 326,
|
||||
offsetY: rName?.offsetY,
|
||||
fill: "#1e3246",
|
||||
fontFamily: rNameFont,
|
||||
fontSize: rName?.fontSize ? rName.fontSize * 2.4 : 76,
|
||||
fontWeight: 900,
|
||||
letterSpacing: 3,
|
||||
})}
|
||||
|
||||
{/* Yönlendirici Oklar */}
|
||||
<G stroke="#1e3246">
|
||||
<Path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={3}
|
||||
d="M390 780c-16-8-16-26-8-36"
|
||||
/>
|
||||
<Path
|
||||
fill="#1e3246"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1.5}
|
||||
d="m374 750 8-6 6 8Z"
|
||||
/>
|
||||
</G>
|
||||
<G stroke="#1e3246">
|
||||
<Path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={3}
|
||||
d="M634 780c16-8 16-26 8-36"
|
||||
/>
|
||||
<Path
|
||||
fill="#1e3246"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1.5}
|
||||
d="m650 750-8-6-6 8Z"
|
||||
/>
|
||||
</G>
|
||||
|
||||
{/* CTA Metni */}
|
||||
{cta?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: cta?.text || "VIEW OUR\nDELICIOUS MENU",
|
||||
x: 512,
|
||||
y: 752,
|
||||
offsetY: cta?.offsetY,
|
||||
fill: "#1e3246",
|
||||
fontFamily: ctaFont,
|
||||
fontSize: cta?.fontSize ? cta.fontSize * 1.6 : 20,
|
||||
fontWeight: 900,
|
||||
letterSpacing: 1,
|
||||
lineHeight: 28,
|
||||
})}
|
||||
|
||||
{/* Alt Bilgi / Karşılama & WiFi */}
|
||||
{wifi?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: wifi?.text || "WELCOME TO OUR TABLE",
|
||||
x: 500,
|
||||
y: 894,
|
||||
offsetY: wifi?.offsetY,
|
||||
fill: "#1e3246",
|
||||
fontFamily: infoFont,
|
||||
fontSize: wifi?.fontSize ? wifi.fontSize * 1.5 : 18,
|
||||
fontWeight: 900,
|
||||
letterSpacing: 1.5,
|
||||
})}
|
||||
|
||||
{/* Sosyal Medya */}
|
||||
{social?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: social?.text || "@menulio.table",
|
||||
x: 500,
|
||||
y: 934,
|
||||
offsetY: social?.offsetY,
|
||||
fill: "#a88b58",
|
||||
fontFamily: infoFont,
|
||||
fontSize: social?.fontSize ? social.fontSize * 1.5 : 16,
|
||||
fontWeight: 700,
|
||||
letterSpacing: 1,
|
||||
})}
|
||||
</Svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default QrTemplate9;
|
||||
@@ -0,0 +1,217 @@
|
||||
import * as React from "react";
|
||||
import Svg, {
|
||||
Path,
|
||||
Rect,
|
||||
Circle,
|
||||
G,
|
||||
Text,
|
||||
Image,
|
||||
} from "react-native-svg";
|
||||
import { type QrTemplateProps, getFontFamily, renderSvgLines } from "./common";
|
||||
|
||||
const QrTemplateLumiere = ({
|
||||
qrBase64,
|
||||
fields,
|
||||
hideQr,
|
||||
...props
|
||||
}: QrTemplateProps) => {
|
||||
const rName = fields?.restaurantName;
|
||||
const sub = fields?.subtitle;
|
||||
const table = fields?.tableNo;
|
||||
const cta = fields?.ctaText;
|
||||
const wifi = fields?.wifiText;
|
||||
const social = fields?.socialText;
|
||||
|
||||
const rNameFont = getFontFamily(rName?.fontId, "Georgia, 'Playfair Display', serif");
|
||||
const subFont = getFontFamily(sub?.fontId, "System, -apple-system, Roboto, sans-serif");
|
||||
const ctaFont = getFontFamily(cta?.fontId, "Georgia, 'Playfair Display', serif");
|
||||
const infoFont = getFontFamily(wifi?.fontId, "System, -apple-system, Roboto, sans-serif");
|
||||
|
||||
return (
|
||||
<Svg viewBox="0 0 600 600" width={300} height={300} {...props}>
|
||||
<Path fill="#0b0d13" d="M0 0h600v600H0z" />
|
||||
<Rect
|
||||
width={560}
|
||||
height={560}
|
||||
x={20}
|
||||
y={20}
|
||||
fill="#132034"
|
||||
stroke="#d4af37"
|
||||
strokeWidth={1.5}
|
||||
rx={24}
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#d4af37"
|
||||
strokeOpacity={0.85}
|
||||
strokeWidth={1.5}
|
||||
d="M56 40h488a16 16 0 0 1 16 16v488a16 16 0 0 1-16 16H56a16 16 0 0 1-16-16V56a16 16 0 0 1 16-16Z"
|
||||
/>
|
||||
<Rect
|
||||
width={504}
|
||||
height={504}
|
||||
x={48}
|
||||
y={48}
|
||||
fill="none"
|
||||
stroke="#c5a059"
|
||||
strokeOpacity={0.45}
|
||||
strokeWidth={0.8}
|
||||
rx={8}
|
||||
/>
|
||||
{/* Köşe süslemeleri */}
|
||||
<Path fill="none" stroke="#d4af37" strokeWidth={1.5} d="M40 68a18 18 0 0 0 28-28" />
|
||||
<Circle cx={52} cy={52} r={2} fill="#d4af37" />
|
||||
<Path fill="none" stroke="#d4af37" strokeWidth={1.5} d="M560 68a18 18 0 0 1-28-28" />
|
||||
<Circle cx={548} cy={52} r={2} fill="#d4af37" />
|
||||
<Path fill="none" stroke="#d4af37" strokeWidth={1.5} d="M40 532a18 18 0 0 1 28 28" />
|
||||
<Circle cx={52} cy={548} r={2} fill="#d4af37" />
|
||||
<Path fill="none" stroke="#d4af37" strokeWidth={1.5} d="M560 532a18 18 0 0 0-28 28" />
|
||||
<Circle cx={548} cy={548} r={2} fill="#d4af37" />
|
||||
|
||||
{/* Merkez amblem */}
|
||||
<G transform="translate(300 85)">
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#d4af37"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M0-26C-8-16-8-6 0 0c8-6 8-16 0-26Z"
|
||||
/>
|
||||
<Path fill="#d4af37" d="M0-18C-4-12-4-4 0 0c4-4 4-12 0-18" />
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#d4af37"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M-3 3c-13-2-25 7-21 21C-12 24-3 12-3 3Zm6 0c13-2 25 7 21 21C12 24 3 12 3 3Z"
|
||||
/>
|
||||
<Circle cy={10} r={1.8} fill="#d4af37" />
|
||||
</G>
|
||||
|
||||
{/* Yatay çizgi */}
|
||||
<Path stroke="#d4af37" strokeOpacity={0.4} strokeWidth={0.8} d="M120 198h360" />
|
||||
|
||||
{/* QR Placeholder / Gerçek QR */}
|
||||
{!hideQr && (
|
||||
<G transform="translate(190 215)">
|
||||
<Rect width={220} height={220} fill="#fff" stroke="#d4af37" strokeWidth={2} rx={20} />
|
||||
{qrBase64 ? (
|
||||
<Image
|
||||
href={`data:image/png;base64,${qrBase64}`}
|
||||
x={10}
|
||||
y={10}
|
||||
width={200}
|
||||
height={200}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<Rect
|
||||
width={196}
|
||||
height={196}
|
||||
x={12}
|
||||
y={12}
|
||||
fill="#fafafa"
|
||||
stroke="#d4af37"
|
||||
strokeDasharray="4 3"
|
||||
strokeOpacity={0.4}
|
||||
rx={14}
|
||||
/>
|
||||
<Text
|
||||
x={110}
|
||||
y={115}
|
||||
fill="#71717a"
|
||||
fontFamily="System, sans-serif"
|
||||
fontSize={12}
|
||||
fontWeight={600}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"QR KOD ALANI"}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</G>
|
||||
)}
|
||||
|
||||
{/* Metinler (Çok satırlı ve bağımsız font destekli) */}
|
||||
<G textAnchor="middle">
|
||||
{/* Restoran Adı */}
|
||||
{rName?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: rName?.text || "LUMIÈRE",
|
||||
x: 300,
|
||||
y: 148,
|
||||
offsetY: rName?.offsetY,
|
||||
fill: "#d4af37",
|
||||
fontFamily: rNameFont,
|
||||
fontSize: rName?.fontSize || 30,
|
||||
fontWeight: "bold",
|
||||
letterSpacing: 4,
|
||||
})}
|
||||
|
||||
{/* Slogan / Alt Başlık (Enter ile alt satıra geçebilir) */}
|
||||
{sub?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: sub?.text || "RESTAURANT & BAR | MODERN CUISINE",
|
||||
x: 300,
|
||||
y: 178,
|
||||
offsetY: sub?.offsetY,
|
||||
fill: "#e5c378",
|
||||
fontFamily: subFont,
|
||||
fontSize: sub?.fontSize || 11,
|
||||
fontWeight: 600,
|
||||
letterSpacing: 2.5,
|
||||
})}
|
||||
|
||||
{/* CTA */}
|
||||
{cta?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: cta?.text || "MENÜYÜ GÖRMEK İÇİN OKUTUN",
|
||||
x: 300,
|
||||
y: 472,
|
||||
offsetY: cta?.offsetY,
|
||||
fill: "#d4af37",
|
||||
fontFamily: ctaFont,
|
||||
fontSize: cta?.fontSize || 13,
|
||||
fontWeight: "bold",
|
||||
letterSpacing: 2,
|
||||
})}
|
||||
|
||||
{/* Sosyal Medya / İletişim */}
|
||||
{social?.visible !== false &&
|
||||
renderSvgLines({
|
||||
text: social?.text || "Rezervasyon: 0212 555 0199",
|
||||
x: 300,
|
||||
y: 504,
|
||||
offsetY: social?.offsetY,
|
||||
fill: "#e2ddd2",
|
||||
fontFamily: infoFont,
|
||||
fontSize: social?.fontSize || 10.5,
|
||||
fontWeight: 500,
|
||||
letterSpacing: 1,
|
||||
})}
|
||||
|
||||
{/* Masa No & WiFi */}
|
||||
{(table?.visible !== false || wifi?.visible !== false) &&
|
||||
renderSvgLines({
|
||||
text: [
|
||||
table?.visible !== false && table?.text ? table.text : "",
|
||||
wifi?.visible !== false && wifi?.text ? wifi.text : "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" • "),
|
||||
x: 300,
|
||||
y: 526,
|
||||
offsetY: table?.offsetY || wifi?.offsetY,
|
||||
fill: "#c5a059",
|
||||
fontFamily: infoFont,
|
||||
fontSize: table?.fontSize || wifi?.fontSize || 10,
|
||||
fontWeight: 600,
|
||||
letterSpacing: 1,
|
||||
})}
|
||||
|
||||
</G>
|
||||
</Svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default QrTemplateLumiere;
|
||||
@@ -0,0 +1,120 @@
|
||||
import React from "react";
|
||||
import { Text as SvgText, type SvgProps } from "react-native-svg";
|
||||
|
||||
export interface TextItemConfig {
|
||||
text: string;
|
||||
fontSize?: number;
|
||||
fontId?: string;
|
||||
offsetY?: number; // Dikey konum kaydırma (yukarı / aşağı)
|
||||
visible?: boolean;
|
||||
}
|
||||
|
||||
export interface QrCustomFields {
|
||||
restaurantName: TextItemConfig;
|
||||
subtitle: TextItemConfig;
|
||||
tableNo: TextItemConfig;
|
||||
ctaText: TextItemConfig;
|
||||
wifiText: TextItemConfig;
|
||||
socialText: TextItemConfig;
|
||||
}
|
||||
|
||||
export interface QrTemplateProps extends SvgProps {
|
||||
qrBase64?: string;
|
||||
fields?: Partial<QrCustomFields>;
|
||||
hideQr?: boolean;
|
||||
}
|
||||
|
||||
import { Platform } from "react-native";
|
||||
|
||||
// Türkçe karakterleri (İ, ı, Ş, ş, Ğ, ğ, Ü, ü, Ö, ö, Ç, ç) eksiksiz destekleyen sistem fontları
|
||||
export const FONT_OPTIONS = [
|
||||
{
|
||||
id: "sans",
|
||||
label: "Modern Sans",
|
||||
fontFamily: Platform.OS === "ios" ? "System" : "sans-serif",
|
||||
},
|
||||
{
|
||||
id: "serif",
|
||||
label: "Zarif Serif",
|
||||
fontFamily: Platform.OS === "ios" ? "Georgia" : "serif",
|
||||
},
|
||||
{
|
||||
id: "times",
|
||||
label: "Klasik (Times)",
|
||||
fontFamily: Platform.OS === "ios" ? "Times New Roman" : "serif",
|
||||
},
|
||||
{
|
||||
id: "palatino",
|
||||
label: "Lüks Palatino",
|
||||
fontFamily: Platform.OS === "ios" ? "Palatino" : "serif",
|
||||
},
|
||||
{
|
||||
id: "trebuchet",
|
||||
label: "Dinamik",
|
||||
fontFamily: Platform.OS === "ios" ? "Trebuchet MS" : "sans-serif",
|
||||
},
|
||||
];
|
||||
|
||||
export function getFontFamily(fontId?: string, defaultFamily = "Georgia"): string {
|
||||
if (!fontId) {
|
||||
return defaultFamily.includes(",") ? defaultFamily.split(",")[0].trim().replace(/['"]/g, "") : defaultFamily;
|
||||
}
|
||||
const found = FONT_OPTIONS.find((f) => f.id === fontId);
|
||||
if (found) return found.fontFamily;
|
||||
if (fontId.includes(",")) {
|
||||
return fontId.split(",")[0].trim().replace(/['"]/g, "");
|
||||
}
|
||||
return fontId;
|
||||
}
|
||||
|
||||
// Çok satırlı (Enter / \n) ve dikey kaydırmalı (offsetY) SVG metin render fonksiyonu
|
||||
export function renderSvgLines({
|
||||
text,
|
||||
x,
|
||||
y,
|
||||
offsetY = 0,
|
||||
lineHeight,
|
||||
fill,
|
||||
fontFamily,
|
||||
fontSize,
|
||||
fontWeight = "normal",
|
||||
letterSpacing,
|
||||
textAnchor = "middle",
|
||||
}: {
|
||||
text?: string;
|
||||
x: number;
|
||||
y: number;
|
||||
offsetY?: number;
|
||||
lineHeight?: number;
|
||||
fill?: string;
|
||||
fontFamily?: string;
|
||||
fontSize?: number;
|
||||
fontWeight?: string | number;
|
||||
letterSpacing?: number;
|
||||
textAnchor?: "middle" | "start" | "end";
|
||||
}) {
|
||||
if (!text) return null;
|
||||
const lines = text.split("\n");
|
||||
const computedLineHeight = lineHeight || (fontSize ? fontSize * 1.25 : 18);
|
||||
const startY = y + (offsetY || 0);
|
||||
|
||||
return (
|
||||
<>
|
||||
{lines.map((line, idx) => (
|
||||
<SvgText
|
||||
key={idx}
|
||||
x={x}
|
||||
y={startY + idx * computedLineHeight}
|
||||
fill={fill}
|
||||
fontFamily={fontFamily}
|
||||
fontSize={fontSize}
|
||||
fontWeight={fontWeight}
|
||||
letterSpacing={letterSpacing}
|
||||
textAnchor={textAnchor}
|
||||
>
|
||||
{line}
|
||||
</SvgText>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import React from "react";
|
||||
import QrTemplate1 from "./QrTemplateLumiere";
|
||||
import QrTemplate2 from "./QrTemplate2";
|
||||
import QrTemplate3 from "./QrTemplate3";
|
||||
import QrTemplate4 from "./QrTemplate4";
|
||||
import QrTemplate5 from "./QrTemplate5";
|
||||
import QrTemplate6 from "./QrTemplate6";
|
||||
import QrTemplate8 from "./QrTemplate8";
|
||||
import QrTemplate9 from "./QrTemplate9";
|
||||
import { type QrTemplateProps } from "./common";
|
||||
|
||||
export * from "./common";
|
||||
|
||||
// Yeni component eklemek için buraya import edip COMPONENT_TEMPLATES'e kaydet
|
||||
export const COMPONENT_TEMPLATES: Record<
|
||||
string,
|
||||
React.FC<QrTemplateProps>
|
||||
> = {
|
||||
"qr-1": QrTemplate1,
|
||||
"qr-2": QrTemplate2,
|
||||
"qr-3": QrTemplate3,
|
||||
"qr-4": QrTemplate4,
|
||||
"qr-5": QrTemplate5,
|
||||
"qr-6": QrTemplate6,
|
||||
"qr-8": QrTemplate8,
|
||||
"qr-9": QrTemplate9,
|
||||
};
|
||||
|
||||
// Her template'in aspect ratio'su (width/height)
|
||||
export const TEMPLATE_ASPECT_RATIOS: Record<string, number> = {
|
||||
"qr-1": 1, // 600x600 = 1:1
|
||||
"qr-2": 400 / 650, // 400x650 = standart kart
|
||||
"qr-3": 400 / 650, // 400x650 = standart kart
|
||||
"qr-4": 400 / 580, // 400x580 = zarif altın çerçeve
|
||||
"qr-5": 380 / 500, // 380x500 = PNG arka planlı modern kart
|
||||
"qr-6": 400 / 580, // 400x580 = taş & ızgara vintage stant
|
||||
"qr-8": 1, // 1024x1024 = 1:1 Emerald Gold Özel Tasarım
|
||||
"qr-9": 380 / 500, // 380x500 = Vintage Table Menu Özel Tasarım
|
||||
};
|
||||
@@ -0,0 +1,177 @@
|
||||
import * as React from "react"
|
||||
import Svg, { SvgProps, Path, Rect, Circle, G, Text } from "react-native-svg"
|
||||
const SvgComponent = (props: SvgProps) => (
|
||||
<Svg xmlns="http://www.w3.org/2000/svg" width={600} height={600} {...props}>
|
||||
<Path fill="#0b0d13" d="M0 0h600v600H0z" />
|
||||
<Rect
|
||||
width={560}
|
||||
height={560}
|
||||
x={20}
|
||||
y={20}
|
||||
fill="#132034"
|
||||
stroke="#d4af37"
|
||||
strokeWidth={1.5}
|
||||
rx={24}
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#d4af37"
|
||||
strokeOpacity={0.85}
|
||||
strokeWidth={1.5}
|
||||
d="M56 40h488a16 16 0 0 1 16 16v488a16 16 0 0 1-16 16H56a16 16 0 0 1-16-16V56a16 16 0 0 1 16-16Z"
|
||||
/>
|
||||
<Rect
|
||||
width={504}
|
||||
height={504}
|
||||
x={48}
|
||||
y={48}
|
||||
fill="none"
|
||||
stroke="#c5a059"
|
||||
strokeOpacity={0.45}
|
||||
strokeWidth={0.8}
|
||||
rx={8}
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#d4af37"
|
||||
strokeWidth={1.5}
|
||||
d="M40 68a18 18 0 0 0 28-28"
|
||||
/>
|
||||
<Circle cx={52} cy={52} r={2} fill="#d4af37" />
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#d4af37"
|
||||
strokeWidth={1.5}
|
||||
d="M560 68a18 18 0 0 1-28-28"
|
||||
/>
|
||||
<Circle cx={548} cy={52} r={2} fill="#d4af37" />
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#d4af37"
|
||||
strokeWidth={1.5}
|
||||
d="M40 532a18 18 0 0 1 28 28"
|
||||
/>
|
||||
<Circle cx={52} cy={548} r={2} fill="#d4af37" />
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#d4af37"
|
||||
strokeWidth={1.5}
|
||||
d="M560 532a18 18 0 0 0-28 28"
|
||||
/>
|
||||
<Circle cx={548} cy={548} r={2} fill="#d4af37" />
|
||||
<G transform="translate(300 85)">
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#d4af37"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M0-26C-8-16-8-6 0 0c8-6 8-16 0-26Z"
|
||||
/>
|
||||
<Path fill="#d4af37" d="M0-18C-4-12-4-4 0 0c4-4 4-12 0-18" />
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#d4af37"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M-3 3c-13-2-25 7-21 21C-12 24-3 12-3 3Zm6 0c13-2 25 7 21 21C12 24 3 12 3 3Z"
|
||||
/>
|
||||
<Circle cy={10} r={1.8} fill="#d4af37" />
|
||||
</G>
|
||||
<Path
|
||||
stroke="#d4af37"
|
||||
strokeOpacity={0.4}
|
||||
strokeWidth={0.8}
|
||||
d="M120 198h360"
|
||||
/>
|
||||
<G transform="translate(190 215)">
|
||||
<Rect
|
||||
width={220}
|
||||
height={220}
|
||||
fill="#fff"
|
||||
stroke="#d4af37"
|
||||
strokeWidth={2}
|
||||
rx={20}
|
||||
/>
|
||||
<Rect
|
||||
width={196}
|
||||
height={196}
|
||||
x={12}
|
||||
y={12}
|
||||
fill="#fafafa"
|
||||
stroke="#d4af37"
|
||||
strokeDasharray="4 3"
|
||||
strokeOpacity={0.4}
|
||||
rx={14}
|
||||
/>
|
||||
<Text
|
||||
x={110}
|
||||
y={115}
|
||||
fill="#71717a"
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={12}
|
||||
fontWeight={600}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"QR KOD ALANI"}
|
||||
</Text>
|
||||
</G>
|
||||
<G textAnchor="middle">
|
||||
<Text
|
||||
x={300}
|
||||
y={152}
|
||||
fill="#d4af37"
|
||||
fontFamily="'Playfair Display', 'Cinzel', 'Didot', Georgia, serif"
|
||||
fontSize={38}
|
||||
fontWeight={500}
|
||||
letterSpacing={6}
|
||||
>
|
||||
{"LUMI\xC8RE"}
|
||||
</Text>
|
||||
<Text
|
||||
x={300}
|
||||
y={180}
|
||||
fill="#e5c378"
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={11}
|
||||
fontWeight={600}
|
||||
letterSpacing={4}
|
||||
>
|
||||
{"RESTAURANT & BAR | MODERN CUISINE"}
|
||||
</Text>
|
||||
<Text
|
||||
x={300}
|
||||
y={472}
|
||||
fill="#d4af37"
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={13}
|
||||
fontWeight={600}
|
||||
letterSpacing={3}
|
||||
>
|
||||
{"SCAN WITH CAMERA"}
|
||||
</Text>
|
||||
<Text
|
||||
x={300}
|
||||
y={508}
|
||||
fill="#e2ddd2"
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={10.5}
|
||||
fontWeight={400}
|
||||
letterSpacing={1.2}
|
||||
>
|
||||
{"Reservations: LumiereDining.com | 240-555-0199"}
|
||||
</Text>
|
||||
<Text
|
||||
x={300}
|
||||
y={528}
|
||||
fill="#c5a059"
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={9.5}
|
||||
fontWeight={500}
|
||||
letterSpacing={1.8}
|
||||
>
|
||||
{"TABLE 01 \u2022 WIFI: Lumiere_Guest"}
|
||||
</Text>
|
||||
</G>
|
||||
</Svg>
|
||||
)
|
||||
export default SvgComponent
|
||||
@@ -0,0 +1,286 @@
|
||||
import * as React from "react"
|
||||
import Svg, {
|
||||
SvgProps,
|
||||
Path,
|
||||
Rect,
|
||||
G,
|
||||
Circle,
|
||||
Ellipse,
|
||||
Text,
|
||||
} from "react-native-svg"
|
||||
const SvgComponent = (props: SvgProps) => (
|
||||
<Svg xmlns="http://www.w3.org/2000/svg" width={400} height={650} {...props}>
|
||||
<Path fill="#252c36" d="M0 0h400v650H0z" />
|
||||
<Rect
|
||||
width={360}
|
||||
height={610}
|
||||
x={20}
|
||||
y={20}
|
||||
fill="#f6efe2"
|
||||
stroke="#3a2312"
|
||||
strokeWidth={1.5}
|
||||
rx={20}
|
||||
/>
|
||||
<Rect
|
||||
width={344}
|
||||
height={594}
|
||||
x={28}
|
||||
y={28}
|
||||
fill="none"
|
||||
stroke="#8c6239"
|
||||
strokeOpacity={0.4}
|
||||
rx={14}
|
||||
/>
|
||||
<Rect
|
||||
width={336}
|
||||
height={586}
|
||||
x={32}
|
||||
y={32}
|
||||
fill="none"
|
||||
stroke="#8c6239"
|
||||
strokeOpacity={0.25}
|
||||
strokeWidth={0.5}
|
||||
rx={10}
|
||||
/>
|
||||
<G fill="none" stroke="#3a2312" strokeLinecap="round" strokeWidth={1.05}>
|
||||
<Path d="M36 36c11.25 11.25 18.75 26.25 22.5 48.75" />
|
||||
<Path
|
||||
fill="#eadcc7"
|
||||
d="M43.5 45c-3.75-7.5 7.5-10.5 9-3 0 7.5-9 3-9 3Zm6 11.25C45 51 57 46.5 60 53.25c0 6.75-10.5 3-10.5 3Zm4.5 13.5C60 64.5 69.75 72 64.5 78c-6 4.5-10.5-8.25-10.5-8.25Z"
|
||||
/>
|
||||
</G>
|
||||
<G transform="translate(340 52)">
|
||||
<Circle r={32} fill="#eadcc7" stroke="#3a2312" strokeWidth={1.2} />
|
||||
<Circle
|
||||
r={26}
|
||||
fill="#f6efe2"
|
||||
stroke="#8c6239"
|
||||
strokeDasharray="2 2"
|
||||
strokeWidth={0.8}
|
||||
/>
|
||||
<Circle r={21} fill="#7a4b26" stroke="#3a2312" strokeWidth={1.4} />
|
||||
<Path fill="#f6efe2" d="M0 10C-8 4-4-6 0-12 4-6 8 4 0 10" />
|
||||
<Path fill="#7a4b26" d="M0 4C-5 0-2-5 0-8 2-5 5 0 0 4" />
|
||||
<Circle cy={-14} r={1.8} fill="#f6efe2" />
|
||||
</G>
|
||||
<G transform="translate(48 250)">
|
||||
<Circle r={26} fill="#eadcc7" stroke="#3a2312" />
|
||||
<Circle r={17} fill="#6a3b18" stroke="#3a2312" strokeWidth={1.2} />
|
||||
<Path fill="#f6efe2" d="M-5-3c-3-4 5-7 5-1 0-6 8-3 5 1L0 7z" />
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#3a2312"
|
||||
strokeWidth={1.5}
|
||||
d="M17-4c6 0 6 8 0 8"
|
||||
/>
|
||||
</G>
|
||||
<G fill="none" stroke="#3a2312" strokeWidth={0.91}>
|
||||
<Path d="M352 230c-7 21-10.5 49-3.5 77" />
|
||||
<Path
|
||||
fill="#eadcc7"
|
||||
d="M349.2 247.5c8.4-7 15.4 2.1 5.6 7.7-5.6 0-5.6-7.7-5.6-7.7Zm-2.8 24.5c-9.8-8.4-15.4 1.4-7 7 7 0 7-7 7-7Zm2.1 19.6c9.1-7 13.3 2.8 4.9 8.4-4.9 0-4.9-8.4-4.9-8.4Z"
|
||||
/>
|
||||
</G>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#8c6239"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={1.2}
|
||||
d="M142 66c6-2 12 2 18 0m80 0c6 2 12-2 18 0"
|
||||
/>
|
||||
<G fill="none" stroke="#3a2312" strokeWidth={0.91}>
|
||||
<Path d="M42 601.5c13-26 9.75-52 0-71.5" />
|
||||
<Path
|
||||
fill="#eadcc7"
|
||||
d="M47.2 549.5c10.4-9.75 17.55 0 6.5 7.8Zm2.6 26c11.7-9.75 18.2 1.3 5.2 9.1Z"
|
||||
/>
|
||||
</G>
|
||||
<G
|
||||
stroke="#3a2312"
|
||||
strokeWidth={1.2}
|
||||
transform="rotate(-20 1741.206 -8.75) scale(.8)"
|
||||
>
|
||||
<Ellipse fill="#cbb296" rx={9} ry={6.5} />
|
||||
<Path fill="none" d="M-7 0q7 3 7 0t7 0" />
|
||||
</G>
|
||||
<G
|
||||
stroke="#3a2312"
|
||||
strokeWidth={1.2}
|
||||
transform="rotate(35 -862.106 494.982) scale(.8)"
|
||||
>
|
||||
<Ellipse fill="#cbb296" rx={9} ry={6.5} />
|
||||
<Path fill="none" d="M-7 0q7 3 7 0t7 0" />
|
||||
</G>
|
||||
<G
|
||||
stroke="#3a2312"
|
||||
strokeWidth={1.2}
|
||||
transform="rotate(-45 602.558 -225.588) scale(.7)"
|
||||
>
|
||||
<Ellipse fill="#cbb296" rx={9} ry={6.5} />
|
||||
<Path fill="none" d="M-7 0q7 3 7 0t7 0" />
|
||||
</G>
|
||||
<G transform="translate(325 590)">
|
||||
<Ellipse
|
||||
cy={18}
|
||||
fill="#eadcc7"
|
||||
stroke="#3a2312"
|
||||
strokeWidth={1.2}
|
||||
rx={28}
|
||||
ry={7}
|
||||
/>
|
||||
<Path
|
||||
fill="#f6efe2"
|
||||
stroke="#3a2312"
|
||||
strokeWidth={1.4}
|
||||
d="M-18 2c0 14 36 14 36 0Z"
|
||||
/>
|
||||
<Ellipse
|
||||
cy={2}
|
||||
fill="#6a3b18"
|
||||
stroke="#3a2312"
|
||||
strokeWidth={1.2}
|
||||
rx={18}
|
||||
ry={4}
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#3a2312"
|
||||
strokeWidth={1.3}
|
||||
d="M16 4c8 0 8 8 0 10"
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#8c6239"
|
||||
strokeLinecap="round"
|
||||
d="M-6-4c-2-5 2-8 0-13M2-5c-2-5 2-8 0-14"
|
||||
/>
|
||||
</G>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#3a2312"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={2}
|
||||
d="M80 205c0-10 0-10 10-10m230 10c0-10 0-10-10-10M80 445c0 10 0 10 10 10m230-10c0 10 0 10-10 10"
|
||||
/>
|
||||
<Path stroke="#8c6239" strokeWidth={0.8} d="M145 608h30m50 0h30" />
|
||||
<G transform="translate(90 215)">
|
||||
<Rect
|
||||
width={220}
|
||||
height={220}
|
||||
fill="#fbf8f2"
|
||||
stroke="#d8c7b0"
|
||||
strokeWidth={1.5}
|
||||
rx={20}
|
||||
/>
|
||||
<Rect
|
||||
width={200}
|
||||
height={200}
|
||||
x={10}
|
||||
y={10}
|
||||
fill="#f5efe6"
|
||||
stroke="#d8c7b0"
|
||||
strokeDasharray="4 3"
|
||||
rx={14}
|
||||
/>
|
||||
<Circle cx={110} cy={98} r={18} fill="#fbf8f2" stroke="#8c6239" />
|
||||
<G fill="#3a2312" transform="matrix(.65 0 0 .65 110 98)">
|
||||
<Ellipse cx={-5} cy={2} rx={7} ry={5} transform="rotate(-30)" />
|
||||
<Ellipse cx={5} rx={7} ry={5} transform="rotate(40)" />
|
||||
</G>
|
||||
<Text
|
||||
x={110}
|
||||
y={132}
|
||||
fill="#8c6239"
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={11}
|
||||
fontWeight={700}
|
||||
letterSpacing={1}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"QR KOD ALANI"}
|
||||
</Text>
|
||||
</G>
|
||||
<G textAnchor="middle">
|
||||
<Text
|
||||
x={200}
|
||||
y={70}
|
||||
fill="#3a2312"
|
||||
fontFamily="'Playfair Display', Georgia, serif"
|
||||
fontSize={14}
|
||||
fontStyle="italic"
|
||||
fontWeight={700}
|
||||
letterSpacing={4}
|
||||
>
|
||||
{"THE"}
|
||||
</Text>
|
||||
<Text
|
||||
x={200}
|
||||
y={114}
|
||||
fill="#2c1a0e"
|
||||
fontFamily="'Playfair Display', 'Brush Script MT', 'Great Vibes', Georgia, cursive, serif"
|
||||
fontSize={34}
|
||||
fontWeight={800}
|
||||
letterSpacing={0.5}
|
||||
>
|
||||
{"The Daily Grind"}
|
||||
</Text>
|
||||
<Text
|
||||
x={200}
|
||||
y={146}
|
||||
fill="#3a2312"
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={13}
|
||||
fontWeight={800}
|
||||
letterSpacing={3}
|
||||
>
|
||||
{"COFFEE & KITCHEN"}
|
||||
</Text>
|
||||
<Text
|
||||
x={200}
|
||||
y={488}
|
||||
fill="#2c1a0e"
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={19}
|
||||
fontWeight={900}
|
||||
letterSpacing={1.5}
|
||||
>
|
||||
{"SCAN TO VIEW MENU"}
|
||||
</Text>
|
||||
<Text
|
||||
x={200}
|
||||
y={520}
|
||||
fill="#3a2312"
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={10.5}
|
||||
fontWeight={700}
|
||||
letterSpacing={0.5}
|
||||
>
|
||||
{"DAILY GRIND CAFE | WIFI: DAILY_GRIND_GUEST"}
|
||||
</Text>
|
||||
<Text
|
||||
x={200}
|
||||
y={542}
|
||||
fill="#8c6239"
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={9}
|
||||
fontWeight={600}
|
||||
letterSpacing={1}
|
||||
>
|
||||
{"\u015E\u0130FRE: coffee2026 \u2022 MASA NO: 04"}
|
||||
</Text>
|
||||
<Text
|
||||
x={200}
|
||||
y={611}
|
||||
fill="#3a2312"
|
||||
fontFamily="'Playfair Display', Georgia, serif"
|
||||
fontSize={11}
|
||||
fontStyle="italic"
|
||||
fontWeight={700}
|
||||
letterSpacing={2}
|
||||
>
|
||||
{"EST. 2018"}
|
||||
</Text>
|
||||
</G>
|
||||
</Svg>
|
||||
)
|
||||
export default SvgComponent
|
||||
@@ -0,0 +1,416 @@
|
||||
import * as React from "react"
|
||||
import Svg, {
|
||||
SvgProps,
|
||||
Defs,
|
||||
LinearGradient,
|
||||
Stop,
|
||||
Pattern,
|
||||
Path,
|
||||
G,
|
||||
Rect,
|
||||
Ellipse,
|
||||
Circle,
|
||||
Text,
|
||||
TSpan,
|
||||
} from "react-native-svg"
|
||||
/* SVGR has dropped some elements not supported by react-native-svg: style */
|
||||
const SvgComponent = (props: SvgProps) => (
|
||||
<Svg width={400} height={650} {...props}>
|
||||
<Defs>
|
||||
<LinearGradient id="card-grad" x1="0%" x2="0%" y1="0%" y2="100%">
|
||||
<Stop offset="0%" stopColor="#fdfbf5" />
|
||||
<Stop offset="50%" stopColor="#faf4e6" />
|
||||
<Stop offset="100%" stopColor="#f7eed8" />
|
||||
</LinearGradient>
|
||||
<LinearGradient id="oven-fire" x1="0%" x2="0%" y1="100%" y2="0%">
|
||||
<Stop offset="0%" stopColor="#c0392b" />
|
||||
<Stop offset="50%" stopColor="#e67e22" />
|
||||
<Stop offset="100%" stopColor="#f1c40f" />
|
||||
</LinearGradient>
|
||||
<LinearGradient id="wine-grad" x1="0%" x2="0%" y1="0%" y2="100%">
|
||||
<Stop offset="0%" stopColor="#7b1123" />
|
||||
<Stop offset="100%" stopColor="#4a0512" />
|
||||
</LinearGradient>
|
||||
<Pattern
|
||||
id="checkered-border"
|
||||
width={20}
|
||||
height={20}
|
||||
patternUnits="userSpaceOnUse"
|
||||
>
|
||||
<Path fill="#b92b27" d="M0 0h20v20H0z" />
|
||||
<Path fill="#fdf8ee" d="M0 0h10v10H0zm10 10h10v10H10z" />
|
||||
</Pattern>
|
||||
</Defs>
|
||||
<Path
|
||||
id="layer-background"
|
||||
fill="url(#checkered-border)"
|
||||
d="M0 0h400v650H0z"
|
||||
/>
|
||||
<G id="layer-card">
|
||||
<Rect
|
||||
width={356}
|
||||
height={606}
|
||||
x={22}
|
||||
y={22}
|
||||
fill="url(#card-grad)"
|
||||
stroke="#b92b27"
|
||||
strokeWidth={2}
|
||||
rx={20}
|
||||
/>
|
||||
<Rect
|
||||
width={344}
|
||||
height={594}
|
||||
x={28}
|
||||
y={28}
|
||||
fill="none"
|
||||
stroke="#8c6239"
|
||||
strokeOpacity={0.5}
|
||||
strokeWidth={0.8}
|
||||
rx={15}
|
||||
/>
|
||||
<Rect
|
||||
width={140}
|
||||
height={24}
|
||||
x={130}
|
||||
y={34}
|
||||
fill="#faf4e6"
|
||||
stroke="#8c6239"
|
||||
strokeWidth={1.2}
|
||||
rx={12}
|
||||
/>
|
||||
</G>
|
||||
<G id="layer-icons">
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c5a880"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={1.5}
|
||||
d="M68 88q12-13 28-2 6 5 10 2m226 0q-12-13-28-2-6 5-10 2"
|
||||
/>
|
||||
<G id="pizza-oven" transform="translate(200 192)">
|
||||
<Rect
|
||||
width={88}
|
||||
height={6}
|
||||
x={-44}
|
||||
fill="#d7ccc8"
|
||||
stroke="#5d4037"
|
||||
rx={2}
|
||||
/>
|
||||
<Path
|
||||
fill="#d7ccc8"
|
||||
stroke="#5d4037"
|
||||
strokeWidth={1.5}
|
||||
d="M-40 0a40 38 0 0 1 80 0Z"
|
||||
/>
|
||||
<Path
|
||||
stroke="#8d6e63"
|
||||
d="m-30-25 7 6m6-15 4 8M0-38v9m17-5-4 8m17 1-7 6"
|
||||
/>
|
||||
<Path fill="#3e2723" stroke="#271c19" d="M-28 0a28 26 0 0 1 56 0Z" />
|
||||
<Path
|
||||
fill="url(#oven-fire)"
|
||||
d="M-18 0q3-16 12-10 6-12 12-2 9-6 12 12Z"
|
||||
/>
|
||||
<Path fill="#fff9c4" d="M-10 0q5-10 10-6 5-6 10 6Z" />
|
||||
</G>
|
||||
<G id="left-ingredients" transform="translate(108 185)">
|
||||
<Ellipse
|
||||
cx={-16}
|
||||
cy={6}
|
||||
fill="#d32f2f"
|
||||
stroke="#8e0000"
|
||||
strokeWidth={0.8}
|
||||
rx={14}
|
||||
ry={10}
|
||||
transform="rotate(-25 -16 6)"
|
||||
/>
|
||||
<Ellipse
|
||||
cx={-16}
|
||||
cy={6}
|
||||
fill="#e53935"
|
||||
rx={10}
|
||||
ry={6.5}
|
||||
transform="rotate(-25 -16 6)"
|
||||
/>
|
||||
<Ellipse
|
||||
cx={2}
|
||||
cy={7}
|
||||
fill="#c62828"
|
||||
stroke="#8e0000"
|
||||
strokeWidth={0.8}
|
||||
rx={13}
|
||||
ry={9}
|
||||
transform="rotate(-10 2 7)"
|
||||
/>
|
||||
<Ellipse
|
||||
cx={2}
|
||||
cy={7}
|
||||
fill="#e53935"
|
||||
rx={9}
|
||||
ry={5.5}
|
||||
transform="rotate(-10 2 7)"
|
||||
/>
|
||||
<Circle cx={2} cy={7} r={1.5} fill="#fdd835" />
|
||||
<Circle
|
||||
cx={16}
|
||||
cy={-2}
|
||||
r={14}
|
||||
fill="#e53935"
|
||||
stroke="#b71c1c"
|
||||
strokeWidth={0.8}
|
||||
/>
|
||||
<Ellipse cx={12} cy={-6} fill="#ff8a80" opacity={0.6} rx={4} ry={2} />
|
||||
<Path
|
||||
stroke="#2e7d32"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={1.5}
|
||||
d="M16-16v3m0 0-5 2m5-2 5 2m-5-2-3-2m3 2 3-2"
|
||||
/>
|
||||
</G>
|
||||
<G id="right-ingredients" transform="translate(290 182)">
|
||||
<Circle
|
||||
cx={-14}
|
||||
cy={2}
|
||||
r={15}
|
||||
fill="#e53935"
|
||||
stroke="#b71c1c"
|
||||
strokeWidth={0.8}
|
||||
/>
|
||||
<Ellipse cx={-18} cy={-3} fill="#ff8a80" opacity={0.6} rx={4} ry={2} />
|
||||
<Path
|
||||
stroke="#2e7d32"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={1.5}
|
||||
d="M-14-13v3m0 0-4 2m4-2 4 2"
|
||||
/>
|
||||
<Path
|
||||
fill="#2e7d32"
|
||||
stroke="#1b5e20"
|
||||
strokeWidth={0.8}
|
||||
d="M-2 0C8-16 26-12 24 2 22 14 6 10-2 0Z"
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#4caf50"
|
||||
strokeWidth={0.7}
|
||||
d="M-2 0q14-4 26 2"
|
||||
/>
|
||||
<Path
|
||||
fill="#388e3c"
|
||||
stroke="#1b5e20"
|
||||
strokeWidth={0.8}
|
||||
d="M8 6c10-6 24 4 18 16-8 6-18-4-18-16Z"
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#81c784"
|
||||
strokeWidth={0.7}
|
||||
d="M8 6q12 6 18 16"
|
||||
/>
|
||||
</G>
|
||||
<G id="footer-pizza-slice" transform="rotate(-10 3278.565 44.969)">
|
||||
<Path
|
||||
fill="#deac68"
|
||||
stroke="#8d6e63"
|
||||
strokeWidth={1.2}
|
||||
d="m0 0 46-18c4 8 6 32 0 40Z"
|
||||
/>
|
||||
<Path fill="#fbc02d" d="m2 0 40-15c3 7 5 25 0 33Z" />
|
||||
<Circle
|
||||
cx={28}
|
||||
cy={-5}
|
||||
r={5}
|
||||
fill="#c62828"
|
||||
stroke="#8e0000"
|
||||
strokeWidth={0.8}
|
||||
/>
|
||||
<Circle
|
||||
cx={32}
|
||||
cy={8}
|
||||
r={4.5}
|
||||
fill="#c62828"
|
||||
stroke="#8e0000"
|
||||
strokeWidth={0.8}
|
||||
/>
|
||||
<Circle
|
||||
cx={16}
|
||||
r={3.5}
|
||||
fill="#c62828"
|
||||
stroke="#8e0000"
|
||||
strokeWidth={0.8}
|
||||
/>
|
||||
<Ellipse
|
||||
cx={23}
|
||||
cy={4}
|
||||
fill="#2e7d32"
|
||||
rx={2}
|
||||
ry={4}
|
||||
transform="rotate(30 23 4)"
|
||||
/>
|
||||
<Ellipse
|
||||
cx={34}
|
||||
cy={-11}
|
||||
fill="#2e7d32"
|
||||
rx={2}
|
||||
ry={3}
|
||||
transform="rotate(-40 34 -11)"
|
||||
/>
|
||||
</G>
|
||||
<G id="footer-wine-glass" transform="translate(345 562)">
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#71717a"
|
||||
strokeWidth={1.2}
|
||||
d="M-14-24C-15-8-11 4 0 6 11 4 15-8 14-24Z"
|
||||
/>
|
||||
<Path
|
||||
fill="url(#wine-grad)"
|
||||
d="M-13-12C-11 0-7 4 0 5.5 7 4 11 0 13-12Z"
|
||||
/>
|
||||
<Ellipse cy={-12} fill="#991b1b" rx={13} ry={2} />
|
||||
<Path stroke="#71717a" strokeWidth={1.4} d="M0 6v22" />
|
||||
<Ellipse
|
||||
cy={28}
|
||||
fill="#e4e4e7"
|
||||
stroke="#71717a"
|
||||
strokeWidth={1.2}
|
||||
rx={11}
|
||||
ry={2}
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#fff"
|
||||
strokeLinecap="round"
|
||||
d="M-10-20c-2 8 1 18 6 23"
|
||||
opacity={0.6}
|
||||
/>
|
||||
</G>
|
||||
<G id="qr-corner-accents">
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#2e7d32"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={3}
|
||||
d="M78 227v-22h22"
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#b92b27"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={3}
|
||||
d="M322 227v-22h-22M78 423v22h22"
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#2e7d32"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={3}
|
||||
d="M322 423v22h-22"
|
||||
/>
|
||||
</G>
|
||||
</G>
|
||||
<G id="qr-placeholder" transform="translate(90 215)">
|
||||
<Rect
|
||||
width={220}
|
||||
height={220}
|
||||
fill="#fff"
|
||||
stroke="#e4e4e7"
|
||||
strokeWidth={2}
|
||||
rx={20}
|
||||
/>
|
||||
<Rect
|
||||
width={196}
|
||||
height={196}
|
||||
x={12}
|
||||
y={12}
|
||||
fill="#fafafa"
|
||||
stroke="#e4e4e7"
|
||||
strokeDasharray="4 3"
|
||||
rx={14}
|
||||
/>
|
||||
<Text
|
||||
x={110}
|
||||
y={115}
|
||||
fill="#71717a"
|
||||
dominantBaseline="middle"
|
||||
fontFamily='"Inter",Arial,sans-serif'
|
||||
fontSize={12}
|
||||
fontWeight={700}
|
||||
letterSpacing={0.5}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"QR KOD ALANI"}
|
||||
</Text>
|
||||
</G>
|
||||
<G id="layer-texts">
|
||||
<Text
|
||||
x={200}
|
||||
y={50}
|
||||
fill="#18181b"
|
||||
fontFamily='"Inter",Arial,sans-serif'
|
||||
fontSize={11}
|
||||
fontWeight={800}
|
||||
letterSpacing={1.2}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"MASA NO: 01"}
|
||||
</Text>
|
||||
<Text
|
||||
x={200}
|
||||
y={96}
|
||||
className="font-brand-arc"
|
||||
fontSize={24}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"PIZZERIA"}
|
||||
</Text>
|
||||
<Text
|
||||
x={200}
|
||||
y={132}
|
||||
className="font-brand-arc"
|
||||
fontSize={31}
|
||||
letterSpacing={1}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"BELLA NAPOLI"}
|
||||
</Text>
|
||||
<Text
|
||||
x={200}
|
||||
y={222}
|
||||
fill="#2e7d32"
|
||||
fontFamily='"Inter",Arial,sans-serif'
|
||||
fontSize={11.5}
|
||||
fontWeight={800}
|
||||
letterSpacing={1.8}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"AUTENTICA CUCINA ITALIANA"}
|
||||
</Text>
|
||||
<Text x={200} y={474} className="font-cta" textAnchor="middle">
|
||||
{"MEN\xDCY\xDC G\xD6RMEK"}
|
||||
</Text>
|
||||
<Text x={200} y={494} className="font-cta" textAnchor="middle">
|
||||
{"\u0130\xC7\u0130N OKUTUN"}
|
||||
</Text>
|
||||
<Text x={200} y={546} textAnchor="middle">
|
||||
<TSpan className="font-footer-bold">{"WiFi: "}</TSpan>
|
||||
<TSpan className="font-footer-reg">{"GourmetBistro"}</TSpan>
|
||||
<TSpan fill="#a1a1aa" className="font-footer-reg">
|
||||
{" | "}
|
||||
</TSpan>
|
||||
<TSpan className="font-footer-bold">{"\u015Eifre: "}</TSpan>
|
||||
<TSpan className="font-footer-reg">{"lezzetli01"}</TSpan>
|
||||
</Text>
|
||||
<Text x={200} y={570} textAnchor="middle">
|
||||
<TSpan fill="#b92b27" className="font-footer-bold">
|
||||
{"B\u0130Z\u0130 TAK\u0130P ED\u0130N"}
|
||||
</TSpan>
|
||||
<TSpan fill="#a1a1aa" className="font-footer-reg">
|
||||
{" | "}
|
||||
</TSpan>
|
||||
<TSpan className="font-footer-reg">{"@gourmetbistro"}</TSpan>
|
||||
</Text>
|
||||
</G>
|
||||
</Svg>
|
||||
)
|
||||
export default SvgComponent
|
||||
@@ -0,0 +1,237 @@
|
||||
import Svg, {
|
||||
SvgProps,
|
||||
Defs,
|
||||
G,
|
||||
Path,
|
||||
Circle,
|
||||
Use,
|
||||
Rect,
|
||||
Text,
|
||||
} from "react-native-svg"
|
||||
import { memo } from "react"
|
||||
const SvgComponent = (props: SvgProps) => (
|
||||
<Svg width={400} height={580} {...props}>
|
||||
<Defs>
|
||||
<G id="b">
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b4b"
|
||||
strokeWidth={1.3}
|
||||
d="M0-22c22-8 36 6 38 22-2 16-16 30-38 22Z"
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b4b"
|
||||
strokeWidth={0.9}
|
||||
d="M0-12c14-6 24 2 26 12-2 10-12 18-26 12Z"
|
||||
/>
|
||||
<Path stroke="#c59b4b" strokeWidth={0.8} d="M0 0h26" />
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b4b"
|
||||
strokeWidth={1.2}
|
||||
d="M0-8c16-28 34-32 46-18C36-12 20-4 0-8ZM0 8c16 28 34 32 46 18C36 12 20 4 0 8Z"
|
||||
/>
|
||||
<Circle cx={48} cy={-28} r={1.5} fill="#c59b4b" />
|
||||
<Circle cx={48} cy={28} r={1.5} fill="#c59b4b" />
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b4b"
|
||||
strokeWidth={1.1}
|
||||
d="M0-35c24-21 52-15 56-1-14 6-32 12-56 1Zm0 70c24 21 52 15 56 1-14-6-32-12-56-1Z"
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b4b"
|
||||
d="M28-44c14-20 36-10 30 2-10 6-20 4-30-2Zm0 88c14 20 36 10 30-2-10-6-20-4-30 2Z"
|
||||
/>
|
||||
<Circle cx={60} cy={-44} r={1.2} fill="#c59b4b" />
|
||||
<Circle cx={60} cy={44} r={1.2} fill="#c59b4b" />
|
||||
<Circle cx={34} cy={-58} r={1.2} fill="#c59b4b" />
|
||||
<Circle cx={34} cy={58} r={1.2} fill="#c59b4b" />
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b4b"
|
||||
strokeLinecap="round"
|
||||
d="M0-60c22-15 36-35 32-55-6 15-16 30-32 55M0 60c22 15 36 35 32 55-6-15-16-30-32-55"
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b4b"
|
||||
strokeWidth={0.9}
|
||||
d="M14-78c8-6 12 2 6 6-4 0-6-4-6-6Zm0 156c8 6 12-2 6-6-4 0-6 4-6 6Z"
|
||||
/>
|
||||
<Circle cx={32} cy={-116} r={1.5} fill="#c59b4b" />
|
||||
<Circle cx={32} cy={116} r={1.5} fill="#c59b4b" />
|
||||
</G>
|
||||
<G id="a">
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b4b"
|
||||
strokeWidth={1.3}
|
||||
d="M18 64c0-28 18-46 46-46"
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b4b"
|
||||
strokeWidth={0.8}
|
||||
d="M24 52c0-16 12-28 28-28"
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b4b"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={0.9}
|
||||
d="M28 32c4-6 10-4 8 2-2 4-8 2-6-4s10-6 14-2"
|
||||
/>
|
||||
<Circle cx={28} cy={28} r={1.8} fill="#c59b4b" />
|
||||
<Circle cx={48} cy={18} r={1.4} fill="#c59b4b" />
|
||||
<Circle cx={18} cy={48} r={1.4} fill="#c59b4b" />
|
||||
</G>
|
||||
</Defs>
|
||||
<Path fill="#fdfdfd" d="M0 0h400v580H0z" />
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b4b"
|
||||
strokeWidth={1.2}
|
||||
d="M20 14h360v552H20z"
|
||||
/>
|
||||
<Use href="#a" />
|
||||
<Use href="#a" transform="matrix(-1 0 0 1 400 0)" />
|
||||
<Use href="#a" transform="matrix(1 0 0 -1 0 580)" />
|
||||
<Use href="#a" transform="rotate(180 200 290)" />
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b4b"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={1.2}
|
||||
d="M132 62c8-4 16 2 24-2 4-2 2-6-2-4m114 6c-8-4-16 2-24-2-4-2-2-6 2-4"
|
||||
/>
|
||||
<Use href="#b" transform="translate(0 310)" />
|
||||
<Use href="#b" transform="matrix(-1 0 0 1 400 310)" />
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b4b"
|
||||
strokeLinecap="square"
|
||||
strokeWidth={2.2}
|
||||
d="M114 278v-58h54m118 58v-58h-54M114 342v58h54m118-58v58h-54"
|
||||
/>
|
||||
<G transform="translate(125 230)">
|
||||
<Rect
|
||||
width={150}
|
||||
height={150}
|
||||
fill="#fff"
|
||||
stroke="#eae5d9"
|
||||
strokeWidth={1.5}
|
||||
rx={12}
|
||||
/>
|
||||
<Rect
|
||||
width={134}
|
||||
height={134}
|
||||
x={8}
|
||||
y={8}
|
||||
fill="#fafaf8"
|
||||
stroke="#c59b4b"
|
||||
strokeDasharray="4 3"
|
||||
strokeOpacity={0.35}
|
||||
rx={8}
|
||||
/>
|
||||
<Text
|
||||
x={75}
|
||||
y={79}
|
||||
fill="#c59b4b"
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={10}
|
||||
fontWeight={700}
|
||||
letterSpacing={1}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"QR KOD ALANI"}
|
||||
</Text>
|
||||
</G>
|
||||
<G fill="#111" textAnchor="middle">
|
||||
<Text
|
||||
x={200}
|
||||
y={66}
|
||||
fontFamily="'Playfair Display', 'Cinzel', 'Times New Roman', serif"
|
||||
fontSize={17}
|
||||
fontWeight={700}
|
||||
letterSpacing={3}
|
||||
>
|
||||
{"THE"}
|
||||
</Text>
|
||||
<Text
|
||||
x={200}
|
||||
y={112}
|
||||
fontFamily="'Playfair Display', 'Cinzel', 'Times New Roman', serif"
|
||||
fontSize={34}
|
||||
fontWeight={700}
|
||||
letterSpacing={1.5}
|
||||
>
|
||||
{"CRIMSON"}
|
||||
</Text>
|
||||
<Text
|
||||
x={200}
|
||||
y={152}
|
||||
fontFamily="'Playfair Display', 'Cinzel', 'Times New Roman', serif"
|
||||
fontSize={34}
|
||||
fontWeight={700}
|
||||
letterSpacing={1.5}
|
||||
>
|
||||
{"PETAL"}
|
||||
</Text>
|
||||
<Text
|
||||
x={200}
|
||||
y={184}
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={11}
|
||||
fontWeight={700}
|
||||
letterSpacing={1.8}
|
||||
>
|
||||
{"FINE DINING EXPERIENCE"}
|
||||
</Text>
|
||||
<Text
|
||||
x={200}
|
||||
y={460}
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={22}
|
||||
fontWeight={800}
|
||||
letterSpacing={0.5}
|
||||
>
|
||||
{"SCAN FOR"}
|
||||
</Text>
|
||||
<Text
|
||||
x={200}
|
||||
y={488}
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={22}
|
||||
fontWeight={800}
|
||||
letterSpacing={0.5}
|
||||
>
|
||||
{"OUR MENU"}
|
||||
</Text>
|
||||
<Text
|
||||
x={200}
|
||||
y={526}
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={11.5}
|
||||
fontWeight={600}
|
||||
letterSpacing={0.3}
|
||||
>
|
||||
{"contactless ordering"}
|
||||
</Text>
|
||||
<Text
|
||||
x={200}
|
||||
y={544}
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={11.5}
|
||||
fontWeight={600}
|
||||
letterSpacing={0.3}
|
||||
>
|
||||
{"scan with camera"}
|
||||
</Text>
|
||||
</G>
|
||||
</Svg>
|
||||
)
|
||||
const Memo = memo(SvgComponent)
|
||||
export default Memo
|
||||
|
After Width: | Height: | Size: 278 KiB |
@@ -0,0 +1,156 @@
|
||||
import Svg, { SvgProps, Image, G, Rect, Path, Text } from "react-native-svg"
|
||||
import { memo } from "react"
|
||||
const SvgComponent = (props: SvgProps) => (
|
||||
<Svg width={380} height={500} {...props}>
|
||||
<Image
|
||||
width={380}
|
||||
height={500}
|
||||
href="arka_plan_rengini_deg\u0306is\u0327_202608210045 1.jpg"
|
||||
preserveAspectRatio="none"
|
||||
/>
|
||||
<G transform="translate(108 206)">
|
||||
<Rect
|
||||
width={164}
|
||||
height={164}
|
||||
fill="#fff"
|
||||
stroke="#dce5ea"
|
||||
strokeWidth={1.2}
|
||||
rx={16}
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#003554"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={3}
|
||||
d="M6 30V12a6 6 0 0 1 6-6h18m128 24V12a6 6 0 0 0-6-6h-18M6 134v18a6 6 0 0 0 6 6h18m128-24v18a6 6 0 0 1-6 6h-18"
|
||||
/>
|
||||
<Rect
|
||||
width={140}
|
||||
height={140}
|
||||
x={12}
|
||||
y={12}
|
||||
fill="#fafcfd"
|
||||
stroke="#003554"
|
||||
strokeDasharray="4 3"
|
||||
strokeOpacity={0.2}
|
||||
rx={10}
|
||||
/>
|
||||
<Text
|
||||
x={82}
|
||||
y={87}
|
||||
fill="#003554"
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={11}
|
||||
fontWeight={700}
|
||||
letterSpacing={1}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"QR KOD ALANI"}
|
||||
</Text>
|
||||
</G>
|
||||
<G fill="#003554" textAnchor="middle">
|
||||
<Text
|
||||
x={190}
|
||||
y={50}
|
||||
fontFamily="'Arial Black', 'Montserrat', sans-serif"
|
||||
fontSize={13}
|
||||
fontWeight={900}
|
||||
letterSpacing={2}
|
||||
>
|
||||
{"THE"}
|
||||
</Text>
|
||||
<Text
|
||||
x={190}
|
||||
y={80}
|
||||
fontFamily="'Arial Black', 'Impact', sans-serif"
|
||||
fontSize={27}
|
||||
fontWeight={900}
|
||||
letterSpacing={0.5}
|
||||
>
|
||||
{"URBAN"}
|
||||
</Text>
|
||||
<Text
|
||||
x={190}
|
||||
y={110}
|
||||
fontFamily="'Arial Black', 'Impact', sans-serif"
|
||||
fontSize={27}
|
||||
fontWeight={900}
|
||||
letterSpacing={0.5}
|
||||
>
|
||||
{"FORK"}
|
||||
</Text>
|
||||
<Text
|
||||
x={190}
|
||||
y={148}
|
||||
fontFamily="'Arial Black', 'Montserrat', sans-serif"
|
||||
fontSize={15.5}
|
||||
fontWeight={900}
|
||||
letterSpacing={0.2}
|
||||
>
|
||||
{"SCAN TO VIEW"}
|
||||
</Text>
|
||||
<Text
|
||||
x={190}
|
||||
y={172}
|
||||
fontFamily="'Arial Black', 'Montserrat', sans-serif"
|
||||
fontSize={15.5}
|
||||
fontWeight={900}
|
||||
letterSpacing={0.2}
|
||||
>
|
||||
{"DIGITAL MENU"}
|
||||
</Text>
|
||||
<Text
|
||||
x={190}
|
||||
y={402}
|
||||
fontFamily="'Arial Black', 'Montserrat', sans-serif"
|
||||
fontSize={15.5}
|
||||
fontWeight={900}
|
||||
letterSpacing={0.8}
|
||||
>
|
||||
{"FRESH FLAVORS DAILY"}
|
||||
</Text>
|
||||
<Text
|
||||
x={190}
|
||||
y={420}
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={8.5}
|
||||
fontWeight={600}
|
||||
letterSpacing={0.2}
|
||||
>
|
||||
{"Contactless ordering to scancnal bresers"}
|
||||
</Text>
|
||||
<Text
|
||||
x={190}
|
||||
y={432}
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={8.5}
|
||||
fontWeight={600}
|
||||
letterSpacing={0.2}
|
||||
>
|
||||
{"and enstand mes with your menu."}
|
||||
</Text>
|
||||
<Text
|
||||
x={190}
|
||||
y={454}
|
||||
fontFamily="'Arial Black', Inter, sans-serif"
|
||||
fontSize={10}
|
||||
fontWeight={900}
|
||||
letterSpacing={0.2}
|
||||
>
|
||||
{"contactless ordering"}
|
||||
</Text>
|
||||
<Text
|
||||
x={190}
|
||||
y={468}
|
||||
fontFamily="'Arial Black', Inter, sans-serif"
|
||||
fontSize={10}
|
||||
fontWeight={900}
|
||||
letterSpacing={0.2}
|
||||
>
|
||||
{"scan with camera"}
|
||||
</Text>
|
||||
</G>
|
||||
</Svg>
|
||||
)
|
||||
const Memo = memo(SvgComponent)
|
||||
export default Memo
|
||||
@@ -0,0 +1,327 @@
|
||||
import Svg, {
|
||||
SvgProps,
|
||||
Defs,
|
||||
G,
|
||||
Path,
|
||||
Circle,
|
||||
Rect,
|
||||
Use,
|
||||
Text,
|
||||
} from "react-native-svg"
|
||||
import { memo } from "react"
|
||||
const SvgComponent = (props: SvgProps) => (
|
||||
<Svg width={400} height={580} {...props}>
|
||||
<Defs>
|
||||
<G id="b">
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b58"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={1.2}
|
||||
d="M0 0c10 25 12 55 5 85"
|
||||
/>
|
||||
<Path
|
||||
fill="#f4efe6"
|
||||
stroke="#c59b58"
|
||||
strokeWidth={1.1}
|
||||
d="M3 15c-11-5-15 9-1 9 4 0 4-6 1-9Z"
|
||||
/>
|
||||
<Path
|
||||
fill="#f4efe6"
|
||||
stroke="#9e4d28"
|
||||
strokeWidth={1.1}
|
||||
d="M5 22c11-6 17 6 3 10-4 0-4-6-3-10Zm2 20c-12-6-17 8-3 10 4 0 4-6 3-10Z"
|
||||
/>
|
||||
<Path
|
||||
fill="#f4efe6"
|
||||
stroke="#c59b58"
|
||||
strokeWidth={1.1}
|
||||
d="M8 50c12-6 16 8 2 12-4 0-4-7-2-12ZM6 70c-10-5-14 8-2 10 3 0 4-6 2-10Z"
|
||||
/>
|
||||
<Path
|
||||
fill="#f4efe6"
|
||||
stroke="#9e4d28"
|
||||
strokeWidth={1.1}
|
||||
d="M5 78c11-4 15 8 3 12-4 0-4-6-3-12Z"
|
||||
/>
|
||||
<Circle cx={-10} cy={18} r={1.5} fill="#c59b58" />
|
||||
<Circle cx={18} cy={46} r={1.5} fill="#9e4d28" />
|
||||
</G>
|
||||
<G id="a">
|
||||
<Path
|
||||
fill="#a64423"
|
||||
d="M0-18C4-12 8-6 8 0c6-4 8 6 4 12 4-2 6 4 2 8S2 26 0 26s-10-2-14-6-2-10 2-8c-4-6-2-16 4-12 0-6 4-12 8-18"
|
||||
/>
|
||||
<Path
|
||||
fill="#f4efe6"
|
||||
d="M-4 4v8c0 3 2 4 4 4v8c2-8 4-9 4-12V4H3v7c0 2-1.5 3-3 3s-3-1-3-3V4Z"
|
||||
/>
|
||||
<Path stroke="#f4efe6" d="M0 4v8" />
|
||||
</G>
|
||||
</Defs>
|
||||
<Path fill="#434b54" d="M0 0h400v580H0z" />
|
||||
<Rect
|
||||
width={360}
|
||||
height={548}
|
||||
x={20}
|
||||
y={16}
|
||||
fill="#f6efe3"
|
||||
stroke="#122a42"
|
||||
strokeWidth={1.5}
|
||||
rx={16}
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#122a42"
|
||||
strokeWidth={1.2}
|
||||
d="M46 32h308a12 12 0 0 1 12 12v492a12 12 0 0 1-12 12H46a12 12 0 0 1-12-12V44a12 12 0 0 1 12-12Z"
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#122a42"
|
||||
strokeWidth={1.2}
|
||||
d="M34 46a12 12 0 0 0 12-12m320 12a12 12 0 0 1-12-12M34 534a12 12 0 0 1 12 12m320-12a12 12 0 0 0-12 12"
|
||||
/>
|
||||
<Path stroke="#c59b58" d="M34 135h332M34 500h332" />
|
||||
<Use href="#a" transform="matrix(.85 0 0 .85 200 52)" />
|
||||
<Use href="#b" transform="matrix(.8 0 0 .8 42 42)" />
|
||||
<Use href="#b" transform="matrix(-.8 0 0 .8 358 42)" />
|
||||
<Use href="#b" transform="matrix(.95 0 0 .95 40 185)" />
|
||||
<Use href="#b" transform="matrix(-.95 0 0 .95 360 185)" />
|
||||
<Use href="#a" transform="matrix(.65 0 0 .65 68 320)" />
|
||||
<Use href="#a" transform="matrix(.65 0 0 .65 332 320)" />
|
||||
<Use href="#b" transform="matrix(.95 0 0 .95 40 360)" />
|
||||
<Use href="#b" transform="matrix(-.95 0 0 .95 360 360)" />
|
||||
<G transform="translate(200 465)">
|
||||
<Rect
|
||||
width={18}
|
||||
height={28}
|
||||
x={-9}
|
||||
y={-14}
|
||||
fill="#122a42"
|
||||
stroke="#122a42"
|
||||
rx={3.5}
|
||||
/>
|
||||
<Rect width={14} height={20} x={-7} y={-11} fill="#fff" rx={1.5} />
|
||||
<Path fill="#122a42" d="M-4.5-8.5h9v9h-9z" />
|
||||
<Path fill="#fff" d="M-3-7h6v6h-6z" />
|
||||
<Path fill="#122a42" d="M-1.5-5.5h3v3h-3z" />
|
||||
<Circle cy={11} r={1} fill="#fff" />
|
||||
</G>
|
||||
<Circle cx={258} cy={537} r={4.5} fill="#a64423" />
|
||||
<Path
|
||||
fill="#f6efe3"
|
||||
d="M259 541h-1.5v-4h-1v-1.5h1v-1c0-1 .5-1.5 2-1.5h1v1.5h-1c-.5 0-.5.3-.5.7v.3h1.5l-.3 1.5H259Z"
|
||||
/>
|
||||
<Rect
|
||||
width={9}
|
||||
height={9}
|
||||
x={266}
|
||||
y={532.5}
|
||||
fill="none"
|
||||
stroke="#a64423"
|
||||
strokeWidth={1.2}
|
||||
rx={2.5}
|
||||
/>
|
||||
<Circle cx={270.5} cy={537} r={2} fill="none" stroke="#a64423" />
|
||||
<Circle cx={273} cy={534.5} r={0.5} fill="#a64423" />
|
||||
<G transform="translate(95 205)">
|
||||
<Path
|
||||
fill="#fdfbf7"
|
||||
stroke="#122a42"
|
||||
strokeWidth={3}
|
||||
d="M0 0h210v210H0z"
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#c59b58"
|
||||
strokeWidth={1.5}
|
||||
d="M4 4h202v202H4z"
|
||||
/>
|
||||
<Path
|
||||
fill="#faf8f5"
|
||||
stroke="#122a42"
|
||||
strokeDasharray="4 3"
|
||||
d="M12 12h186v186H12z"
|
||||
/>
|
||||
<Circle
|
||||
cx={105}
|
||||
cy={95}
|
||||
r={16}
|
||||
fill="#f6efe3"
|
||||
stroke="#c59b58"
|
||||
strokeWidth={1.2}
|
||||
/>
|
||||
<Use href="#a" transform="matrix(.45 0 0 .45 105 93)" />
|
||||
<Text
|
||||
x={105}
|
||||
y={128}
|
||||
fill="#122a42"
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={11}
|
||||
fontWeight={800}
|
||||
letterSpacing={1.5}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"QR KOD ALANI"}
|
||||
</Text>
|
||||
</G>
|
||||
<Text
|
||||
x={172}
|
||||
y={55}
|
||||
fill="#122a42"
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={8.5}
|
||||
fontWeight={800}
|
||||
letterSpacing={1}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"EST."}
|
||||
</Text>
|
||||
<Text
|
||||
x={228}
|
||||
y={55}
|
||||
fill="#122a42"
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={8.5}
|
||||
fontWeight={800}
|
||||
letterSpacing={1}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"2018"}
|
||||
</Text>
|
||||
<Text
|
||||
x={200}
|
||||
y={84}
|
||||
fill="#a64423"
|
||||
fontFamily="'Playfair Display', Georgia, 'Times New Roman', serif"
|
||||
fontSize={19}
|
||||
fontWeight={900}
|
||||
letterSpacing={1.5}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"THE STONEHEARTH GRILL"}
|
||||
</Text>
|
||||
<Text
|
||||
x={200}
|
||||
y={102}
|
||||
fill="#122a42"
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={8.5}
|
||||
fontWeight={700}
|
||||
letterSpacing={2}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"EST. 2018"}
|
||||
</Text>
|
||||
<Text
|
||||
x={200}
|
||||
y={118}
|
||||
fill="#122a42"
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={10}
|
||||
fontWeight={800}
|
||||
letterSpacing={2}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"SCAN. ORDER. ENJOY."}
|
||||
</Text>
|
||||
<Text
|
||||
x={200}
|
||||
y={158}
|
||||
fill="#122a42"
|
||||
fontFamily="'Playfair Display', Georgia, 'Times New Roman', serif"
|
||||
fontSize={15.5}
|
||||
fontWeight={900}
|
||||
letterSpacing={0.5}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"VIEW OUR MENU & ORDER ONLINE"}
|
||||
</Text>
|
||||
<Text
|
||||
x={200}
|
||||
y={176}
|
||||
fill="#222"
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={9}
|
||||
fontWeight={500}
|
||||
letterSpacing={0.1}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"Simply scan the QR code with your phone camera to access our"}
|
||||
</Text>
|
||||
<Text
|
||||
x={200}
|
||||
y={189}
|
||||
fill="#222"
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={9}
|
||||
fontWeight={500}
|
||||
letterSpacing={0.1}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"digital menu, daily specials, wine list, and place your order."}
|
||||
</Text>
|
||||
<Text
|
||||
x={162}
|
||||
y={468}
|
||||
fill="#122a42"
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={11}
|
||||
fontWeight={800}
|
||||
letterSpacing={1}
|
||||
textAnchor="end"
|
||||
>
|
||||
{"NO APP"}
|
||||
</Text>
|
||||
<Text
|
||||
x={238}
|
||||
y={468}
|
||||
fill="#122a42"
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={11}
|
||||
fontWeight={800}
|
||||
letterSpacing={1}
|
||||
>
|
||||
{"REQUIRED"}
|
||||
</Text>
|
||||
<Text
|
||||
x={200}
|
||||
y={515}
|
||||
fill="#122a42"
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={10.5}
|
||||
fontWeight={800}
|
||||
letterSpacing={1}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"WWW.THESTONEHEARTHGRILL.COM"}
|
||||
</Text>
|
||||
<Text
|
||||
x={200}
|
||||
y={527}
|
||||
fill="#222"
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={9}
|
||||
fontWeight={600}
|
||||
letterSpacing={0.2}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"FREE WIFI: StonehearthGuest (PW: Grill720)"}
|
||||
</Text>
|
||||
<Text
|
||||
x={195}
|
||||
y={540}
|
||||
fill="#222"
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={9}
|
||||
fontWeight={600}
|
||||
letterSpacing={0.2}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"Follow Us @StonehearthGrill"}
|
||||
</Text>
|
||||
</Svg>
|
||||
)
|
||||
const Memo = memo(SvgComponent)
|
||||
export default Memo
|
||||
|
After Width: | Height: | Size: 666 KiB |
@@ -0,0 +1,158 @@
|
||||
import Svg, {
|
||||
SvgProps,
|
||||
Path,
|
||||
Image,
|
||||
G,
|
||||
Rect,
|
||||
Circle,
|
||||
Text,
|
||||
} from "react-native-svg"
|
||||
import { memo } from "react"
|
||||
const SvgComponent = (props: SvgProps) => (
|
||||
<Svg width={1024} height={1024} {...props}>
|
||||
<Path fill="#0c4443" d="M0 0h1024v1024H0z" />
|
||||
<Image
|
||||
width={1024}
|
||||
height={1024}
|
||||
href="8.jpeg"
|
||||
preserveAspectRatio="none"
|
||||
/>
|
||||
<G transform="translate(348 305)">
|
||||
<Rect
|
||||
width={328}
|
||||
height={336}
|
||||
fill="#f9f5eb"
|
||||
stroke="#d1a752"
|
||||
strokeWidth={1.5}
|
||||
rx={16}
|
||||
/>
|
||||
<Rect
|
||||
width={304}
|
||||
height={312}
|
||||
x={12}
|
||||
y={12}
|
||||
fill="#faf8f2"
|
||||
stroke="#0c4443"
|
||||
strokeDasharray="6 4"
|
||||
strokeOpacity={0.3}
|
||||
strokeWidth={1.5}
|
||||
rx={12}
|
||||
/>
|
||||
<Circle cx={164} cy={148} r={28} fill="#0c4443" />
|
||||
<G fill="#d1a752">
|
||||
<Path d="M164 132.6c4.4 6.6 4.4 15.4 0 24.2-4.4-8.8-4.4-17.6 0-24.2" />
|
||||
<Path d="M161.8 150.2c-8.8-4.4-13.2 4.4-6.6 8.8 6.6 0 8.8-6.6 6.6-8.8m4.4 0c8.8-4.4 13.2 4.4 6.6 8.8-6.6 0-8.8-6.6-6.6-8.8" />
|
||||
</G>
|
||||
<Text
|
||||
x={164}
|
||||
y={218}
|
||||
fill="#0c4443"
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={14}
|
||||
fontWeight={800}
|
||||
letterSpacing={2}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"QR KOD ALANI"}
|
||||
</Text>
|
||||
</G>
|
||||
<Text
|
||||
x={512}
|
||||
y={142}
|
||||
fill="#fff"
|
||||
fontFamily="'Arial Black', 'Montserrat', sans-serif"
|
||||
fontSize={52}
|
||||
fontWeight={900}
|
||||
letterSpacing={2}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"EXPLORE OUR MENU"}
|
||||
</Text>
|
||||
<Path
|
||||
stroke="#d1a752"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={2}
|
||||
d="M210 188h240"
|
||||
/>
|
||||
<Text
|
||||
x={512}
|
||||
y={194}
|
||||
fill="#d1a752"
|
||||
fontFamily="'Arial Black', 'Montserrat', sans-serif"
|
||||
fontSize={24}
|
||||
fontWeight={900}
|
||||
letterSpacing={2}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"OR"}
|
||||
</Text>
|
||||
<Path
|
||||
stroke="#d1a752"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={2}
|
||||
d="M574 188h240"
|
||||
/>
|
||||
<Text
|
||||
x={512}
|
||||
y={242}
|
||||
fill="#d1a752"
|
||||
fontFamily="'Arial Black', 'Montserrat', sans-serif"
|
||||
fontSize={44}
|
||||
fontWeight={900}
|
||||
letterSpacing={2}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"THE DAILY SPECIALS"}
|
||||
</Text>
|
||||
<Text
|
||||
x={512}
|
||||
y={738}
|
||||
fill="#fff"
|
||||
fontFamily="'Arial Black', 'Montserrat', sans-serif"
|
||||
fontSize={28}
|
||||
fontWeight={900}
|
||||
letterSpacing={1}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"SCAN TO VIEW OUR"}
|
||||
</Text>
|
||||
<Text
|
||||
x={512}
|
||||
y={776}
|
||||
fill="#fff"
|
||||
fontFamily="'Arial Black', 'Montserrat', sans-serif"
|
||||
fontSize={28}
|
||||
fontWeight={900}
|
||||
letterSpacing={1}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"FULL MENU & SPECIALS"}
|
||||
</Text>
|
||||
<Text
|
||||
x={512}
|
||||
y={842}
|
||||
fill="#d1a752"
|
||||
fontFamily="'Arial Black', 'Montserrat', sans-serif"
|
||||
fontSize={21}
|
||||
fontWeight={900}
|
||||
letterSpacing={1.5}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"SCAN TO ORDER"}
|
||||
</Text>
|
||||
<Text
|
||||
x={512}
|
||||
y={872}
|
||||
fill="#d1a752"
|
||||
fontFamily="'Arial Black', 'Montserrat', sans-serif"
|
||||
fontSize={21}
|
||||
fontWeight={900}
|
||||
letterSpacing={1.5}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"& BROWSE"}
|
||||
</Text>
|
||||
</Svg>
|
||||
)
|
||||
const Memo = memo(SvgComponent)
|
||||
export default Memo
|
||||
|
After Width: | Height: | Size: 516 KiB |
@@ -0,0 +1,231 @@
|
||||
import Svg, {
|
||||
SvgProps,
|
||||
Defs,
|
||||
Path,
|
||||
Image,
|
||||
G,
|
||||
Rect,
|
||||
Circle,
|
||||
Text,
|
||||
TextPath,
|
||||
} from "react-native-svg"
|
||||
import { memo } from "react"
|
||||
const SvgComponent = (props: SvgProps) => (
|
||||
<Svg width={1024} height={1024} {...props}>
|
||||
<Defs>
|
||||
<Path id="a" fill="none" d="M330 260q182-115 364 0" />
|
||||
</Defs>
|
||||
<Path fill="#f8f6f0" d="M0 0h1024v1024H0z" />
|
||||
<Image
|
||||
width={1024}
|
||||
height={1024}
|
||||
href="xx.png"
|
||||
preserveAspectRatio="none"
|
||||
/>
|
||||
<G transform="translate(362 428)">
|
||||
<Rect
|
||||
width={300}
|
||||
height={300}
|
||||
fill="#fff"
|
||||
stroke="#d5cebe"
|
||||
strokeWidth={1.5}
|
||||
rx={24}
|
||||
/>
|
||||
<Rect
|
||||
width={276}
|
||||
height={276}
|
||||
x={12}
|
||||
y={12}
|
||||
fill="#fafaf7"
|
||||
stroke="#1e3246"
|
||||
strokeDasharray="6 4"
|
||||
strokeOpacity={0.35}
|
||||
strokeWidth={1.5}
|
||||
rx={16}
|
||||
/>
|
||||
<Circle
|
||||
cx={150}
|
||||
cy={132}
|
||||
r={28}
|
||||
fill="#f5efe6"
|
||||
stroke="#1e3246"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
stroke="#1e3246"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={1.9}
|
||||
d="M142.4 116.8v11.4c0 3.8 5.7 3.8 5.7 0v-11.4m-2.85 0v11.4m0 3.8v15.2m9.5-30.4c4.75 3.8 4.75 15.2 0 17.1v13.3"
|
||||
/>
|
||||
<Text
|
||||
x={150}
|
||||
y={192}
|
||||
fill="#1e3246"
|
||||
fontFamily="Inter, Arial, sans-serif"
|
||||
fontSize={14}
|
||||
fontWeight={800}
|
||||
letterSpacing={2}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"QR KOD ALANI"}
|
||||
</Text>
|
||||
</G>
|
||||
<G stroke="#a88b58" transform="translate(290 142)">
|
||||
<Circle cx={-16} cy={-8} r={4.5} fill="none" strokeWidth={1.5} />
|
||||
<Path strokeLinecap="round" strokeWidth={1.5} d="M-16-12v8m-4-4h8" />
|
||||
<Path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={1.5}
|
||||
d="M0-12c4 0 8 6 8 12S4 12 0 12"
|
||||
/>
|
||||
<Path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={1.2}
|
||||
d="M-32 20c12 8 52 8 64 0"
|
||||
/>
|
||||
</G>
|
||||
<G stroke="#1e3246">
|
||||
<Path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={3}
|
||||
d="M390 780c-16-8-16-26-8-36"
|
||||
/>
|
||||
<Path
|
||||
fill="#1e3246"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1.5}
|
||||
d="m374 750 8-6 6 8Z"
|
||||
/>
|
||||
</G>
|
||||
<G stroke="#1e3246">
|
||||
<Path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeWidth={3}
|
||||
d="M634 780c16-8 16-26 8-36"
|
||||
/>
|
||||
<Path
|
||||
fill="#1e3246"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1.5}
|
||||
d="m650 750-8-6-6 8Z"
|
||||
/>
|
||||
</G>
|
||||
<Text
|
||||
x={290}
|
||||
y={164}
|
||||
fill="#1e3246"
|
||||
fontFamily="'Arial Black', 'Impact', sans-serif"
|
||||
fontSize={16}
|
||||
fontWeight={900}
|
||||
letterSpacing={1}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"TABLE MENU"}
|
||||
</Text>
|
||||
<Text
|
||||
x={290}
|
||||
y={180}
|
||||
fill="#8c7348"
|
||||
fontFamily="Georgia, serif"
|
||||
fontSize={9}
|
||||
fontStyle="italic"
|
||||
fontWeight={700}
|
||||
letterSpacing={1}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"EST. 2023"}
|
||||
</Text>
|
||||
<Text
|
||||
x={364}
|
||||
y={248}
|
||||
fill="#1e3246"
|
||||
fontFamily="'Arial Black', 'Impact', sans-serif"
|
||||
fontSize={23}
|
||||
fontWeight={900}
|
||||
letterSpacing={1}
|
||||
textAnchor="middle"
|
||||
transform="rotate(-26 364 248)"
|
||||
>
|
||||
{"SIAT"}
|
||||
</Text>
|
||||
<Text
|
||||
fill="#1e3246"
|
||||
fontFamily="'Arial Black', 'Impact', sans-serif"
|
||||
fontSize={52}
|
||||
fontWeight={900}
|
||||
letterSpacing={2}
|
||||
>
|
||||
<TextPath href="#a" startOffset="50%" textAnchor="middle">
|
||||
{"\n SCAN TO\n "}
|
||||
</TextPath>
|
||||
</Text>
|
||||
<Text
|
||||
x={670}
|
||||
y={244}
|
||||
fill="#1e3246"
|
||||
fontFamily="'Arial Black', 'Impact', sans-serif"
|
||||
fontSize={25}
|
||||
fontWeight={900}
|
||||
letterSpacing={1}
|
||||
textAnchor="middle"
|
||||
transform="rotate(24 670 244)"
|
||||
>
|
||||
{"VIEW"}
|
||||
</Text>
|
||||
<Text
|
||||
x={512}
|
||||
y={320}
|
||||
fill="#1e3246"
|
||||
fontFamily="'Arial Black', 'Impact', sans-serif"
|
||||
fontSize={82}
|
||||
fontWeight={900}
|
||||
letterSpacing={3}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"OUR MENU"}
|
||||
</Text>
|
||||
<Text
|
||||
x={512}
|
||||
y={748}
|
||||
fill="#1e3246"
|
||||
fontFamily="'Arial Black', sans-serif"
|
||||
fontSize={18.5}
|
||||
fontWeight={900}
|
||||
letterSpacing={1}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"VIEW OUR"}
|
||||
</Text>
|
||||
<Text
|
||||
x={512}
|
||||
y={774}
|
||||
fill="#1e3246"
|
||||
fontFamily="'Arial Black', sans-serif"
|
||||
fontSize={18.5}
|
||||
fontWeight={900}
|
||||
letterSpacing={1}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"DELICIOUS MENU"}
|
||||
</Text>
|
||||
<Text
|
||||
x={500}
|
||||
y={894}
|
||||
fill="#1e3246"
|
||||
fontFamily="'Arial Black', 'Montserrat', sans-serif"
|
||||
fontSize={18}
|
||||
fontWeight={900}
|
||||
letterSpacing={1.5}
|
||||
textAnchor="middle"
|
||||
>
|
||||
{"WELCOME TO OUR TABLE"}
|
||||
</Text>
|
||||
</Svg>
|
||||
)
|
||||
const Memo = memo(SvgComponent)
|
||||
export default Memo
|
||||
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 357 KiB After Width: | Height: | Size: 4.5 KiB |
@@ -0,0 +1,76 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 650" width="400" height="650">
|
||||
<defs>
|
||||
<!-- Warm cream gradient for the outer canvas -->
|
||||
<linearGradient id="cream-bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#faf4e6" />
|
||||
<stop offset="100%" stop-color="#f7eed8" />
|
||||
</linearGradient>
|
||||
|
||||
<!-- Rosso accent gradient -->
|
||||
<linearGradient id="rosso-accent" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#d1453f" />
|
||||
<stop offset="100%" stop-color="#b92b27" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- 1. Backgrounds & Contrast Panels -->
|
||||
<g id="layer-background">
|
||||
<rect id="bg-canvas" width="400" height="650" fill="url(#cream-bg)" />
|
||||
<rect id="main-card" x="20" y="20" width="360" height="610" rx="24" fill="#fdfbf5" stroke="url(#rosso-accent)" stroke-width="1.5" />
|
||||
<rect id="inner-border" x="30" y="30" width="340" height="590" rx="18" fill="none" stroke="#e8ddc7" stroke-width="1" stroke-dasharray="5 3" />
|
||||
</g>
|
||||
|
||||
<!-- 2. Decorative Icons & Accents -->
|
||||
<g id="layer-icons">
|
||||
<!-- Pizza slice crest -->
|
||||
<g transform="translate(170, 50)" stroke="url(#rosso-accent)" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M30 6 L58 46 A32 32 0 0 1 2 46 Z" />
|
||||
<circle cx="26" cy="24" r="2.2" fill="#b92b27" stroke="none" />
|
||||
<circle cx="38" cy="30" r="2.2" fill="#5d4037" stroke="none" />
|
||||
<circle cx="30" cy="38" r="2.2" fill="#b92b27" stroke="none" />
|
||||
<path d="M2 46 h56" stroke-width="1" />
|
||||
</g>
|
||||
|
||||
<!-- Center wheat-sprig divider -->
|
||||
<g id="divider-accent">
|
||||
<path d="M130 162 h55 M215 162 h55" stroke="url(#rosso-accent)" stroke-width="1" opacity="0.6" />
|
||||
<polygon points="200,157 202.5,161.5 207,162 203.5,165 204.5,169.5 200,167 195.5,169.5 196.5,165 193,162 197.5,161.5" fill="url(#rosso-accent)" />
|
||||
</g>
|
||||
|
||||
<!-- WiFi Icon -->
|
||||
<g transform="translate(68, 592)" stroke="#8c6b52" stroke-width="1.5" fill="none" stroke-linecap="round">
|
||||
<path d="M0 0 a14 14 0 0 1 18 0" />
|
||||
<path d="M3 4 a9 9 0 0 1 12 0" />
|
||||
<circle cx="9" cy="8" r="1" fill="#8c6b52" />
|
||||
</g>
|
||||
|
||||
<!-- Instagram / Social Icon -->
|
||||
<g transform="translate(225, 592)" stroke="#8c6b52" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="0" y="0" width="13" height="13" rx="3.5" />
|
||||
<circle cx="6.5" cy="6.5" r="3" />
|
||||
<circle cx="10" cy="3" r="0.5" fill="#8c6b52" />
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- 3. Clean Placeholder Box for QR Code (aligned to the app's fixed 33.08% / 55% overlay) -->
|
||||
<g id="qr-placeholder" transform="translate(90, 215)">
|
||||
<rect width="220" height="220" rx="20" fill="#FFFFFF" stroke="#f0d9d8" stroke-width="2" />
|
||||
<rect x="12" y="12" width="196" height="196" rx="14" fill="#FCFAF6" stroke="#f0d9d8" stroke-dasharray="4 3" />
|
||||
<text x="110" y="115" font-family="Inter, sans-serif" font-size="12" font-weight="600" fill="#b92b27" text-anchor="middle">QR KOD ALANI</text>
|
||||
</g>
|
||||
|
||||
<!-- 4. Native Editable Texts -->
|
||||
<g id="layer-texts" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" text-anchor="middle">
|
||||
<text x="200" y="130" font-size="22" font-weight="800" fill="#18181b" letter-spacing="2">PIZZERIA BELLA NAPOLI</text>
|
||||
<text x="200" y="150" font-size="11" font-weight="500" fill="#b92b27" letter-spacing="2.5">TEMASSIZ DİJİTAL MENÜ</text>
|
||||
|
||||
<text x="200" y="475" font-size="13" font-weight="700" fill="#18181b" letter-spacing="1.2">MENÜYÜ GÖRMEK İÇİN OKUTUN</text>
|
||||
<text x="200" y="495" font-size="11" font-weight="400" fill="#5d4037">Kameranız ile QR kodu taratın</text>
|
||||
|
||||
<rect x="140" y="520" width="120" height="28" rx="14" fill="#fdece9" stroke="#f0d9d8" stroke-width="1" />
|
||||
<text x="200" y="539" font-size="12" font-weight="600" fill="#b92b27" letter-spacing="0.5">Masa No: 01</text>
|
||||
|
||||
<text x="128" y="602" font-size="10" font-weight="400" fill="#8c6b52" text-anchor="start">Bella_Guest</text>
|
||||
<text x="246" y="602" font-size="10" font-weight="400" fill="#8c6b52" text-anchor="start">@bellanapoli</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
@@ -0,0 +1,71 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 650" width="400" height="650">
|
||||
<defs>
|
||||
<!-- Define a linear gradient for gold effect -->
|
||||
<linearGradient id="goldGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#D4AF37"/>
|
||||
<stop offset="100%" stop-color="#FFD700"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Background Canvas -->
|
||||
<rect id="bg-canvas" fill="#451A03" width="400" height="650"/>
|
||||
<!-- Main Menu Card Panel -->
|
||||
<rect id="main-card" x="20" y="20" width="360" height="610" rx="24" fill="#000000" stroke="url(#goldGradient)" stroke-width="2"/>
|
||||
<!-- Layer for Icons/Graphics -->
|
||||
<g id="layer-icons">
|
||||
<!-- Top decorative line -->
|
||||
<line x1="80" y1="165" x2="320" y2="165" stroke="url(#goldGradient)" stroke-width="1" fill="#D97706"/>
|
||||
<!-- Bottom decorative line -->
|
||||
<line x1="80" y1="535" x2="320" y2="535" stroke="url(#goldGradient)" stroke-width="1" fill="#B45309"/>
|
||||
<!-- Subtle corner accents (L-shapes) -->
|
||||
<path d="M40 40 L40 70 M40 40 L70 40" stroke="url(#goldGradient)" stroke-width="1.5" stroke-linecap="round" fill="#D97706"/>
|
||||
<path d="M360 40 L360 70 M360 40 L330 40" stroke="url(#goldGradient)" stroke-width="1.5" stroke-linecap="round" fill="#B45309"/>
|
||||
<path d="M40 610 L40 580 M40 610 L70 610" stroke="url(#goldGradient)" stroke-width="1.5" stroke-linecap="round" fill="#D97706"/>
|
||||
<path d="M360 610 L360 580 M360 610 L330 610" stroke="url(#goldGradient)" stroke-width="1.5" stroke-linecap="round" fill="#B45309"/>
|
||||
<!-- Minimalist Fork and Knife Line Art -->
|
||||
<g transform="translate(0, 15)">
|
||||
<!-- Fork -->
|
||||
<path d="M185 550 L185 560 M188 550 L188 560 M191 550 L191 560 M188 560 L188 565" stroke="url(#goldGradient)" stroke-width="1.5" stroke-linecap="round" fill="#D97706"/>
|
||||
<!-- Knife -->
|
||||
<path d="M205 550 L205 560 L208 560 L208 552 C208 550 206.5 549 205 549 Z M206.5 560 L206.5 565" stroke="url(#goldGradient)" stroke-width="1.5" stroke-linecap="round" fill="#B45309"/>
|
||||
</g>
|
||||
</g>
|
||||
<!-- QR Placeholder -->
|
||||
<g id="qr-placeholder" transform="translate(90, 215)">
|
||||
<rect width="220" height="220" rx="20" fill="#FFFFFF" stroke="#E4E4E7" stroke-width="2"/>
|
||||
<rect x="12" y="12" width="196" height="196" rx="14" fill="#FAFAFA" stroke="#E4E4E7" stroke-dasharray="4 3"/>
|
||||
<text x="110" y="115" font-family="Inter, sans-serif" font-size="12" font-weight="600" fill="#71717A" text-anchor="middle">
|
||||
QR KOD ALANI
|
||||
</text>
|
||||
</g>
|
||||
<!-- Layer for Texts -->
|
||||
<g id="layer-texts" fill="#c10101">
|
||||
<!-- Table Info badge -->
|
||||
<rect x="140" y="40" width="120" height="30" rx="15" fill="url(#goldGradient)"/>
|
||||
<text x="200" y="60" font-family="Inter, sans-serif" font-size="14" font-weight="600" fill="#1A1A1D" text-anchor="middle">
|
||||
MASA NO: 01
|
||||
</text>
|
||||
<!-- Restaurant Name -->
|
||||
<text x="200" y="120" font-family="Inter, sans-serif" font-size="26" font-weight="700" fill="url(#goldGradient)" text-anchor="middle">
|
||||
Gourmet Bistro
|
||||
</text>
|
||||
<!-- Tagline / Subtitle -->
|
||||
<text x="200" y="145" font-family="Inter, sans-serif" font-size="12" font-weight="400" fill="#E4E4E7" text-anchor="middle">
|
||||
Temassız Dijital Menü
|
||||
</text>
|
||||
<!-- "MENÜYÜ GÖRMEK İÇİN OKUTUN" -->
|
||||
<text x="200" y="480" font-family="Inter, sans-serif" font-size="16" font-weight="700" fill="url(#goldGradient)" text-anchor="middle">
|
||||
MENÜYÜ GÖRMEK İÇİN OKUTUN
|
||||
</text>
|
||||
<!-- Instruction note -->
|
||||
<text x="200" y="505" font-family="Inter, sans-serif" font-size="11" font-weight="400" fill="#A1A1AA" text-anchor="middle">
|
||||
Kameranızı QR Koda Odaklayın
|
||||
</text>
|
||||
<!-- WiFi & Social Media footer info -->
|
||||
<text x="200" y="595" font-family="Inter, sans-serif" font-size="11" font-weight="400" fill="#A1A1AA" text-anchor="middle">
|
||||
Ücretsiz Wi-Fi: GourmetBistro_Guest
|
||||
</text>
|
||||
<text x="200" y="610" font-family="Inter, sans-serif" font-size="11" font-weight="400" fill="#A1A1AA" text-anchor="middle">
|
||||
Bizi Takip Edin: @GourmetBistro
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1,285 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 400 650"
|
||||
width="400"
|
||||
height="650"
|
||||
>
|
||||
<g id="layer-background">
|
||||
<rect
|
||||
id="bg-canvas"
|
||||
x="0"
|
||||
y="0"
|
||||
width="400"
|
||||
height="650"
|
||||
fill="#0B0E14"
|
||||
/>
|
||||
</g>
|
||||
|
||||
<g id="layer-card">
|
||||
<!-- Ana Lüks Kart Arka Planı (Gece Yarısı Laciverti & Kömür) -->
|
||||
<rect
|
||||
id="main-card"
|
||||
x="20"
|
||||
y="20"
|
||||
width="360"
|
||||
height="610"
|
||||
rx="24"
|
||||
fill="#121721"
|
||||
stroke="#D4AF37"
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
|
||||
<!-- İç Çift Altın Bordür Hattı -->
|
||||
<rect
|
||||
id="inner-border"
|
||||
x="28"
|
||||
y="28"
|
||||
width="344"
|
||||
height="594"
|
||||
rx="18"
|
||||
fill="none"
|
||||
stroke="#C5A059"
|
||||
stroke-width="0.8"
|
||||
stroke-opacity="0.4"
|
||||
/>
|
||||
<rect
|
||||
x="32"
|
||||
y="32"
|
||||
width="336"
|
||||
height="586"
|
||||
rx="14"
|
||||
fill="none"
|
||||
stroke="#C5A059"
|
||||
stroke-width="0.5"
|
||||
stroke-opacity="0.2"
|
||||
/>
|
||||
</g>
|
||||
|
||||
<g id="layer-icons">
|
||||
<!-- Üst Köşe Geometrik Art-Deco Motifleri -->
|
||||
<path
|
||||
d="M 38 48 L 48 38 M 38 38 L 52 38 M 38 38 L 38 52"
|
||||
stroke="#D4AF37"
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
stroke-opacity="0.6"
|
||||
fill="none"
|
||||
/>
|
||||
<path
|
||||
d="M 362 48 L 352 38 M 362 38 L 348 38 M 362 38 L 362 52"
|
||||
stroke="#D4AF37"
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
stroke-opacity="0.6"
|
||||
fill="none"
|
||||
/>
|
||||
|
||||
<!-- Üst Masa Numarası Kapsülü -->
|
||||
<rect
|
||||
x="148"
|
||||
y="44"
|
||||
width="104"
|
||||
height="22"
|
||||
rx="11"
|
||||
fill="#1A2230"
|
||||
stroke="#D4AF37"
|
||||
stroke-width="0.8"
|
||||
/>
|
||||
<circle cx="158" cy="55" r="1.5" fill="#D4AF37" />
|
||||
<circle cx="242" cy="55" r="1.5" fill="#D4AF37" />
|
||||
|
||||
<!-- Merkezi Gastronomi & Yıldız Amblemi -->
|
||||
<g transform="translate(200, 84)">
|
||||
<circle cx="0" cy="0" r="11" fill="none" stroke="#D4AF37" stroke-width="0.8" stroke-dasharray="2 2" />
|
||||
<polygon points="0,-7 2,-2 7,0 2,2 0,7 -2,2 -7,0 -2,-2" fill="#D4AF37" />
|
||||
</g>
|
||||
|
||||
<!-- Zarif Tipografik Ayırıcı -->
|
||||
<line x1="75" y1="172" x2="175" y2="172" stroke="#D4AF37" stroke-width="0.75" stroke-opacity="0.5" />
|
||||
<polygon points="200,169.5 202.5,172 200,174.5 197.5,172" fill="#D4AF37" />
|
||||
<line x1="225" y1="172" x2="325" y2="172" stroke="#D4AF37" stroke-width="0.75" stroke-opacity="0.5" />
|
||||
|
||||
<!-- QR Alanı Hedef Odak Çerçeveleri -->
|
||||
<path d="M 80 205 L 80 195 L 90 195" fill="none" stroke="#D4AF37" stroke-width="2" stroke-linecap="round" />
|
||||
<path d="M 320 205 L 320 195 L 310 195" fill="none" stroke="#D4AF37" stroke-width="2" stroke-linecap="round" />
|
||||
<path d="M 80 445 L 80 455 L 90 455" fill="none" stroke="#D4AF37" stroke-width="2" stroke-linecap="round" />
|
||||
<path d="M 320 445 L 320 455 L 310 455" fill="none" stroke="#D4AF37" stroke-width="2" stroke-linecap="round" />
|
||||
|
||||
<!-- Alt Bilgi Kartı Paneli -->
|
||||
<rect
|
||||
x="36"
|
||||
y="520"
|
||||
width="328"
|
||||
height="84"
|
||||
rx="14"
|
||||
fill="#171F2C"
|
||||
stroke="#D4AF37"
|
||||
stroke-width="0.8"
|
||||
stroke-opacity="0.4"
|
||||
/>
|
||||
<line x1="200" y1="530" x2="200" y2="594" stroke="#D4AF37" stroke-width="0.5" stroke-opacity="0.25" />
|
||||
|
||||
<!-- Minimal WiFi Vektörü -->
|
||||
<g transform="translate(62, 545)">
|
||||
<path d="M 0 4 A 12 12 0 0 1 18 4" fill="none" stroke="#D4AF37" stroke-width="1.4" stroke-linecap="round" />
|
||||
<path d="M 3.5 8 A 7 7 0 0 1 14.5 8" fill="none" stroke="#D4AF37" stroke-width="1.4" stroke-linecap="round" />
|
||||
<circle cx="9" cy="12" r="1.2" fill="#D4AF37" />
|
||||
</g>
|
||||
|
||||
<!-- Minimal Instagram / Web Vektörü -->
|
||||
<g transform="translate(224, 545)">
|
||||
<rect x="0" y="0" width="15" height="15" rx="4" fill="none" stroke="#D4AF37" stroke-width="1.4" />
|
||||
<circle cx="7.5" cy="7.5" r="3.2" fill="none" stroke="#D4AF37" stroke-width="1.1" />
|
||||
<circle cx="11.5" cy="3.5" r="0.75" fill="#D4AF37" />
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<g id="qr-placeholder" transform="translate(90, 215)">
|
||||
<rect
|
||||
width="220"
|
||||
height="220"
|
||||
rx="20"
|
||||
fill="#FFFFFF"
|
||||
stroke="#E4E4E7"
|
||||
stroke-width="2"
|
||||
/>
|
||||
|
||||
<rect
|
||||
x="12"
|
||||
y="12"
|
||||
width="196"
|
||||
height="196"
|
||||
rx="14"
|
||||
fill="#FAFAFA"
|
||||
stroke="#E4E4E7"
|
||||
stroke-dasharray="4 3"
|
||||
/>
|
||||
|
||||
<text
|
||||
x="110"
|
||||
y="115"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="12"
|
||||
font-weight="600"
|
||||
fill="#71717A"
|
||||
text-anchor="middle"
|
||||
>QR KOD ALANI</text>
|
||||
</g>
|
||||
|
||||
<g id="layer-texts">
|
||||
<!-- Masa Numarası -->
|
||||
<text
|
||||
x="200"
|
||||
y="59"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="9"
|
||||
font-weight="700"
|
||||
letter-spacing="2.5"
|
||||
fill="#E5C378"
|
||||
text-anchor="middle"
|
||||
>TABLE • 01</text>
|
||||
|
||||
<!-- Restoran Logosu & İsmi -->
|
||||
<text
|
||||
x="200"
|
||||
y="126"
|
||||
font-family="'Playfair Display', 'Cinzel', Georgia, serif"
|
||||
font-size="28"
|
||||
font-weight="700"
|
||||
letter-spacing="1.5"
|
||||
fill="#F9F6F0"
|
||||
text-anchor="middle"
|
||||
>L’ATELIER BISTRO</text>
|
||||
|
||||
<!-- Fransızca / Uluslararası Alt Başlık -->
|
||||
<text
|
||||
x="200"
|
||||
y="152"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="8.5"
|
||||
font-weight="600"
|
||||
letter-spacing="4"
|
||||
fill="#D4AF37"
|
||||
text-anchor="middle"
|
||||
>FINE DINING & COCKTAILS</text>
|
||||
|
||||
<!-- Çağrı Metni / Talimat -->
|
||||
<text
|
||||
x="200"
|
||||
y="480"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="11.5"
|
||||
font-weight="700"
|
||||
letter-spacing="2"
|
||||
fill="#F9F6F0"
|
||||
text-anchor="middle"
|
||||
>DİJİTAL MENÜ İÇİN OKUTUN</text>
|
||||
|
||||
<text
|
||||
x="200"
|
||||
y="498"
|
||||
font-family="'Playfair Display', Georgia, serif"
|
||||
font-size="10.5"
|
||||
font-style="italic"
|
||||
font-weight="400"
|
||||
letter-spacing="0.5"
|
||||
fill="#94A3B8"
|
||||
text-anchor="middle"
|
||||
>Kameranızı QR koda yaklaştırınız</text>
|
||||
|
||||
<!-- Sol Sütun: Yüksek Hızlı WiFi Bilgileri -->
|
||||
<text
|
||||
x="126"
|
||||
y="550"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="9"
|
||||
font-weight="700"
|
||||
letter-spacing="1.2"
|
||||
fill="#E5C378"
|
||||
text-anchor="middle"
|
||||
>GUEST WIFI</text>
|
||||
|
||||
<text
|
||||
x="126"
|
||||
y="567"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="8.5"
|
||||
font-weight="500"
|
||||
fill="#CBD5E1"
|
||||
text-anchor="middle"
|
||||
>Ağ: Atelier-Guest</text>
|
||||
|
||||
<text
|
||||
x="126"
|
||||
y="582"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="8.5"
|
||||
font-weight="500"
|
||||
fill="#CBD5E1"
|
||||
text-anchor="middle"
|
||||
>Şifre: bistro2026</text>
|
||||
|
||||
<!-- Sağ Sütun: Sosyal Medya & Rezervasyon -->
|
||||
<text
|
||||
x="286"
|
||||
y="550"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="8.5"
|
||||
font-weight="700"
|
||||
letter-spacing="1.2"
|
||||
fill="#E5C378"
|
||||
text-anchor="middle"
|
||||
>SOSYAL MEDYA</text>
|
||||
|
||||
<text
|
||||
x="286"
|
||||
y="575"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="10.5"
|
||||
font-weight="600"
|
||||
letter-spacing="0.5"
|
||||
fill="#F9F6F0"
|
||||
text-anchor="middle"
|
||||
>@latelier.bistro</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.1 KiB |
@@ -0,0 +1,275 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 400 650"
|
||||
width="400"
|
||||
height="650"
|
||||
>
|
||||
<g id="layer-background">
|
||||
<!-- Koyu Doku / Kontrast Arka Plan -->
|
||||
<rect
|
||||
id="bg-canvas"
|
||||
x="0"
|
||||
y="0"
|
||||
width="400"
|
||||
height="650"
|
||||
fill="#252C36"
|
||||
/>
|
||||
</g>
|
||||
|
||||
<g id="layer-card">
|
||||
<!-- Sıcak Parşömen / Vintage Krem Ana Kart -->
|
||||
<rect
|
||||
id="main-card"
|
||||
x="20"
|
||||
y="20"
|
||||
width="360"
|
||||
height="610"
|
||||
rx="20"
|
||||
fill="#F6EFE2"
|
||||
stroke="#3A2312"
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
<!-- Çift İnce Vintage Çerçeve -->
|
||||
<rect
|
||||
x="28"
|
||||
y="28"
|
||||
width="344"
|
||||
height="594"
|
||||
rx="14"
|
||||
fill="none"
|
||||
stroke="#8C6239"
|
||||
stroke-width="1"
|
||||
stroke-opacity="0.4"
|
||||
/>
|
||||
<rect
|
||||
x="32"
|
||||
y="32"
|
||||
width="336"
|
||||
height="586"
|
||||
rx="10"
|
||||
fill="none"
|
||||
stroke="#8C6239"
|
||||
stroke-width="0.5"
|
||||
stroke-opacity="0.25"
|
||||
/>
|
||||
</g>
|
||||
|
||||
<g id="layer-icons">
|
||||
<!-- Sol Üst: Botanik Kahve / Çay Dalı -->
|
||||
<g transform="translate(36, 36) scale(0.75)" stroke="#3A2312" stroke-width="1.4" fill="none" stroke-linecap="round">
|
||||
<path d="M 0 0 C 15 15 25 35 30 65" />
|
||||
<path d="M 10 12 C 5 2 20 -2 22 8 C 22 18 10 12 10 12 Z" fill="#EADCC7" />
|
||||
<path d="M 18 27 C 12 20 28 14 32 23 C 32 32 18 27 18 27 Z" fill="#EADCC7" />
|
||||
<path d="M 24 45 C 32 38 45 48 38 56 C 30 62 24 45 24 45 Z" fill="#EADCC7" />
|
||||
</g>
|
||||
|
||||
<!-- Sağ Üst: Kuşbakışı Latte Art Fincanı -->
|
||||
<g transform="translate(340, 52)">
|
||||
<!-- Tabak -->
|
||||
<circle cx="0" cy="0" r="32" fill="#EADCC7" stroke="#3A2312" stroke-width="1.2" />
|
||||
<circle cx="0" cy="0" r="26" fill="#F6EFE2" stroke="#8C6239" stroke-width="0.8" stroke-dasharray="2 2" />
|
||||
<!-- Fincan Kenarı & Kahve -->
|
||||
<circle cx="0" cy="0" r="21" fill="#7A4B26" stroke="#3A2312" stroke-width="1.4" />
|
||||
<!-- Latte Art / Rosetta Yaprakları -->
|
||||
<path d="M 0 10 C -8 4 -4 -6 0 -12 C 4 -6 8 4 0 10 Z" fill="#F6EFE2" />
|
||||
<path d="M 0 4 C -5 0 -2 -5 0 -8 C 2 -5 5 0 0 4 Z" fill="#7A4B26" />
|
||||
<circle cx="0" cy="-14" r="1.8" fill="#F6EFE2" />
|
||||
</g>
|
||||
|
||||
<!-- Sol Orta: Küçük Kahve Fincanı & Tabağı -->
|
||||
<g transform="translate(48, 250)">
|
||||
<ellipse cx="0" cy="0" rx="26" ry="26" fill="#EADCC7" stroke="#3A2312" stroke-width="1" />
|
||||
<circle cx="0" cy="0" r="17" fill="#6A3B18" stroke="#3A2312" stroke-width="1.2" />
|
||||
<!-- Basit Kalp Latte Deseni -->
|
||||
<path d="M -5 -3 C -8 -7 0 -10 0 -4 C 0 -10 8 -7 5 -3 C 2 3 0 7 0 7 C 0 7 -2 3 -5 -3 Z" fill="#F6EFE2" />
|
||||
<!-- Fincan Kulpu -->
|
||||
<path d="M 17 -4 C 23 -4 23 4 17 4" fill="none" stroke="#3A2312" stroke-width="1.5" />
|
||||
</g>
|
||||
|
||||
<!-- Sağ Orta: Dikey Yaprak İllüstrasyonu -->
|
||||
<g transform="translate(345, 230) scale(0.7)" stroke="#3A2312" stroke-width="1.3" fill="none">
|
||||
<path d="M 10 0 C 0 30 -5 70 5 110" />
|
||||
<path d="M 6 25 C 18 15 28 28 14 36 C 6 36 6 25 6 25 Z" fill="#EADCC7" />
|
||||
<path d="M 2 60 C -12 48 -20 62 -8 70 C 2 70 2 60 2 60 Z" fill="#EADCC7" />
|
||||
<path d="M 5 88 C 18 78 24 92 12 100 C 5 100 5 88 5 88 Z" fill="#EADCC7" />
|
||||
</g>
|
||||
|
||||
<!-- Üst Başlık Yan Süsleme Çizgileri -->
|
||||
<path d="M 142 66 C 148 64 154 68 160 66" fill="none" stroke="#8C6239" stroke-width="1.2" stroke-linecap="round" />
|
||||
<path d="M 240 66 C 246 68 252 64 258 66" fill="none" stroke="#8C6239" stroke-width="1.2" stroke-linecap="round" />
|
||||
|
||||
<!-- Sol Alt: Çekirdek ve Yaprak Grubu -->
|
||||
<g transform="translate(42, 530) scale(0.65)" stroke="#3A2312" stroke-width="1.4" fill="none">
|
||||
<path d="M 0 110 C 20 70 15 30 0 0" />
|
||||
<path d="M 8 30 C 24 15 35 30 18 42 Z" fill="#EADCC7" />
|
||||
<path d="M 12 70 C 30 55 40 72 20 84 Z" fill="#EADCC7" />
|
||||
</g>
|
||||
|
||||
<!-- Kahve Çekirdekleri Motifleri -->
|
||||
<!-- Sol Alt Çekirdek 1 -->
|
||||
<g transform="translate(108, 595) rotate(-20) scale(0.8)">
|
||||
<ellipse cx="0" cy="0" rx="9" ry="6.5" fill="#CBB296" stroke="#3A2312" stroke-width="1.2" />
|
||||
<path d="M -7 0 Q 0 3 0 0 Q 0 -3 7 0" fill="none" stroke="#3A2312" stroke-width="1.2" />
|
||||
</g>
|
||||
<!-- Sol Alt Çekirdek 2 -->
|
||||
<g transform="translate(128, 584) rotate(35) scale(0.8)">
|
||||
<ellipse cx="0" cy="0" rx="9" ry="6.5" fill="#CBB296" stroke="#3A2312" stroke-width="1.2" />
|
||||
<path d="M -7 0 Q 0 3 0 0 Q 0 -3 7 0" fill="none" stroke="#3A2312" stroke-width="1.2" />
|
||||
</g>
|
||||
<!-- Sağ Orta Çekirdek -->
|
||||
<g transform="translate(336, 360) rotate(-45) scale(0.7)">
|
||||
<ellipse cx="0" cy="0" rx="9" ry="6.5" fill="#CBB296" stroke="#3A2312" stroke-width="1.2" />
|
||||
<path d="M -7 0 Q 0 3 0 0 Q 0 -3 7 0" fill="none" stroke="#3A2312" stroke-width="1.2" />
|
||||
</g>
|
||||
|
||||
<!-- Sağ Alt: Sıcak Kahve Fincanı İllüstrasyonu -->
|
||||
<g transform="translate(325, 590)">
|
||||
<!-- Tabak -->
|
||||
<ellipse cx="0" cy="18" rx="28" ry="7" fill="#EADCC7" stroke="#3A2312" stroke-width="1.2" />
|
||||
<!-- Fincan Gövdesi -->
|
||||
<path d="M -18 2 C -18 16 18 16 18 2 Z" fill="#F6EFE2" stroke="#3A2312" stroke-width="1.4" />
|
||||
<ellipse cx="0" cy="2" rx="18" ry="4" fill="#6A3B18" stroke="#3A2312" stroke-width="1.2" />
|
||||
<!-- Kulp -->
|
||||
<path d="M 16 4 C 24 4 24 12 16 14" fill="none" stroke="#3A2312" stroke-width="1.3" />
|
||||
<!-- Buhar Çizgileri -->
|
||||
<path d="M -6 -4 C -8 -9 -4 -12 -6 -17" fill="none" stroke="#8C6239" stroke-width="1" stroke-linecap="round" />
|
||||
<path d="M 2 -5 C 0 -10 4 -13 2 -19" fill="none" stroke="#8C6239" stroke-width="1" stroke-linecap="round" />
|
||||
</g>
|
||||
|
||||
<!-- QR Alanı İnce Köşe Süsleri -->
|
||||
<path d="M 80 205 C 80 195 80 195 90 195" fill="none" stroke="#3A2312" stroke-width="2" stroke-linecap="round" />
|
||||
<path d="M 320 205 C 320 195 320 195 310 195" fill="none" stroke="#3A2312" stroke-width="2" stroke-linecap="round" />
|
||||
<path d="M 80 445 C 80 455 80 455 90 455" fill="none" stroke="#3A2312" stroke-width="2" stroke-linecap="round" />
|
||||
<path d="M 320 445 C 320 455 320 455 310 455" fill="none" stroke="#3A2312" stroke-width="2" stroke-linecap="round" />
|
||||
|
||||
<!-- Alt Est. Bölümü Çizgileri -->
|
||||
<line x1="145" y1="608" x2="175" y2="608" stroke="#8C6239" stroke-width="0.8" />
|
||||
<line x1="225" y1="608" x2="255" y2="608" stroke="#8C6239" stroke-width="0.8" />
|
||||
</g>
|
||||
|
||||
<g id="qr-placeholder" transform="translate(90, 215)">
|
||||
<!-- QR Kod Arka Planı (Hafif Sıcak Ton) -->
|
||||
<rect
|
||||
width="220"
|
||||
height="220"
|
||||
rx="20"
|
||||
fill="#FBF8F2"
|
||||
stroke="#D8C7B0"
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
<rect
|
||||
x="10"
|
||||
y="10"
|
||||
width="200"
|
||||
height="200"
|
||||
rx="14"
|
||||
fill="#F5EFE6"
|
||||
stroke="#D8C7B0"
|
||||
stroke-dasharray="4 3"
|
||||
/>
|
||||
<!-- QR Merkezine Kahve Çekirdeği İkonu Rozeti -->
|
||||
<circle cx="110" cy="98" r="18" fill="#FBF8F2" stroke="#8C6239" stroke-width="1" />
|
||||
<g transform="translate(110, 98) scale(0.65)">
|
||||
<ellipse cx="-5" cy="2" rx="7" ry="5" fill="#3A2312" transform="rotate(-30)" />
|
||||
<ellipse cx="5" cy="0" rx="7" ry="5" fill="#3A2312" transform="rotate(40)" />
|
||||
</g>
|
||||
<text
|
||||
x="110"
|
||||
y="132"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="11"
|
||||
font-weight="700"
|
||||
letter-spacing="1"
|
||||
fill="#8C6239"
|
||||
text-anchor="middle"
|
||||
>QR KOD ALANI</text>
|
||||
</g>
|
||||
|
||||
<g id="layer-texts">
|
||||
<!-- Üst Başlık Takısı -->
|
||||
<text
|
||||
x="200"
|
||||
y="70"
|
||||
font-family="'Playfair Display', Georgia, serif"
|
||||
font-size="14"
|
||||
font-style="italic"
|
||||
font-weight="700"
|
||||
letter-spacing="4"
|
||||
fill="#3A2312"
|
||||
text-anchor="middle"
|
||||
>THE</text>
|
||||
|
||||
<!-- Ana Tipografi: El Yazısı / Retro Tabela Havası -->
|
||||
<text
|
||||
x="200"
|
||||
y="114"
|
||||
font-family="'Playfair Display', 'Brush Script MT', 'Great Vibes', Georgia, cursive, serif"
|
||||
font-size="34"
|
||||
font-weight="800"
|
||||
letter-spacing="0.5"
|
||||
fill="#2C1A0E"
|
||||
text-anchor="middle"
|
||||
>The Daily Grind</text>
|
||||
|
||||
<!-- Alt Kategori Başlığı -->
|
||||
<text
|
||||
x="200"
|
||||
y="146"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="13"
|
||||
font-weight="800"
|
||||
letter-spacing="3"
|
||||
fill="#3A2312"
|
||||
text-anchor="middle"
|
||||
>COFFEE & KITCHEN</text>
|
||||
|
||||
<!-- Büyük ve Belirgin Çağrı Metni -->
|
||||
<text
|
||||
x="200"
|
||||
y="488"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="19"
|
||||
font-weight="900"
|
||||
letter-spacing="1.5"
|
||||
fill="#2C1A0E"
|
||||
text-anchor="middle"
|
||||
>SCAN TO VIEW MENU</text>
|
||||
|
||||
<!-- Restoran & WiFi Bilgi Bandı -->
|
||||
<text
|
||||
x="200"
|
||||
y="520"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="10.5"
|
||||
font-weight="700"
|
||||
letter-spacing="0.5"
|
||||
fill="#3A2312"
|
||||
text-anchor="middle"
|
||||
>DAILY GRIND CAFE | WIFI: DAILY_GRIND_GUEST</text>
|
||||
|
||||
<!-- Şifre ve Masa Detayı -->
|
||||
<text
|
||||
x="200"
|
||||
y="542"
|
||||
font-family="Inter, Arial, sans-serif"
|
||||
font-size="9"
|
||||
font-weight="600"
|
||||
letter-spacing="1"
|
||||
fill="#8C6239"
|
||||
text-anchor="middle"
|
||||
>ŞİFRE: coffee2026 • MASA NO: 04</text>
|
||||
|
||||
<!-- Kuruluş / Est. Yılı -->
|
||||
<text
|
||||
x="200"
|
||||
y="611"
|
||||
font-family="'Playfair Display', Georgia, serif"
|
||||
font-size="11"
|
||||
font-style="italic"
|
||||
font-weight="700"
|
||||
letter-spacing="2"
|
||||
fill="#3A2312"
|
||||
text-anchor="middle"
|
||||
>EST. 2018</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.3 KiB |
@@ -0,0 +1,72 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 650" width="400" height="650">
|
||||
<defs>
|
||||
<linearGradient id="gold-accent-8" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#e9c877" />
|
||||
<stop offset="50%" stop-color="#d1a752" />
|
||||
<stop offset="100%" stop-color="#a3803a" />
|
||||
</linearGradient>
|
||||
|
||||
<linearGradient id="emerald-surface" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#0c4443" />
|
||||
<stop offset="100%" stop-color="#062423" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- 1. Backgrounds & Contrast Panels -->
|
||||
<g id="layer-background">
|
||||
<rect id="bg-canvas" width="400" height="650" fill="#052120" />
|
||||
<rect id="main-card" x="20" y="20" width="360" height="610" rx="24" fill="url(#emerald-surface)" stroke="url(#gold-accent-8)" stroke-width="1.5" />
|
||||
<rect id="inner-border" x="30" y="30" width="340" height="590" rx="18" fill="none" stroke="#134a48" stroke-width="1" stroke-dasharray="5 3" />
|
||||
</g>
|
||||
|
||||
<!-- 2. Decorative Icons & Accents -->
|
||||
<g id="layer-icons">
|
||||
<!-- Laurel crest -->
|
||||
<g transform="translate(170, 50)" stroke="url(#gold-accent-8)" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M30 4 v42" />
|
||||
<path d="M30 12 c-10 0 -16 6 -16 14 M30 22 c-10 0 -15 5 -15 12 M30 32 c-10 0 -13 4 -13 10" />
|
||||
<path d="M30 12 c10 0 16 6 16 14 M30 22 c10 0 15 5 15 12 M30 32 c10 0 13 4 13 10" />
|
||||
</g>
|
||||
|
||||
<g id="divider-accent">
|
||||
<path d="M130 162 h55 M215 162 h55" stroke="url(#gold-accent-8)" stroke-width="1" opacity="0.6" />
|
||||
<polygon points="200,157 202.5,161.5 207,162 203.5,165 204.5,169.5 200,167 195.5,169.5 196.5,165 193,162 197.5,161.5" fill="url(#gold-accent-8)" />
|
||||
</g>
|
||||
|
||||
<!-- WiFi Icon -->
|
||||
<g transform="translate(68, 592)" stroke="#7fb9b3" stroke-width="1.5" fill="none" stroke-linecap="round">
|
||||
<path d="M0 0 a14 14 0 0 1 18 0" />
|
||||
<path d="M3 4 a9 9 0 0 1 12 0" />
|
||||
<circle cx="9" cy="8" r="1" fill="#7fb9b3" />
|
||||
</g>
|
||||
|
||||
<!-- Instagram / Social Icon -->
|
||||
<g transform="translate(225, 592)" stroke="#7fb9b3" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="0" y="0" width="13" height="13" rx="3.5" />
|
||||
<circle cx="6.5" cy="6.5" r="3" />
|
||||
<circle cx="10" cy="3" r="0.5" fill="#7fb9b3" />
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- 3. Clean Placeholder Box for QR Code (aligned to the app's fixed 33.08% / 55% overlay) -->
|
||||
<g id="qr-placeholder" transform="translate(90, 215)">
|
||||
<rect width="220" height="220" rx="20" fill="#f9f5eb" stroke="url(#gold-accent-8)" stroke-width="2" />
|
||||
<rect x="12" y="12" width="196" height="196" rx="14" fill="#FFFFFF" stroke="#e6dcc0" stroke-dasharray="4 3" />
|
||||
<text x="110" y="115" font-family="Inter, sans-serif" font-size="12" font-weight="600" fill="#a3803a" text-anchor="middle">QR KOD ALANI</text>
|
||||
</g>
|
||||
|
||||
<!-- 4. Native Editable Texts -->
|
||||
<g id="layer-texts" font-family="Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" text-anchor="middle">
|
||||
<text x="200" y="130" font-size="22" font-weight="800" fill="#ffffff" letter-spacing="2">EMERALD & GOLD</text>
|
||||
<text x="200" y="150" font-size="11" font-weight="500" fill="#d1a752" letter-spacing="2.5">TEMASSIZ DİJİTAL MENÜ</text>
|
||||
|
||||
<text x="200" y="475" font-size="13" font-weight="700" fill="#ffffff" letter-spacing="1.2">MENÜYÜ GÖRMEK İÇİN OKUTUN</text>
|
||||
<text x="200" y="495" font-size="11" font-weight="400" fill="#8fbdb8">Kameranız ile QR kodu taratın</text>
|
||||
|
||||
<rect x="140" y="520" width="120" height="28" rx="14" fill="#0f3c3a" stroke="#1a5350" stroke-width="1" />
|
||||
<text x="200" y="539" font-size="12" font-weight="600" fill="#d1a752" letter-spacing="0.5">Masa No: 01</text>
|
||||
|
||||
<text x="128" y="602" font-size="10" font-weight="400" fill="#7fb9b3" text-anchor="start">Emerald_Guest</text>
|
||||
<text x="246" y="602" font-size="10" font-weight="400" fill="#7fb9b3" text-anchor="start">@emeraldgold</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,74 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 650" width="400" height="650">
|
||||
<defs>
|
||||
<linearGradient id="vintage-bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#f8f6f0" />
|
||||
<stop offset="100%" stop-color="#e2ded5" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- 1. Backgrounds & Contrast Panels -->
|
||||
<g id="layer-background">
|
||||
<rect id="bg-canvas" width="400" height="650" fill="url(#vintage-bg)" />
|
||||
<rect id="main-card" x="20" y="20" width="360" height="610" rx="10" fill="#ffffff" stroke="#d5cebe" stroke-width="1.5" />
|
||||
<rect id="inner-border" x="30" y="30" width="340" height="590" fill="none" stroke="#1e3246" stroke-width="1" />
|
||||
<rect id="inner-border-2" x="34" y="34" width="332" height="582" fill="none" stroke="#d5cebe" stroke-width="1" />
|
||||
|
||||
<!-- Vintage corner flourishes -->
|
||||
<g stroke="#a88b58" stroke-width="1" fill="none">
|
||||
<path d="M30 60 v-30 h30" />
|
||||
<path d="M370 60 v-30 h-30" />
|
||||
<path d="M30 590 v30 h30" />
|
||||
<path d="M370 590 v30 h-30" />
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- 2. Decorative Icons & Accents -->
|
||||
<g id="layer-icons">
|
||||
<!-- Ornamental crest -->
|
||||
<g transform="translate(170, 52)" stroke="#1e3246" stroke-width="1.3" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="30" cy="22" r="16" />
|
||||
<path d="M14 22 h32 M30 6 v32" stroke-width="0.75" opacity="0.6" />
|
||||
</g>
|
||||
|
||||
<g id="divider-accent">
|
||||
<path d="M120 162 h60 M220 162 h60" stroke="#a88b58" stroke-width="1" opacity="0.7" />
|
||||
<circle cx="200" cy="162" r="3" fill="none" stroke="#a88b58" stroke-width="1" />
|
||||
</g>
|
||||
|
||||
<!-- WiFi Icon -->
|
||||
<g transform="translate(68, 592)" stroke="#7a7267" stroke-width="1.5" fill="none" stroke-linecap="round">
|
||||
<path d="M0 0 a14 14 0 0 1 18 0" />
|
||||
<path d="M3 4 a9 9 0 0 1 12 0" />
|
||||
<circle cx="9" cy="8" r="1" fill="#7a7267" />
|
||||
</g>
|
||||
|
||||
<!-- Instagram / Social Icon -->
|
||||
<g transform="translate(225, 592)" stroke="#7a7267" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="0" y="0" width="13" height="13" rx="3.5" />
|
||||
<circle cx="6.5" cy="6.5" r="3" />
|
||||
<circle cx="10" cy="3" r="0.5" fill="#7a7267" />
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- 3. Clean Placeholder Box for QR Code (aligned to the app's fixed 33.08% / 55% overlay) -->
|
||||
<g id="qr-placeholder" transform="translate(90, 215)">
|
||||
<rect width="220" height="220" fill="#FFFFFF" stroke="#1e3246" stroke-width="1.5" />
|
||||
<rect x="10" y="10" width="200" height="200" fill="none" stroke="#d5cebe" stroke-dasharray="4 3" />
|
||||
<text x="110" y="115" font-family="Georgia, 'Times New Roman', serif" font-size="12" font-weight="600" fill="#a88b58" text-anchor="middle">QR KOD ALANI</text>
|
||||
</g>
|
||||
|
||||
<!-- 4. Native Editable Texts -->
|
||||
<g id="layer-texts" font-family="Georgia, 'Times New Roman', serif" text-anchor="middle">
|
||||
<text x="200" y="130" font-size="22" font-weight="700" fill="#1e3246" letter-spacing="1.5">VINTAGE TABLE MENU</text>
|
||||
<text x="200" y="150" font-size="11" font-weight="500" fill="#a88b58" letter-spacing="2.5" font-family="Inter, sans-serif">TEMASSIZ DİJİTAL MENÜ</text>
|
||||
|
||||
<text x="200" y="475" font-size="13" font-weight="700" fill="#1e3246" letter-spacing="1">MENÜYÜ GÖRMEK İÇİN OKUTUN</text>
|
||||
<text x="200" y="495" font-size="11" font-weight="400" fill="#7a7267" font-family="Inter, sans-serif">Kameranız ile QR kodu taratın</text>
|
||||
|
||||
<rect x="140" y="520" width="120" height="28" fill="#ffffff" stroke="#1e3246" stroke-width="1" />
|
||||
<text x="200" y="539" font-size="12" font-weight="600" fill="#1e3246" letter-spacing="0.5" font-family="Inter, sans-serif">Masa No: 01</text>
|
||||
|
||||
<text x="128" y="602" font-size="10" font-weight="400" fill="#a88b58" text-anchor="start" font-family="Inter, sans-serif">Vintage_Guest</text>
|
||||
<text x="246" y="602" font-size="10" font-weight="400" fill="#a88b58" text-anchor="start" font-family="Inter, sans-serif">@vintagetable</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,213 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import AdminAuthGuard from "@/components/AdminAuthGuard";
|
||||
import { getAdminSession, logoutAdmin, AdminUser } from "@/lib/admin-auth";
|
||||
import { AyrisFooterBadge } from "@/components/AyrisFooterBadge";
|
||||
|
||||
export default function AdminLayoutClient({ children }: { children: React.ReactNode }) {
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const [adminUser, setAdminUser] = useState<AdminUser | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setAdminUser(getAdminSession());
|
||||
}, [pathname]);
|
||||
|
||||
const handleLogout = async () => {
|
||||
if (confirm("Menulio Admin panelinden çıkış yapmak istediğinize emin misiniz?")) {
|
||||
await logoutAdmin();
|
||||
router.replace("/admin/login");
|
||||
}
|
||||
};
|
||||
|
||||
// Login page doesn't use the Admin Dashboard Shell (sidebar, header, etc.)
|
||||
if (pathname === "/admin/login") {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
return (
|
||||
<AdminAuthGuard>
|
||||
<div className="min-h-screen bg-[#09090b] text-[#fafafa] flex flex-col md:flex-row antialiased selection:bg-[#D4AF37]/30 selection:text-[#D4AF37]">
|
||||
{/* Sol Sidebar */}
|
||||
<aside className="w-full md:w-72 bg-[#121215] border-b md:border-b-0 md:border-r border-white/10 flex flex-col justify-between shrink-0">
|
||||
<div>
|
||||
{/* Logo & Başlık */}
|
||||
<div className="p-6 border-b border-white/10 flex items-center justify-between">
|
||||
<Link href="/admin" className="flex items-center gap-3 group">
|
||||
<div className="w-9 h-9 rounded-xl bg-gradient-to-tr from-[#D4AF37] to-[#F59E0B] flex items-center justify-center shadow-lg shadow-[#D4AF37]/20">
|
||||
<span className="font-extrabold text-[#0C0C0E] text-lg tracking-wider">M</span>
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-bold text-white tracking-wide text-base group-hover:text-[#D4AF37] transition-colors">
|
||||
MENULIO
|
||||
</div>
|
||||
<div className="text-[10px] font-semibold text-[#D4AF37] tracking-widest uppercase">
|
||||
Admin Stüdyosu
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Navigasyon Linkleri */}
|
||||
<nav className="p-4 space-y-1.5">
|
||||
<div className="text-[10px] font-bold text-zinc-500 uppercase tracking-wider px-3 mb-2">
|
||||
Tasarım & Şablonlar
|
||||
</div>
|
||||
|
||||
<Link
|
||||
href="/admin/templates"
|
||||
className={`flex items-center gap-3 px-3 py-2.5 rounded-xl font-semibold text-sm transition-all shadow-sm ${
|
||||
pathname === "/admin/templates"
|
||||
? "bg-[#D4AF37]/10 text-[#D4AF37] border border-[#D4AF37]/25"
|
||||
: "text-zinc-400 hover:text-white hover:bg-white/5"
|
||||
}`}
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
QR Şablon Stüdyosu
|
||||
<span className="ml-auto text-[10px] bg-[#D4AF37] text-black font-extrabold px-1.5 py-0.5 rounded-full">
|
||||
Masa Kartı
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/admin/menu-templates"
|
||||
className={`flex items-center gap-3 px-3 py-2.5 rounded-xl font-semibold text-sm transition-all shadow-sm ${
|
||||
pathname === "/admin/menu-templates"
|
||||
? "bg-[#D4AF37]/10 text-[#D4AF37] border border-[#D4AF37]/25"
|
||||
: "text-zinc-400 hover:text-white hover:bg-white/5"
|
||||
}`}
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
||||
</svg>
|
||||
Web Menü Şablonları
|
||||
<span className="ml-auto text-[10px] bg-[#D4AF37] text-black font-extrabold px-1.5 py-0.5 rounded-full">
|
||||
10 Tema
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/admin"
|
||||
className={`flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium transition-all ${
|
||||
pathname === "/admin"
|
||||
? "bg-[#D4AF37]/10 text-[#D4AF37] border border-[#D4AF37]/25"
|
||||
: "text-zinc-400 hover:text-white hover:bg-white/5"
|
||||
}`}
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||
</svg>
|
||||
Genel Bakış & İstatistikler
|
||||
</Link>
|
||||
|
||||
<div className="text-[10px] font-bold text-zinc-500 uppercase tracking-wider px-3 pt-4 mb-2">
|
||||
Önizleme & Test
|
||||
</div>
|
||||
|
||||
<Link
|
||||
href="/templates"
|
||||
target="_blank"
|
||||
className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-zinc-400 hover:text-white hover:bg-white/5 text-sm font-medium transition-all"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
||||
</svg>
|
||||
Web Menü Simülatörü
|
||||
<svg className="w-3.5 h-3.5 ml-auto text-zinc-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
||||
</svg>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/qr"
|
||||
target="_blank"
|
||||
className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-zinc-400 hover:text-white hover:bg-white/5 text-sm font-medium transition-all"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 4v1m6 11h2m-6 0h-2v4m0-11v3m0 0h.01M12 12h4.01M16 20h4M4 12h4m12 0h.01M5 8h2a1 1 0 001-1V5a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1zm12 0h2a1 1 0 001-1V5a1 1 0 00-1-1h-2a1 1 0 00-1 1v2a1 1 0 001 1zM5 20h2a1 1 0 001-1v-2a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1z" />
|
||||
</svg>
|
||||
Web Masa Standı Üretici
|
||||
<svg className="w-3.5 h-3.5 ml-auto text-zinc-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
||||
</svg>
|
||||
</Link>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* Alt Yönetici Profil & Çıkış Kartı */}
|
||||
<div className="p-4 border-t border-white/10 space-y-3">
|
||||
<div className="p-3 rounded-xl bg-white/[0.04] border border-white/5 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2.5 min-w-0">
|
||||
<div className="w-8 h-8 rounded-lg bg-[#D4AF37]/20 border border-[#D4AF37]/40 flex items-center justify-center shrink-0">
|
||||
<span className="font-extrabold text-[#D4AF37] text-xs">
|
||||
{adminUser?.email?.[0]?.toUpperCase() || "A"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="text-xs font-bold text-white truncate">
|
||||
{adminUser?.name || "Yönetici"}
|
||||
</div>
|
||||
<div className="text-[10px] text-zinc-400 truncate">
|
||||
{adminUser?.email || "admin@menul.io"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleLogout}
|
||||
className="p-1.5 rounded-lg text-zinc-400 hover:text-red-400 hover:bg-red-500/10 transition-colors"
|
||||
title="Çıkış Yap"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{/* Sağ Ana İçerik */}
|
||||
<main className="flex-1 flex flex-col min-w-0 bg-[#0C0C0E]">
|
||||
{/* Üst Bar */}
|
||||
<header className="h-16 border-b border-white/10 px-6 flex items-center justify-between bg-[#121215]/50 backdrop-blur shrink-0">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="text-sm font-semibold text-zinc-300">
|
||||
Dinamik Şablon & İçerik Yönetimi
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="hidden sm:flex items-center gap-2 px-3 py-1.5 rounded-full bg-white/5 border border-white/10 text-xs text-zinc-300">
|
||||
<span className="w-2 h-2 rounded-full bg-[#D4AF37]" />
|
||||
Oturum: <span className="font-semibold text-[#D4AF37]">{adminUser?.role === "super_admin" ? "Süper Admin" : "Yönetici"}</span>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleLogout}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-red-500/10 border border-red-500/20 text-xs font-semibold text-red-400 hover:bg-red-500/20 transition-all cursor-pointer"
|
||||
>
|
||||
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
|
||||
</svg>
|
||||
Çıkış Yap
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Sayfa İçeriği */}
|
||||
<div className="flex-1 p-6 md:p-8 overflow-y-auto">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
<AyrisFooterBadge className="mt-auto border-t border-white/10 bg-[#09090b]" />
|
||||
</main>
|
||||
</div>
|
||||
</AdminAuthGuard>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { Metadata } from "next";
|
||||
import React from "react";
|
||||
import AdminLayoutClient from "./AdminLayoutClient";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Menulio Admin Panel | Şablon & Sistem Yönetimi",
|
||||
description: "Menulio QR Şablon Stüdyosu, Masa Standı Tasarım Motoru ve Sistem Yönetimi",
|
||||
};
|
||||
|
||||
export default function AdminLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return <AdminLayoutClient>{children}</AdminLayoutClient>;
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect, Suspense } from "react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { loginAdmin, getAdminSession } from "@/lib/admin-auth";
|
||||
|
||||
function AdminLoginForm() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const callbackUrl = searchParams.get("callbackUrl") || "/admin";
|
||||
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [errorMsg, setErrorMsg] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// If already logged in, redirect straight to admin dashboard
|
||||
const session = getAdminSession();
|
||||
if (session) {
|
||||
router.replace(callbackUrl);
|
||||
}
|
||||
}, [router, callbackUrl]);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!email.trim() || !password.trim()) {
|
||||
setErrorMsg("Lütfen e-posta ve şifrenizi girin.");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
setErrorMsg("");
|
||||
|
||||
try {
|
||||
const res = await loginAdmin(email, password);
|
||||
if (res.success) {
|
||||
router.replace(callbackUrl);
|
||||
} else {
|
||||
setErrorMsg(res.error || "Giriş başarısız.");
|
||||
}
|
||||
} catch {
|
||||
setErrorMsg("Bir hata oluştu. Lütfen tekrar deneyin.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleQuickDemoLogin = () => {
|
||||
setEmail("admin@menul.io");
|
||||
setPassword("menulio2026");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#09090b] text-white flex flex-col justify-center items-center p-4 relative overflow-hidden selection:bg-[#D4AF37]/30 selection:text-[#D4AF37]">
|
||||
{/* Arka Plan Işık Efektleri */}
|
||||
<div className="absolute top-1/4 left-1/2 -translate-x-1/2 -translate-y-1/2 w-96 h-96 bg-[#D4AF37]/10 rounded-full blur-3xl pointer-events-none" />
|
||||
<div className="absolute bottom-10 right-10 w-72 h-72 bg-amber-500/5 rounded-full blur-3xl pointer-events-none" />
|
||||
|
||||
{/* Kart Konteynırı */}
|
||||
<div className="w-full max-w-md bg-[#121215]/80 backdrop-blur-xl border border-white/10 rounded-3xl p-8 shadow-2xl shadow-black/80 relative z-10 space-y-6">
|
||||
{/* Logo & Başlık */}
|
||||
<div className="text-center space-y-2">
|
||||
<Link href="/" className="inline-flex items-center gap-3 group mb-2">
|
||||
<div className="w-12 h-12 rounded-2xl bg-gradient-to-tr from-[#D4AF37] to-[#F59E0B] flex items-center justify-center shadow-lg shadow-[#D4AF37]/30 group-hover:scale-105 transition-transform">
|
||||
<span className="font-black text-[#0C0C0E] text-2xl tracking-wider">M</span>
|
||||
</div>
|
||||
</Link>
|
||||
<h1 className="text-2xl font-black text-white tracking-tight">
|
||||
Menulio Admin Girişi
|
||||
</h1>
|
||||
<p className="text-xs text-zinc-400">
|
||||
QR Şablon Stüdyosu ve Sistem Yönetim Paneli
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Hata Bildirimi */}
|
||||
{errorMsg && (
|
||||
<div className="p-3.5 rounded-xl bg-red-500/10 border border-red-500/30 text-red-400 text-xs font-semibold flex items-center gap-2 animate-shake">
|
||||
<svg className="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
{errorMsg}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Giriş Formu */}
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{/* E-posta */}
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-xs font-bold text-zinc-300 uppercase tracking-wider block">
|
||||
Yönetici E-Postası
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="admin@menul.io"
|
||||
className="w-full bg-white/[0.04] border border-white/10 rounded-xl px-4 py-3 text-sm text-white placeholder-zinc-500 focus:outline-none focus:border-[#D4AF37] focus:ring-1 focus:ring-[#D4AF37] transition-all"
|
||||
required
|
||||
/>
|
||||
<div className="absolute right-3.5 top-1/2 -translate-y-1/2 text-zinc-500 pointer-events-none">
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Şifre */}
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-xs font-bold text-zinc-300 uppercase tracking-wider block">
|
||||
Şifre
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type={showPassword ? "text" : "password"}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="••••••••"
|
||||
className="w-full bg-white/[0.04] border border-white/10 rounded-xl px-4 py-3 text-sm text-white placeholder-zinc-500 focus:outline-none focus:border-[#D4AF37] focus:ring-1 focus:ring-[#D4AF37] transition-all pr-10"
|
||||
required
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute right-3.5 top-1/2 -translate-y-1/2 text-zinc-400 hover:text-white transition-colors"
|
||||
title={showPassword ? "Şifreyi gizle" : "Şifreyi göster"}
|
||||
>
|
||||
{showPassword ? (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858-5.908a10.05 10.05 0 012.122-.388c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21M3 3l18 18" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Giriş Yap Butonu */}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className="w-full mt-2 bg-gradient-to-r from-[#D4AF37] to-[#F59E0B] text-[#0C0C0E] font-extrabold text-sm py-3.5 px-4 rounded-xl shadow-lg shadow-[#D4AF37]/20 hover:opacity-95 active:scale-[0.99] transition-all disabled:opacity-50 flex items-center justify-center gap-2 cursor-pointer"
|
||||
>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<svg className="w-4 h-4 animate-spin text-[#0C0C0E]" fill="none" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
|
||||
</svg>
|
||||
Giriş Yapılıyor...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Giriş Yap
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
|
||||
</svg>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{/* Hızlı Demo Girişi */}
|
||||
<div className="pt-4 border-t border-white/10 space-y-2 text-center">
|
||||
<div className="text-[11px] text-zinc-500">
|
||||
Varsayılan Admin Girişi: <span className="font-mono text-zinc-300">admin@menul.io / menulio2026</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleQuickDemoLogin}
|
||||
className="text-xs text-[#D4AF37] hover:underline font-medium transition-colors"
|
||||
>
|
||||
Formu Varsayılan Admin Bilgileri ile Doldur
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Alt Footer */}
|
||||
<div className="mt-8 text-center text-xs text-zinc-600 relative z-10">
|
||||
Menulio QR & Masa Kartı Yönetim Ekosistemi © {new Date().getFullYear()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function AdminLoginPage() {
|
||||
return (
|
||||
<Suspense fallback={
|
||||
<div className="min-h-screen bg-[#09090b] flex items-center justify-center">
|
||||
<div className="w-8 h-8 rounded-full border-2 border-[#D4AF37] border-t-transparent animate-spin" />
|
||||
</div>
|
||||
}>
|
||||
<AdminLoginForm />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { Metadata } from "next";
|
||||
import { MenuTemplatesAdminClient } from "@/components/MenuTemplatesAdminClient";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Web Menü Şablon Stüdyosu | Menulio Admin",
|
||||
description: "Web restoran menü temalarını, renklerini ve düzen ayarlarını canlı önizleme ile yönetin.",
|
||||
};
|
||||
|
||||
export default function MenuTemplatesAdminPage() {
|
||||
return <MenuTemplatesAdminClient />;
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
import Link from "next/link";
|
||||
import { QR_STAND_PRESETS } from "@menulio/shared";
|
||||
|
||||
export default function AdminDashboardPage() {
|
||||
const presetCount = Object.keys(QR_STAND_PRESETS).length;
|
||||
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto space-y-8">
|
||||
{/* Karşılama Başlığı */}
|
||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||
<div>
|
||||
<h1 className="text-2xl md:text-3xl font-extrabold text-white tracking-tight">
|
||||
Menulio Sistem & Şablon Yönetimi
|
||||
</h1>
|
||||
<p className="text-zinc-400 text-sm mt-1">
|
||||
Masa standı şablonlarını, JSON şablon motorunu ve restoran görünüm ayarlarını yönetin.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 flex-wrap self-start">
|
||||
<Link
|
||||
href="/admin/menu-templates"
|
||||
className="inline-flex items-center gap-2 bg-white/5 border border-white/10 hover:bg-white/10 text-white font-bold px-4 py-2.5 rounded-xl transition-all text-sm"
|
||||
>
|
||||
<svg className="w-4 h-4 text-[#D4AF37]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
||||
</svg>
|
||||
Web Menü Şablon Stüdyosu
|
||||
</Link>
|
||||
<Link
|
||||
href="/admin/templates"
|
||||
className="inline-flex items-center gap-2 bg-gradient-to-r from-[#D4AF37] to-[#F59E0B] text-[#0C0C0E] font-bold px-5 py-2.5 rounded-xl shadow-lg shadow-[#D4AF37]/20 hover:opacity-95 transition-all text-sm"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
QR Şablon Stüdyosu
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* İstatistik Kartları */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div className="p-5 rounded-2xl bg-[#121215] border border-white/10 space-y-2">
|
||||
<div className="text-xs font-semibold text-zinc-400 uppercase tracking-wider">
|
||||
Aktif Şablonlar
|
||||
</div>
|
||||
<div className="text-3xl font-black text-white">{presetCount} Adet</div>
|
||||
<div className="text-xs text-emerald-400 font-medium flex items-center gap-1">
|
||||
<span>●</span> Mobil & Web Canlıda
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-5 rounded-2xl bg-[#121215] border border-white/10 space-y-2">
|
||||
<div className="text-xs font-semibold text-zinc-400 uppercase tracking-wider">
|
||||
Şablon Motoru Tipi
|
||||
</div>
|
||||
<div className="text-2xl font-black text-[#D4AF37]">JSON Driven</div>
|
||||
<div className="text-xs text-zinc-400 font-medium">
|
||||
0 App Store güncellemesi
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-5 rounded-2xl bg-[#121215] border border-white/10 space-y-2">
|
||||
<div className="text-xs font-semibold text-zinc-400 uppercase tracking-wider">
|
||||
Baskı Çıktı Formatı
|
||||
</div>
|
||||
<div className="text-3xl font-black text-white">A5 & 300DPI</div>
|
||||
<div className="text-xs text-zinc-400 font-medium">
|
||||
PDF + Vektörel PNG
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-5 rounded-2xl bg-[#121215] border border-white/10 space-y-2">
|
||||
<div className="text-xs font-semibold text-zinc-400 uppercase tracking-wider">
|
||||
API Durumu
|
||||
</div>
|
||||
<div className="text-2xl font-black text-emerald-400">Aktif & Senkron</div>
|
||||
<div className="text-xs text-zinc-400 font-medium">
|
||||
REST API v1
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Nasıl Çalışır / Mimari Rehber Kutusu */}
|
||||
<div className="p-6 rounded-2xl bg-gradient-to-br from-[#18181B] to-[#121215] border border-[#D4AF37]/20 relative overflow-hidden">
|
||||
<div className="relative z-10 space-y-4">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-[#D4AF37]/15 border border-[#D4AF37]/30 text-xs font-bold text-[#D4AF37]">
|
||||
💡 DİNAMİK ŞABLON MİMARİSİ
|
||||
</div>
|
||||
|
||||
<h2 className="text-xl font-bold text-white">
|
||||
Mobil Uygulamayı Güncellemeden Yeni Şablon Yayınlama
|
||||
</h2>
|
||||
|
||||
<p className="text-sm text-zinc-300 leading-relaxed max-w-3xl">
|
||||
Menulio, QR masa kartı şablonlarını doğrudan kod içine gömmek yerine <strong>JSON şablon spesifikasyonu</strong> ile çalışır.
|
||||
Admin panelinden yeni bir SVG/Görsel arka plan ve varsayılan QR/metin koordinatları tanımladığınızda,
|
||||
tüm mobil kullanıcılar uygulamayı güncellemek zorunda kalmadan yeni şablonu anında görürler.
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 pt-2">
|
||||
<div className="p-4 rounded-xl bg-black/40 border border-white/5 space-y-1.5">
|
||||
<div className="text-xs font-bold text-[#D4AF37]">1. Arka Planı Belirle</div>
|
||||
<div className="text-xs text-zinc-400">
|
||||
Figma veya Illustrator'dan SVG/PNG arka planı oluşturup yükleyin veya renk seçin.
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4 rounded-xl bg-black/40 border border-white/5 space-y-1.5">
|
||||
<div className="text-xs font-bold text-[#D4AF37]">2. QR & Katmanları Ayarla</div>
|
||||
<div className="text-xs text-zinc-400">
|
||||
QR kodun yerini, boyutunu, restoran ismi ve slogan metinlerinin varsayılan yerlerini belirleyin.
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4 rounded-xl bg-black/40 border border-white/5 space-y-1.5">
|
||||
<div className="text-xs font-bold text-[#D4AF37]">3. Tek Tıkla Yayınla</div>
|
||||
<div className="text-xs text-zinc-400">
|
||||
Kaydet butonuna bastığınız anda API üzerinden tüm restoranların mobil uygulamasına dağıtılır.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Hızlı Şablonlar Listesi */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-bold text-white">Mevcut Hazır Şablonlar</h3>
|
||||
<Link
|
||||
href="/admin/templates"
|
||||
className="text-xs font-semibold text-[#D4AF37] hover:underline"
|
||||
>
|
||||
Tümünü Yönet & Düzenle →
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{Object.values(QR_STAND_PRESETS).slice(0, 4).map((preset) => (
|
||||
<div
|
||||
key={preset.key}
|
||||
className="p-4 rounded-xl bg-[#121215] border border-white/10 space-y-3 flex flex-col justify-between"
|
||||
>
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-[11px] font-mono text-zinc-500">{preset.key}</span>
|
||||
<span className="text-[10px] bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 px-2 py-0.5 rounded-full font-bold">
|
||||
Aktif
|
||||
</span>
|
||||
</div>
|
||||
<div className="font-bold text-white text-sm line-clamp-1">{preset.name}</div>
|
||||
<div className="text-xs text-zinc-400 mt-0.5">{preset.categoryName || "Masa Kartı"}</div>
|
||||
</div>
|
||||
|
||||
<div className="pt-3 border-t border-white/5 flex items-center justify-between text-xs text-zinc-400">
|
||||
<span>Oran: {preset.aspectRatio || "1/1"}</span>
|
||||
<Link
|
||||
href={`/admin/templates?key=${preset.key}`}
|
||||
className="font-bold text-[#D4AF37] hover:underline"
|
||||
>
|
||||
Düzenle
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { Metadata } from "next";
|
||||
import { TemplateStudioAdminClient } from "@/components/TemplateStudioAdminClient";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "QR Şablon Stüdyosu | Menulio Admin",
|
||||
description: "Masa standı şablonlarını görsel editör veya JSON ile tasarlayın, canlı önizleyin ve anında yayınlayın.",
|
||||
};
|
||||
|
||||
export default function AdminTemplatesPage() {
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto space-y-6">
|
||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-2 border-b border-white/10 pb-5">
|
||||
<div>
|
||||
<h1 className="text-2xl font-extrabold text-white tracking-tight">
|
||||
QR Masa Standı Şablon Stüdyosu
|
||||
</h1>
|
||||
<p className="text-zinc-400 text-xs mt-1">
|
||||
Uygulama güncellemesi gerektirmeden yeni şablonlar ekleyin, renkleri ve QR koordinatlarını düzenleyin.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TemplateStudioAdminClient />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import { supabase } from "@/lib/supabase";
|
||||
|
||||
const DATA_FILE = path.join(process.cwd(), "src/data/qr-templates.json");
|
||||
|
||||
function mapFromDb(row: any) {
|
||||
return {
|
||||
key: row.key,
|
||||
name: row.name,
|
||||
categoryName: row.category_name,
|
||||
aspectRatio: row.aspect_ratio || "1/1",
|
||||
bgType: row.bg_type || "gradient",
|
||||
bgImageUrl: row.bg_image_url || undefined,
|
||||
bgGradient: row.bg_gradient || undefined,
|
||||
plaqueColor: row.plaque_color || undefined,
|
||||
borderAccent: row.border_accent || undefined,
|
||||
accentText: row.accent_text || undefined,
|
||||
headerText: row.header_text || undefined,
|
||||
subText: row.sub_text || undefined,
|
||||
dividerColor: row.divider_color || undefined,
|
||||
qrConfig: row.qr_config,
|
||||
layers: row.layers,
|
||||
isActive: row.is_active ?? true,
|
||||
createdAt: row.created_at,
|
||||
updatedAt: row.updated_at,
|
||||
};
|
||||
}
|
||||
|
||||
function mapToDb(item: any) {
|
||||
return {
|
||||
key: item.key,
|
||||
name: item.name,
|
||||
category_name: item.categoryName || null,
|
||||
aspect_ratio: item.aspectRatio || "1/1",
|
||||
bg_type: item.bgType || "gradient",
|
||||
bg_image_url: item.bgImageUrl || null,
|
||||
bg_gradient: item.bgGradient || null,
|
||||
plaque_color: item.plaqueColor || null,
|
||||
border_accent: item.borderAccent || null,
|
||||
accent_text: item.accentText || null,
|
||||
header_text: item.headerText || null,
|
||||
sub_text: item.subText || null,
|
||||
divider_color: item.dividerColor || null,
|
||||
qr_config: item.qrConfig || item.defaultQrConfig || null,
|
||||
layers: item.layers || item.defaultLayers || null,
|
||||
is_active: item.isActive !== undefined ? item.isActive : true,
|
||||
updated_at: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
async function readFromDisk(): Promise<any[]> {
|
||||
try {
|
||||
const raw = await fs.readFile(DATA_FILE, "utf-8");
|
||||
return JSON.parse(raw);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function writeToDisk(list: any[]): Promise<void> {
|
||||
try {
|
||||
await fs.writeFile(DATA_FILE, JSON.stringify(list, null, 2), "utf-8");
|
||||
} catch {}
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from("qr_stand_templates")
|
||||
.select("*")
|
||||
.order("created_at", { ascending: false });
|
||||
|
||||
if (!error && data && data.length > 0) {
|
||||
const mapped = data.map(mapFromDb);
|
||||
await writeToDisk(mapped);
|
||||
return NextResponse.json({ templates: mapped, source: "supabase_db" });
|
||||
}
|
||||
} catch {}
|
||||
|
||||
const diskTemplates = await readFromDisk();
|
||||
return NextResponse.json({ templates: diskTemplates, source: "disk_cache" });
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const body = await req.json();
|
||||
if (!body || !body.key) {
|
||||
return NextResponse.json({ error: "Şablon key zorunludur" }, { status: 400 });
|
||||
}
|
||||
|
||||
// 1. Supabase'e Upsert et
|
||||
try {
|
||||
const dbRow = mapToDb(body);
|
||||
await supabase.from("qr_stand_templates").upsert(dbRow, { onConflict: "key" });
|
||||
} catch (dbErr) {
|
||||
console.warn("Supabase upsert warning:", dbErr);
|
||||
}
|
||||
|
||||
// 2. Diske de yaz (offline / backup)
|
||||
const diskList = await readFromDisk();
|
||||
const existingIdx = diskList.findIndex((t: any) => t.key === body.key);
|
||||
if (existingIdx >= 0) {
|
||||
diskList[existingIdx] = { ...diskList[existingIdx], ...body };
|
||||
} else {
|
||||
diskList.unshift(body);
|
||||
}
|
||||
await writeToDisk(diskList);
|
||||
|
||||
return NextResponse.json({ success: true, template: body });
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ error: err.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(req: Request) {
|
||||
try {
|
||||
const { searchParams } = new URL(req.url);
|
||||
const key = searchParams.get("key");
|
||||
if (!key) {
|
||||
return NextResponse.json({ error: "Şablon key zorunludur" }, { status: 400 });
|
||||
}
|
||||
|
||||
// 1. Supabase'den Sil
|
||||
try {
|
||||
await supabase.from("qr_stand_templates").delete().eq("key", key);
|
||||
} catch (dbErr) {
|
||||
console.warn("Supabase delete warning:", dbErr);
|
||||
}
|
||||
|
||||
// 2. Diskten de Sil
|
||||
const diskList = await readFromDisk();
|
||||
const filtered = diskList.filter((t: any) => t.key !== key);
|
||||
await writeToDisk(filtered);
|
||||
|
||||
return NextResponse.json({ success: true, deletedKey: key });
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ error: err.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ interface MenuItemRow {
|
||||
price: number;
|
||||
image_url: string | null;
|
||||
is_active: boolean;
|
||||
translations: Record<string, { name: string; description: string | null }> | null;
|
||||
}
|
||||
|
||||
interface MenuCategoryRow {
|
||||
@@ -25,6 +26,7 @@ interface MenuCategoryRow {
|
||||
description: string | null;
|
||||
is_active: boolean;
|
||||
menu_items: MenuItemRow[];
|
||||
translations: Record<string, { name: string }> | null;
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
||||
@@ -71,14 +73,14 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
|
||||
async function fetchRestaurantBySlug(slug: string) {
|
||||
let { data: bySlug, error } = await supabase
|
||||
.from("restaurants")
|
||||
.select("id, name, slug, logo_url, cover_url, phone, address")
|
||||
.select("id, name, slug, logo_url, cover_url, phone, address, enabled_languages")
|
||||
.eq("slug", slug)
|
||||
.maybeSingle();
|
||||
|
||||
if (error && error.code === "42703") {
|
||||
const { data: fallback } = await supabase
|
||||
.from("restaurants")
|
||||
.select("id, name, slug, logo_url, phone, address")
|
||||
.select("id, name, slug, logo_url, phone, address, enabled_languages")
|
||||
.eq("slug", slug)
|
||||
.maybeSingle();
|
||||
if (fallback) {
|
||||
@@ -90,14 +92,14 @@ async function fetchRestaurantBySlug(slug: string) {
|
||||
|
||||
let { data: domainRow, error: domErr } = await supabase
|
||||
.from("domains")
|
||||
.select("restaurant_id, restaurants(id, name, slug, logo_url, cover_url, phone, address)")
|
||||
.select("restaurant_id, restaurants(id, name, slug, logo_url, cover_url, phone, address, enabled_languages)")
|
||||
.eq("hostname", slug)
|
||||
.maybeSingle();
|
||||
|
||||
if (domErr && domErr.code === "42703") {
|
||||
const { data: fallbackDom } = await supabase
|
||||
.from("domains")
|
||||
.select("restaurant_id, restaurants(id, name, slug, logo_url, phone, address)")
|
||||
.select("restaurant_id, restaurants(id, name, slug, logo_url, phone, address, enabled_languages)")
|
||||
.eq("hostname", slug)
|
||||
.maybeSingle();
|
||||
if (fallbackDom?.restaurants) {
|
||||
@@ -150,7 +152,9 @@ export default async function PublicMenuPage({ params, searchParams }: PageProps
|
||||
if (menu) {
|
||||
const { data: rawCategories } = await supabase
|
||||
.from("menu_categories")
|
||||
.select("id, name, description, is_active, menu_items(id, name, description, price, image_url, is_active)")
|
||||
.select(
|
||||
"id, name, description, is_active, translations, menu_items(id, name, description, price, image_url, is_active, translations)",
|
||||
)
|
||||
.eq("menu_id", menu.id)
|
||||
.eq("is_active", true)
|
||||
.order("sort_order", { ascending: true })
|
||||
@@ -161,6 +165,7 @@ export default async function PublicMenuPage({ params, searchParams }: PageProps
|
||||
name: cat.name,
|
||||
description: cat.description,
|
||||
is_active: cat.is_active,
|
||||
translations: cat.translations ?? {},
|
||||
menu_items: (cat.menu_items ?? [])
|
||||
.filter((item) => item.is_active)
|
||||
.map((item): MenuItem => ({
|
||||
@@ -170,6 +175,7 @@ export default async function PublicMenuPage({ params, searchParams }: PageProps
|
||||
price: item.price,
|
||||
image_url: item.image_url,
|
||||
is_active: item.is_active,
|
||||
translations: item.translations ?? {},
|
||||
})),
|
||||
}));
|
||||
} else if (slug === "demo" || slug === "gusto-brasserie") {
|
||||
@@ -202,6 +208,7 @@ export default async function PublicMenuPage({ params, searchParams }: PageProps
|
||||
categories={categories}
|
||||
initialThemeKey={themeKey}
|
||||
customThemeConfig={customConfig}
|
||||
enabledLanguages={restaurant.enabled_languages ?? ["tr"]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { THEME_PRESETS } from "@/lib/theme";
|
||||
import { AyrisFooterBadge } from "@/components/AyrisFooterBadge";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Menulio | AI Destekli Yeni Nesil QR Menü Platformu",
|
||||
@@ -283,7 +284,7 @@ export default function HomePage() {
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="border-t border-stone-800/80 bg-stone-950 py-12 px-6">
|
||||
<div className="max-w-7xl mx-auto flex flex-col sm:flex-row items-center justify-between gap-6 text-xs text-stone-500">
|
||||
<div className="max-w-7xl mx-auto flex flex-col sm:flex-row items-center justify-between gap-6 text-xs text-stone-500 mb-8">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="font-bold text-stone-300 text-sm tracking-wider">MENULIO</span>
|
||||
<span>•</span>
|
||||
@@ -302,6 +303,8 @@ export default function HomePage() {
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AyrisFooterBadge />
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import { getAdminSession, AdminUser } from "@/lib/admin-auth";
|
||||
|
||||
export default function AdminAuthGuard({ children }: { children: React.ReactNode }) {
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const [session, setSession] = useState<AdminUser | null>(null);
|
||||
const [isChecking, setIsChecking] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
// Skip auth guard for login page itself
|
||||
if (pathname === "/admin/login") {
|
||||
setIsChecking(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const currentSession = getAdminSession();
|
||||
if (!currentSession) {
|
||||
const callback = encodeURIComponent(pathname);
|
||||
router.replace(`/admin/login?callbackUrl=${callback}`);
|
||||
} else {
|
||||
setSession(currentSession);
|
||||
setIsChecking(false);
|
||||
}
|
||||
}, [pathname, router]);
|
||||
|
||||
// Don't protect non-admin pages if wrapper is applied globally
|
||||
if (pathname === "/admin/login") {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
if (isChecking) {
|
||||
return (
|
||||
<div className="min-h-screen bg-[#09090b] text-[#fafafa] flex flex-col items-center justify-center p-6 antialiased selection:bg-[#D4AF37]/30 selection:text-[#D4AF37]">
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<div className="relative w-14 h-14 flex items-center justify-center">
|
||||
<div className="absolute inset-0 rounded-2xl bg-gradient-to-tr from-[#D4AF37] to-[#F59E0B] opacity-20 blur-md animate-pulse" />
|
||||
<div className="w-12 h-12 rounded-xl bg-[#121215] border border-[#D4AF37]/40 flex items-center justify-center shadow-xl">
|
||||
<span className="font-extrabold text-[#D4AF37] text-xl">M</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs font-semibold text-zinc-400">
|
||||
<div className="w-2 h-2 rounded-full bg-[#D4AF37] animate-ping" />
|
||||
Yönetici Oturumu Doğrulanıyor...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from "react";
|
||||
|
||||
export function AyrisFooterBadge({ className = "" }: { className?: string }) {
|
||||
return (
|
||||
<footer className={`w-full border-t border-white/10 dark:border-zinc-800/80 py-4 px-6 text-xs text-zinc-500 dark:text-zinc-400 ${className}`}>
|
||||
<div className="max-w-7xl mx-auto flex flex-col sm:flex-row items-center justify-between gap-3 text-center sm:text-left">
|
||||
{/* Telif Hakkı */}
|
||||
<p>© {new Date().getFullYear()} Menulio. Tüm Hakları Saklıdır.</p>
|
||||
|
||||
{/* Görünür Ayris Tech İmzası */}
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="opacity-75">Created by</span>
|
||||
<a
|
||||
href="https://ayris.tech"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="font-medium text-zinc-300 hover:text-white transition-colors duration-200 hover:underline underline-offset-4"
|
||||
>
|
||||
Ayris Tech
|
||||
</a>
|
||||
|
||||
{/* Google Bot & Arama Motoru İndeksleme Katmanı (Görünmez / Screen-Reader Safe) */}
|
||||
<span className="sr-only">
|
||||
Legal AI & Solutions:{" "}
|
||||
<a href="https://ayris.legal" rel="noopener noreferrer">AyrisLegal</a>,{" "}
|
||||
<a href="https://ayrislegal.com" rel="noopener noreferrer">Ayris Legal AI</a>.{" "}
|
||||
The best local spots, hidden from plain sight. Marmaris Local{" "}
|
||||
<a href="https://marmarislocal.com" rel="noopener noreferrer">Marmaris Local</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,556 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { THEME_PRESETS, ThemePreset } from "@/lib/theme";
|
||||
import { DEMO_RESTAURANT, DEMO_CATEGORIES } from "@/lib/demo-data";
|
||||
import { PublicMenuClient } from "@/components/PublicMenuClient";
|
||||
|
||||
export function MenuTemplatesAdminClient() {
|
||||
const [themesMap, setThemesMap] = useState<Record<string, ThemePreset>>(THEME_PRESETS);
|
||||
const [activeThemeKey, setActiveThemeKey] = useState<string>("coastal");
|
||||
const [activeTab, setActiveTab] = useState<"gallery" | "customize">("gallery");
|
||||
const [toastMsg, setToastMsg] = useState<string | null>(null);
|
||||
const [jsonModalOpen, setJsonModalOpen] = useState(false);
|
||||
const [importedJson, setImportedJson] = useState("");
|
||||
|
||||
const currentTheme = themesMap[activeThemeKey] || THEME_PRESETS.coastal!;
|
||||
|
||||
const showToast = (msg: string) => {
|
||||
setToastMsg(msg);
|
||||
setTimeout(() => setToastMsg(null), 3000);
|
||||
};
|
||||
|
||||
const handleUpdateCurrentTheme = (updates: Partial<ThemePreset> | Partial<ThemePreset["config"]>) => {
|
||||
setThemesMap((prev) => {
|
||||
const existing = prev[activeThemeKey];
|
||||
if (!existing) return prev;
|
||||
|
||||
let updatedConfig = { ...existing.config };
|
||||
let updatedTopLevel = { ...existing };
|
||||
|
||||
if ("primaryColor" in updates || "productLayout" in updates || "categoryLayout" in updates || "headerLayout" in updates) {
|
||||
updatedConfig = { ...updatedConfig, ...updates };
|
||||
} else {
|
||||
updatedTopLevel = { ...updatedTopLevel, ...updates };
|
||||
}
|
||||
|
||||
return {
|
||||
...prev,
|
||||
[activeThemeKey]: {
|
||||
...updatedTopLevel,
|
||||
config: updatedConfig,
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const handleSaveAll = () => {
|
||||
showToast(`"${currentTheme.name}" şablon ayarları başarıyla kaydedildi!`);
|
||||
};
|
||||
|
||||
const handleImportJsonSubmit = () => {
|
||||
try {
|
||||
const parsed = JSON.parse(importedJson);
|
||||
if (!parsed.key || !parsed.name || !parsed.config) {
|
||||
throw new Error("Geçersiz şablon JSON formatı. 'key', 'name' ve 'config' alanları zorunludur.");
|
||||
}
|
||||
setThemesMap((prev) => ({
|
||||
...prev,
|
||||
[parsed.key]: parsed,
|
||||
}));
|
||||
setActiveThemeKey(parsed.key);
|
||||
setJsonModalOpen(false);
|
||||
setImportedJson("");
|
||||
showToast(`Yeni şablon "${parsed.name}" başarıyla içe aktarıldı!`);
|
||||
} catch (err: any) {
|
||||
alert("Hata: " + (err?.message || "Geçersiz JSON formatı"));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto space-y-6">
|
||||
{/* Toast Bildirimi */}
|
||||
{toastMsg && (
|
||||
<div className="fixed top-20 right-6 z-50 bg-[#D4AF37] text-black font-extrabold px-5 py-3 rounded-2xl shadow-2xl shadow-[#D4AF37]/30 text-xs flex items-center gap-2 animate-bounce">
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2.5" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
{toastMsg}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Üst Karşılama Barı */}
|
||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4 bg-[#121215] border border-white/10 p-6 rounded-3xl">
|
||||
<div>
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-[#D4AF37]/10 border border-[#D4AF37]/30 text-xs font-bold text-[#D4AF37] mb-2">
|
||||
🎨 WEB DİNAMİK MENÜ TEMA STÜDYOSU
|
||||
</div>
|
||||
<h1 className="text-2xl md:text-3xl font-black text-white tracking-tight">
|
||||
Web Menü Şablon Yöneticisi
|
||||
</h1>
|
||||
<p className="text-xs text-zinc-400 mt-1">
|
||||
Restoran web menülerinin renk paletlerini, düzen yapılarını ve tipografisini canlı cihaz simülatöründe anında test edip yönetin.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setImportedJson(JSON.stringify(currentTheme, null, 2));
|
||||
setJsonModalOpen(true);
|
||||
}}
|
||||
className="px-4 py-2.5 rounded-xl bg-white/5 border border-white/10 hover:bg-white/10 text-xs font-bold text-zinc-300 transition-all flex items-center gap-2 cursor-pointer"
|
||||
>
|
||||
<svg className="w-4 h-4 text-[#D4AF37]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
|
||||
</svg>
|
||||
JSON İçe/Dışa Aktar
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSaveAll}
|
||||
className="px-5 py-2.5 rounded-xl bg-gradient-to-r from-[#D4AF37] to-[#F59E0B] text-black font-extrabold text-xs shadow-lg shadow-[#D4AF37]/20 hover:opacity-95 transition-all flex items-center gap-2 cursor-pointer"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2.5" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
Değişiklikleri Yayınla & Kaydet
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Ana İçerik Çift Sütun Düzeni: Sol Kontrol / Sağ Canlı Simülatör */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 items-start">
|
||||
{/* SOL PANEL: Şablon Galerisi ve Özelleştirici */}
|
||||
<div className="lg:col-span-7 space-y-6">
|
||||
{/* Sekme Butonları */}
|
||||
<div className="flex items-center gap-2 p-1.5 rounded-2xl bg-[#121215] border border-white/10">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActiveTab("gallery")}
|
||||
className={`flex-1 py-2.5 px-4 rounded-xl font-extrabold text-xs transition-all flex items-center justify-center gap-2 cursor-pointer ${
|
||||
activeTab === "gallery"
|
||||
? "bg-[#D4AF37] text-black shadow-lg shadow-[#D4AF37]/20"
|
||||
: "text-zinc-400 hover:text-white"
|
||||
}`}
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
|
||||
</svg>
|
||||
Hazır Şablonlar ({Object.keys(themesMap).length})
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActiveTab("customize")}
|
||||
className={`flex-1 py-2.5 px-4 rounded-xl font-extrabold text-xs transition-all flex items-center justify-center gap-2 cursor-pointer ${
|
||||
activeTab === "customize"
|
||||
? "bg-[#D4AF37] text-black shadow-lg shadow-[#D4AF37]/20"
|
||||
: "text-zinc-400 hover:text-white"
|
||||
}`}
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" />
|
||||
</svg>
|
||||
Seçili Temayı Özelleştir ({currentTheme.key})
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* TAB 1: ŞABLON GALERİSİ KARTLARI */}
|
||||
{activeTab === "gallery" && (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{Object.values(themesMap).map((theme) => {
|
||||
const isSelected = theme.key === activeThemeKey;
|
||||
return (
|
||||
<div
|
||||
key={theme.key}
|
||||
onClick={() => setActiveThemeKey(theme.key)}
|
||||
className={`p-5 rounded-2xl border transition-all cursor-pointer space-y-4 relative overflow-hidden group ${
|
||||
isSelected
|
||||
? "bg-[#16161A] border-[#D4AF37] ring-2 ring-[#D4AF37]/30 shadow-xl shadow-[#D4AF37]/10"
|
||||
: "bg-[#121215] border-white/10 hover:border-white/20 hover:bg-white/[0.02]"
|
||||
}`}
|
||||
>
|
||||
{/* Üst İkon & Seçim Rozeti */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className="w-4 h-4 rounded-full border border-black/20 shadow-sm"
|
||||
style={{ backgroundColor: theme.config.primaryColor }}
|
||||
/>
|
||||
<span
|
||||
className="w-4 h-4 rounded-full border border-black/20 shadow-sm"
|
||||
style={{ backgroundColor: theme.cardBg }}
|
||||
/>
|
||||
</div>
|
||||
{isSelected ? (
|
||||
<span className="text-[10px] bg-[#D4AF37] text-black font-extrabold px-2 py-0.5 rounded-full flex items-center gap-1">
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="3" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
AKTİF
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-[10px] text-zinc-500 font-mono uppercase">
|
||||
{theme.key}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Tema Adı & Açıklama */}
|
||||
<div>
|
||||
<h3 className="font-extrabold text-white text-base group-hover:text-[#D4AF37] transition-colors">
|
||||
{theme.name}
|
||||
</h3>
|
||||
<div className="flex items-center gap-2 mt-2 text-[11px] text-zinc-400">
|
||||
<span className="bg-white/5 border border-white/10 px-2 py-0.5 rounded-md capitalize font-mono">
|
||||
{theme.fontFamily}
|
||||
</span>
|
||||
<span className="bg-white/5 border border-white/10 px-2 py-0.5 rounded-md capitalize font-mono">
|
||||
{theme.config.productLayout}
|
||||
</span>
|
||||
<span className="bg-white/5 border border-white/10 px-2 py-0.5 rounded-md capitalize font-mono">
|
||||
{theme.config.categoryLayout}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Alt Butonlar */}
|
||||
<div className="pt-3 border-t border-white/5 flex items-center justify-between">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setActiveThemeKey(theme.key);
|
||||
setActiveTab("customize");
|
||||
}}
|
||||
className="text-xs font-bold text-[#D4AF37] hover:underline flex items-center gap-1"
|
||||
>
|
||||
Renk & Düzen Özelleştir →
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* TAB 2: SEÇİLİ TEMAYI ÖZELLEŞTİR */}
|
||||
{activeTab === "customize" && (
|
||||
<div className="p-6 rounded-2xl bg-[#121215] border border-white/10 space-y-6">
|
||||
<div className="flex items-center justify-between border-b border-white/10 pb-4">
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-white flex items-center gap-2">
|
||||
<span
|
||||
className="w-3.5 h-3.5 rounded-full inline-block"
|
||||
style={{ backgroundColor: currentTheme.config.primaryColor }}
|
||||
/>
|
||||
{currentTheme.name}
|
||||
</h3>
|
||||
<p className="text-xs text-zinc-400 mt-0.5">
|
||||
Ana renkleri, arka plan tonlarını ve düzen tiplerini anında özelleştirin.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActiveTab("gallery")}
|
||||
className="text-xs font-bold text-zinc-400 hover:text-white"
|
||||
>
|
||||
← Galeriye Dön
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Renk Ayarları */}
|
||||
<div className="space-y-4">
|
||||
<h4 className="text-xs font-extrabold text-[#D4AF37] uppercase tracking-wider">
|
||||
1. Renk Paleti & Tonlar
|
||||
</h4>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{/* Primary Color */}
|
||||
<div className="space-y-1.5 p-3 rounded-xl bg-white/[0.03] border border-white/5">
|
||||
<label className="text-xs font-semibold text-zinc-300 block">
|
||||
Ana Vurgu Rengi (Primary)
|
||||
</label>
|
||||
<div className="flex items-center gap-3">
|
||||
<input
|
||||
type="color"
|
||||
value={currentTheme.config.primaryColor || "#D4AF37"}
|
||||
onChange={(e) =>
|
||||
handleUpdateCurrentTheme({ primaryColor: e.target.value })
|
||||
}
|
||||
className="w-10 h-10 rounded-lg cursor-pointer bg-transparent border-0"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={currentTheme.config.primaryColor || "#D4AF37"}
|
||||
onChange={(e) =>
|
||||
handleUpdateCurrentTheme({ primaryColor: e.target.value })
|
||||
}
|
||||
className="flex-1 bg-black/40 border border-white/10 rounded-lg px-3 py-1.5 text-xs text-white font-mono uppercase"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Background Color */}
|
||||
<div className="space-y-1.5 p-3 rounded-xl bg-white/[0.03] border border-white/5">
|
||||
<label className="text-xs font-semibold text-zinc-300 block">
|
||||
Sayfa Arka Plan Rengi
|
||||
</label>
|
||||
<div className="flex items-center gap-3">
|
||||
<input
|
||||
type="color"
|
||||
value={currentTheme.config.background || "#FAF8F5"}
|
||||
onChange={(e) =>
|
||||
handleUpdateCurrentTheme({ background: e.target.value })
|
||||
}
|
||||
className="w-10 h-10 rounded-lg cursor-pointer bg-transparent border-0"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={currentTheme.config.background || "#FAF8F5"}
|
||||
onChange={(e) =>
|
||||
handleUpdateCurrentTheme({ background: e.target.value })
|
||||
}
|
||||
className="flex-1 bg-black/40 border border-white/10 rounded-lg px-3 py-1.5 text-xs text-white font-mono uppercase"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Card Background */}
|
||||
<div className="space-y-1.5 p-3 rounded-xl bg-white/[0.03] border border-white/5">
|
||||
<label className="text-xs font-semibold text-zinc-300 block">
|
||||
Ürün Kart Arka Planı
|
||||
</label>
|
||||
<div className="flex items-center gap-3">
|
||||
<input
|
||||
type="color"
|
||||
value={currentTheme.cardBg.startsWith("#") ? currentTheme.cardBg : "#FFFFFF"}
|
||||
onChange={(e) => handleUpdateCurrentTheme({ cardBg: e.target.value })}
|
||||
className="w-10 h-10 rounded-lg cursor-pointer bg-transparent border-0"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={currentTheme.cardBg}
|
||||
onChange={(e) => handleUpdateCurrentTheme({ cardBg: e.target.value })}
|
||||
className="flex-1 bg-black/40 border border-white/10 rounded-lg px-3 py-1.5 text-xs text-white font-mono uppercase"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Text Primary */}
|
||||
<div className="space-y-1.5 p-3 rounded-xl bg-white/[0.03] border border-white/5">
|
||||
<label className="text-xs font-semibold text-zinc-300 block">
|
||||
Ana Başlık & Fiyat Yazı Rengi
|
||||
</label>
|
||||
<div className="flex items-center gap-3">
|
||||
<input
|
||||
type="color"
|
||||
value={currentTheme.textPrimary.startsWith("#") ? currentTheme.textPrimary : "#1C1917"}
|
||||
onChange={(e) => handleUpdateCurrentTheme({ textPrimary: e.target.value })}
|
||||
className="w-10 h-10 rounded-lg cursor-pointer bg-transparent border-0"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={currentTheme.textPrimary}
|
||||
onChange={(e) => handleUpdateCurrentTheme({ textPrimary: e.target.value })}
|
||||
className="flex-1 bg-black/40 border border-white/10 rounded-lg px-3 py-1.5 text-xs text-white font-mono uppercase"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Düzen & Yerleşim Ayarları */}
|
||||
<div className="space-y-4 pt-4 border-t border-white/10">
|
||||
<h4 className="text-xs font-extrabold text-[#D4AF37] uppercase tracking-wider">
|
||||
2. Düzen & Yerleşim Mimarisi
|
||||
</h4>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||
{/* Product Layout */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-xs font-semibold text-zinc-300 block">
|
||||
Ürün Görünüm Tipi
|
||||
</label>
|
||||
<select
|
||||
value={currentTheme.config.productLayout || "card"}
|
||||
onChange={(e) =>
|
||||
handleUpdateCurrentTheme({ productLayout: e.target.value as any })
|
||||
}
|
||||
className="w-full bg-black/40 border border-white/10 rounded-xl px-3 py-2 text-xs text-white focus:border-[#D4AF37] focus:outline-none"
|
||||
>
|
||||
<option value="card">Card (Kutu / Kart)</option>
|
||||
<option value="list">List (Satır / Liste)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Category Layout */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-xs font-semibold text-zinc-300 block">
|
||||
Kategori Gezinme Tipi
|
||||
</label>
|
||||
<select
|
||||
value={currentTheme.config.categoryLayout || "tabs"}
|
||||
onChange={(e) =>
|
||||
handleUpdateCurrentTheme({ categoryLayout: e.target.value as any })
|
||||
}
|
||||
className="w-full bg-black/40 border border-white/10 rounded-xl px-3 py-2 text-xs text-white focus:border-[#D4AF37] focus:outline-none"
|
||||
>
|
||||
<option value="tabs">Tabs (Yatay Sekmeler)</option>
|
||||
<option value="accordion">Accordion (Açılır Kapanır)</option>
|
||||
<option value="flat">Flat (Düz Sıralı)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Header Layout */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-xs font-semibold text-zinc-300 block">
|
||||
Header Hizalaması
|
||||
</label>
|
||||
<select
|
||||
value={currentTheme.config.headerLayout || "centered"}
|
||||
onChange={(e) =>
|
||||
handleUpdateCurrentTheme({ headerLayout: e.target.value as any })
|
||||
}
|
||||
className="w-full bg-black/40 border border-white/10 rounded-xl px-3 py-2 text-xs text-white focus:border-[#D4AF37] focus:outline-none"
|
||||
>
|
||||
<option value="centered">Centered (Ortalanmış)</option>
|
||||
<option value="left">Left (Sola Hizalı)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tipografi */}
|
||||
<div className="space-y-4 pt-4 border-t border-white/10">
|
||||
<h4 className="text-xs font-extrabold text-[#D4AF37] uppercase tracking-wider">
|
||||
3. Tipografi & Font Ailesi
|
||||
</h4>
|
||||
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
{[
|
||||
{ id: "serif", label: "Zarif Serif", desc: "Playfair / Georgia" },
|
||||
{ id: "sans", label: "Modern Sans", desc: "Inter / System" },
|
||||
{ id: "heading", label: "Güçlü Display", desc: "Outfit / Montserrat" },
|
||||
].map((font) => (
|
||||
<button
|
||||
key={font.id}
|
||||
type="button"
|
||||
onClick={() => handleUpdateCurrentTheme({ fontFamily: font.id as any })}
|
||||
className={`p-3 rounded-xl border text-left transition-all cursor-pointer ${
|
||||
currentTheme.fontFamily === font.id
|
||||
? "bg-[#D4AF37]/15 border-[#D4AF37] text-white"
|
||||
: "bg-white/[0.02] border-white/10 text-zinc-400 hover:text-white"
|
||||
}`}
|
||||
>
|
||||
<div className="text-xs font-bold">{font.label}</div>
|
||||
<div className="text-[10px] opacity-70 mt-0.5">{font.desc}</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* SAĞ PANEL: Canlı İnceleme Telefon Simülatörü */}
|
||||
<div className="lg:col-span-5 sticky top-20">
|
||||
<div className="bg-[#121215] border border-white/10 rounded-3xl p-5 space-y-4 shadow-2xl">
|
||||
<div className="flex items-center justify-between border-b border-white/10 pb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-emerald-400 animate-pulse" />
|
||||
<span className="text-xs font-bold text-white">Canlı Telefon Simülatörü</span>
|
||||
</div>
|
||||
<span className="text-[10px] font-mono text-[#D4AF37] bg-[#D4AF37]/10 px-2 py-0.5 rounded-full font-bold">
|
||||
{currentTheme.name}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Mock iPhone Frame */}
|
||||
<div className="w-full max-w-[340px] h-[640px] mx-auto rounded-[40px] border-[8px] border-zinc-800 bg-black shadow-2xl overflow-hidden relative flex flex-col">
|
||||
{/* iPhone Notch Header */}
|
||||
<div className="w-full h-6 bg-black shrink-0 flex items-center justify-center relative z-20">
|
||||
<div className="w-24 h-4 bg-zinc-900 rounded-b-xl" />
|
||||
</div>
|
||||
|
||||
{/* Dynamic Theme Render Component */}
|
||||
<div className="flex-1 overflow-y-auto relative">
|
||||
<PublicMenuClient
|
||||
restaurant={DEMO_RESTAURANT}
|
||||
categories={DEMO_CATEGORIES}
|
||||
initialThemeKey={currentTheme.key}
|
||||
customThemeConfig={currentTheme.config}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Home Bar Indicator */}
|
||||
<div className="w-full h-4 bg-black shrink-0 flex items-center justify-center z-20">
|
||||
<div className="w-28 h-1 bg-zinc-700 rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center text-[11px] text-zinc-500">
|
||||
Sol taraftan yaptığınız renk ve düzen değişiklikleri anında burada simüle edilir.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* JSON İÇE / DIŞA AKTARMA MODAL */}
|
||||
{jsonModalOpen && (
|
||||
<div className="fixed inset-0 z-50 bg-black/80 backdrop-blur-sm flex items-center justify-center p-4">
|
||||
<div className="w-full max-w-xl bg-[#121215] border border-white/15 rounded-3xl p-6 space-y-4 shadow-2xl">
|
||||
<div className="flex items-center justify-between border-b border-white/10 pb-3">
|
||||
<h3 className="font-extrabold text-white text-base">
|
||||
Tema JSON Spesifikasyonu
|
||||
</h3>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setJsonModalOpen(false)}
|
||||
className="text-zinc-400 hover:text-white"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-zinc-400">
|
||||
Mevcut temanın JSON kodunu kopyalayabilir veya yeni bir tema JSON spesifikasyonunu buraya yapıştırıp içe aktarabilirsiniz:
|
||||
</p>
|
||||
|
||||
<textarea
|
||||
value={importedJson}
|
||||
onChange={(e) => setImportedJson(e.target.value)}
|
||||
rows={12}
|
||||
className="w-full bg-black/60 border border-white/10 rounded-xl p-4 text-xs font-mono text-emerald-400 focus:outline-none focus:border-[#D4AF37]"
|
||||
/>
|
||||
|
||||
<div className="flex items-center justify-end gap-3 pt-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(importedJson);
|
||||
showToast("JSON panoya kopyalandı!");
|
||||
}}
|
||||
className="px-4 py-2 rounded-xl bg-white/5 border border-white/10 hover:bg-white/10 text-xs font-bold text-white cursor-pointer"
|
||||
>
|
||||
Panoya Kopyala
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleImportJsonSubmit}
|
||||
className="px-4 py-2 rounded-xl bg-[#D4AF37] text-black font-extrabold text-xs cursor-pointer shadow-lg shadow-[#D4AF37]/20"
|
||||
>
|
||||
İçe Aktar & Uygula
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||