feat: admin auth, web menu template studio, live text editing & Ayris Tech footer backlink

This commit is contained in:
AyrisAI
2026-08-23 01:45:00 +03:00
parent e7ba6c7807
commit 1f8195ff67
48 changed files with 5588 additions and 402 deletions
+205
View File
@@ -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 &copy; {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>
);
}