feat(qr): complete QR studio, dynamic google fonts, mouse canvas drag, and template sync
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import React from "react";
|
||||
|
||||
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 (
|
||||
<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 bg-[#D4AF37]/10 text-[#D4AF37] border border-[#D4AF37]/25 font-semibold text-sm transition-all shadow-sm"
|
||||
>
|
||||
<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-2V6a2 2 0 00-2-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">
|
||||
JSON
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/admin"
|
||||
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="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 Durum Kartı */}
|
||||
<div className="p-4 border-t border-white/10">
|
||||
<div className="p-3.5 rounded-xl bg-white/[0.04] border border-white/5 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-[11px] font-medium text-zinc-400">Şablon Motoru</span>
|
||||
<span className="flex items-center gap-1.5 text-[11px] font-bold text-emerald-400">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse" />
|
||||
CANLI (V2)
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-[11px] text-zinc-500">
|
||||
Uygulama güncellemesi gerektirmeden JSON ile anında yayınlanır.
|
||||
</div>
|
||||
</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]" />
|
||||
API: <span className="font-semibold text-white">localhost:3001</span>
|
||||
</div>
|
||||
<Link
|
||||
href="/"
|
||||
className="text-xs text-zinc-400 hover:text-white transition-colors"
|
||||
>
|
||||
Menulio Web →
|
||||
</Link>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Sayfa İçeriği */}
|
||||
<div className="flex-1 p-6 md:p-8 overflow-y-auto">
|
||||
{children}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
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>
|
||||
|
||||
<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 self-start"
|
||||
>
|
||||
<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>
|
||||
Yeni Şablon Tasarla & Ekle
|
||||
</Link>
|
||||
</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 });
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user