Files
kovakyazilim/app/admin/(protected)/page.tsx
T
Ege Can Komur 4096b3d87b fix: Node 26 uyumsuzluğunu çöz — node-appwrite -> appwrite SDK
Sorun:
- node-appwrite paketi 'node-fetch-native-with-agent' polyfill'i kullanıyor
- Node.js 26'nın undici implementation'ı ile uyumsuz
- 'fetch failed / InvalidArgumentError: invalid onError method' hatası
- Login dahil tüm Appwrite çağrıları başarısız

Çözüm:
- Tüm node-appwrite kullanımını browser SDK 'appwrite'a geçir
- Browser SDK native fetch kullanıyor, Node 26 uyumlu
- API key tabanlı admin client yerine session cookie tabanlı user client
- Public reads (read('any')): publicDB (auth'suz client)
- Admin CRUD: userDB(sessionSecret) (cookie'deki session)
- Storage upload doğrudan File objesi alıyor (InputFile.fromBuffer gerekmez)

Etkilenen dosyalar:
- lib/appwrite-server.ts: publicClient + sessionClient
- lib/auth.ts: requireSessionSecret eklendi
- lib/admin-actions.ts: tüm action'lar sessionClient kullanıyor
- app/actions.ts: publicDB
- lib/data.ts: publicDB
- app/admin/login/actions.ts: appwrite SDK
- app/admin/(protected)/page.tsx, medya/page.tsx: userDB/userStorage

End-to-end test edildi:
✓ Login (401 doğru hata)
✓ Public read (services)
✓ Anonim create (contact form)
✓ npm run build 23 route
2026-05-20 02:21:34 +03:00

127 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Link from "next/link";
import { ArrowRight } from "lucide-react";
import { Query } from "appwrite";
import { DATABASE_ID, TABLES, userDB } from "@/lib/appwrite-server";
import { requireSessionSecret } from "@/lib/auth";
async function safeCount(tableId: string, queries: string[] = []) {
try {
const secret = await requireSessionSecret();
const res = await userDB(secret).listRows({
databaseId: DATABASE_ID,
tableId,
queries: [...queries, Query.limit(1)],
});
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, [Query.equal("status", "published")]),
safeCount(TABLES.blogPosts, [Query.equal("status", "draft")]),
safeCount(TABLES.services),
safeCount(TABLES.projects),
safeCount(TABLES.testimonials),
safeCount(TABLES.contactMessages, [Query.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 </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>
);
}