7eb0c1acc2
Sorun: - node-appwrite v20-25 hepsi Node 26'da bozuk (node-fetch-native-with-agent polyfill) - appwrite browser SDK Server Action context'inde 'unexpected response' veriyor (büyük olasılıkla browser-only global'ları kontrol ederken) Çözüm: - Tüm Appwrite SDK'larını sil (appwrite + node-appwrite) - lib/appwrite-rest.ts: native fetch üzerinde ~250 satırlık ince REST wrapper - account: createEmailPasswordSession, get, deleteSession - tablesDB: listRows, getRow, createRow, updateRow, deleteRow - storage: listFiles, createFile, deleteFile, fileViewUrl - Q helpers: equal, orderAsc/Desc, limit, offset - AppwriteError class - Session secret cookie tabanlı auth korundu (isletmem-kovakcrm'deki desen) - Tüm CRUD action ve query'ler REST katmanına bağlandı End-to-end test edildi: ✓ Login (proper 401 hata mesajları, başarılı durumda redirect) ✓ Public read (services, blog, testimonials) ✓ Anonim create (contact form) ✓ Build (24 route) ✓ Sıfır SDK bağımlılığı — Node 26 sorunu yok
127 lines
4.6 KiB
TypeScript
127 lines
4.6 KiB
TypeScript
import Link from "next/link";
|
||
import { ArrowRight } from "lucide-react";
|
||
import { DATABASE_ID, Q, TABLES, tablesDB } from "@/lib/appwrite-rest";
|
||
import { requireSessionSecret } from "@/lib/auth";
|
||
|
||
async function safeCount(tableId: string, queries: string[] = []) {
|
||
try {
|
||
const secret = await requireSessionSecret();
|
||
const res = await tablesDB.listRows(
|
||
DATABASE_ID,
|
||
tableId,
|
||
[...queries, Q.limit(1)],
|
||
secret,
|
||
);
|
||
return res.total ?? 0;
|
||
} catch {
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
export default async function AdminDashboard() {
|
||
const [posts, drafts, services, projects, testimonials, newMessages, totalMessages] =
|
||
await Promise.all([
|
||
safeCount(TABLES.blogPosts, [Q.equal("status", "published")]),
|
||
safeCount(TABLES.blogPosts, [Q.equal("status", "draft")]),
|
||
safeCount(TABLES.services),
|
||
safeCount(TABLES.projects),
|
||
safeCount(TABLES.testimonials),
|
||
safeCount(TABLES.contactMessages, [Q.equal("status", "new")]),
|
||
safeCount(TABLES.contactMessages),
|
||
]);
|
||
|
||
const cards = [
|
||
{ label: "Yayında blog", value: posts, href: "/admin/blog", accent: "navy" },
|
||
{ label: "Taslak blog", value: drafts, href: "/admin/blog", accent: "amber" },
|
||
{ label: "Hizmet", value: services, href: "/admin/hizmetler", accent: "navy" },
|
||
{ label: "Proje", value: projects, href: "/admin/projeler", accent: "navy" },
|
||
{ label: "Referans", value: testimonials, href: "/admin/referanslar", accent: "navy" },
|
||
{ label: "Yeni mesaj", value: newMessages, href: "/admin/iletisim?filter=new", accent: "red" },
|
||
{ label: "Toplam mesaj", value: totalMessages, href: "/admin/iletisim", accent: "navy" },
|
||
];
|
||
|
||
return (
|
||
<div>
|
||
<header>
|
||
<h1 className="text-2xl font-bold text-[var(--navy)]">Pano</h1>
|
||
<p className="mt-1 text-sm text-[var(--muted)]">
|
||
Site içeriklerini buradan yönetin.
|
||
</p>
|
||
</header>
|
||
|
||
<div className="mt-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||
{cards.map((c) => (
|
||
<Link
|
||
key={c.label}
|
||
href={c.href}
|
||
className="group rounded-2xl border border-[var(--border)] bg-white p-5 transition hover:border-[var(--sky)]/40 hover:shadow-md"
|
||
>
|
||
<p className="text-xs font-medium uppercase tracking-wider text-[var(--muted)]">
|
||
{c.label}
|
||
</p>
|
||
<div className="mt-2 flex items-end justify-between">
|
||
<p
|
||
className={`text-3xl font-bold ${
|
||
c.accent === "red"
|
||
? "text-red-600"
|
||
: c.accent === "amber"
|
||
? "text-amber-600"
|
||
: "text-[var(--navy)]"
|
||
}`}
|
||
>
|
||
{c.value}
|
||
</p>
|
||
<ArrowRight className="size-4 -translate-x-2 text-[var(--muted)] opacity-0 transition group-hover:translate-x-0 group-hover:opacity-100" />
|
||
</div>
|
||
</Link>
|
||
))}
|
||
</div>
|
||
|
||
<section className="mt-10 grid gap-6 lg:grid-cols-2">
|
||
<QuickActions />
|
||
<RecentLinks />
|
||
</section>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function QuickActions() {
|
||
const actions = [
|
||
{ href: "/admin/blog/new", label: "Yeni blog yazısı" },
|
||
{ href: "/admin/projeler/new", label: "Yeni proje ekle" },
|
||
{ href: "/admin/referanslar/new", label: "Yeni referans" },
|
||
{ href: "/admin/seo", label: "SEO ayarları" },
|
||
];
|
||
return (
|
||
<div className="rounded-2xl border border-[var(--border)] bg-white p-6">
|
||
<h2 className="text-base font-semibold text-[var(--navy)]">Hızlı aksiyonlar</h2>
|
||
<ul className="mt-4 space-y-2">
|
||
{actions.map((a) => (
|
||
<li key={a.href}>
|
||
<Link
|
||
href={a.href}
|
||
className="flex items-center justify-between rounded-lg border border-[var(--border)] px-3 py-2 text-sm text-[var(--navy)] transition hover:border-[var(--sky)]"
|
||
>
|
||
{a.label}
|
||
<ArrowRight className="size-4 text-[var(--sky-600)]" />
|
||
</Link>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function RecentLinks() {
|
||
return (
|
||
<div className="rounded-2xl border border-[var(--border)] bg-white p-6">
|
||
<h2 className="text-base font-semibold text-[var(--navy)]">Kısayollar</h2>
|
||
<ul className="mt-4 space-y-1 text-sm text-[var(--muted)]">
|
||
<li>• <Link href="/" target="_blank" className="hover:text-[var(--navy)]">Siteyi yeni sekmede aç</Link></li>
|
||
<li>• <Link href="/admin/medya" className="hover:text-[var(--navy)]">Medya kütüphanesi</Link></li>
|
||
<li>• <Link href="/admin/seo" className="hover:text-[var(--navy)]">SEO yönetimi</Link></li>
|
||
</ul>
|
||
</div>
|
||
);
|
||
}
|