Files
kovakyazilim/app/admin/(protected)/menu/form.tsx
T
egecankomur d49c9aa225 feat: SEO altyapısı + admin editör/favicon/menü düzeltmeleri
Admin & site:
- @tailwindcss/typography ekle → editör ve yayın içeriği prose stilleriyle düzgün render
- Favicon: logo.png'den kare app/icon.png + apple-icon.png, varsayılan favicon.ico kaldırıldı
- SEO keyword: seo_settings.default_keywords + seo_pages.keywords + buildMetadata birleştirme
- Menü düzeni admin'den yönetilebilir (site_settings.nav_items, /admin/menu, header & mobile-menu refactor)

SEO:
- app/sitemap.ts (statik + blog/hizmet/çözüm/proje/sektör dinamik)
- app/robots.ts (sitemap ref + /admin,/api disallow)
- app/llms.txt/route.ts (AI/LLM rehberi)
- BlogPosting/Service/FAQ/Article JSON-LD wire (json-ld bileşenleri bağlandı)
- buildMetadata: blog/proje OG görseli + type article + keywords birleştirme düzeltmesi
- blog tags → keyword
2026-06-04 07:15:18 +03:00

138 lines
4.5 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.
"use client";
import { useState } from "react";
import {
ArrowDown,
ArrowUp,
Eye,
EyeOff,
GripVertical,
Save,
} from "lucide-react";
import {
FormActions,
FormShell,
PrimaryButton,
} from "@/components/admin/form";
import { saveNavMenu } from "@/lib/admin-actions";
import { serializeNavItems, type NavItem } from "@/lib/nav";
export function MenuForm({ initial }: { initial: NavItem[] }) {
const [items, setItems] = useState<NavItem[]>(initial);
function move(index: number, dir: -1 | 1) {
const target = index + dir;
if (target < 0 || target >= items.length) return;
setItems((prev) => {
const next = [...prev];
[next[index], next[target]] = [next[target], next[index]];
return next;
});
}
function toggleVisible(index: number) {
setItems((prev) =>
prev.map((it, i) =>
i === index ? { ...it, visible: !it.visible } : it,
),
);
}
function setLabel(index: number, value: string) {
setItems((prev) =>
prev.map((it, i) => (i === index ? { ...it, label: value } : it)),
);
}
const payload = serializeNavItems(
items.map((i) => ({ key: i.key, visible: i.visible, label: i.label })),
);
return (
<form action={saveNavMenu}>
<input type="hidden" name="nav_items" value={payload} />
<FormShell>
<ul className="space-y-2">
{items.map((item, i) => (
<li
key={item.key}
className={`flex items-center gap-3 rounded-xl border border-[var(--border)] bg-white px-3 py-2.5 ${
item.visible ? "" : "opacity-50"
}`}
>
<GripVertical className="size-4 shrink-0 text-[var(--muted)]" />
<div className="flex shrink-0 flex-col">
<button
type="button"
onClick={() => move(i, -1)}
disabled={i === 0}
aria-label="Yukarı taşı"
className="text-[var(--muted)] transition hover:text-[var(--navy)] disabled:opacity-30"
>
<ArrowUp className="size-4" />
</button>
<button
type="button"
onClick={() => move(i, 1)}
disabled={i === items.length - 1}
aria-label="Aşağı taşı"
className="text-[var(--muted)] transition hover:text-[var(--navy)] disabled:opacity-30"
>
<ArrowDown className="size-4" />
</button>
</div>
<input
value={item.label}
onChange={(e) => setLabel(i, e.target.value)}
className="min-w-0 flex-1 rounded-lg border border-[var(--border)] bg-white px-3 py-1.5 text-sm outline-none transition focus:border-[var(--sky)] focus:ring-2 focus:ring-[var(--sky)]/20"
/>
<span className="shrink-0 font-mono text-xs text-[var(--muted)]">
{item.href}
</span>
{item.mega && (
<span className="shrink-0 rounded-full bg-[var(--navy-50)] px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wider text-[var(--navy)]">
Mega menü
</span>
)}
<button
type="button"
onClick={() => toggleVisible(i)}
aria-label={item.visible ? "Gizle" : "Göster"}
title={item.visible ? "Menüde görünür" : "Menüde gizli"}
className={`inline-flex size-8 shrink-0 items-center justify-center rounded-lg transition ${
item.visible
? "bg-[var(--navy)] text-white"
: "border border-[var(--border)] text-[var(--muted)] hover:text-[var(--navy)]"
}`}
>
{item.visible ? (
<Eye className="size-4" />
) : (
<EyeOff className="size-4" />
)}
</button>
</li>
))}
</ul>
<p className="mt-3 text-xs text-[var(--muted)]">
Sıralama ok tuşlarıyla değişir. Göz simgesiyle bir öğeyi menüden
gizleyebilirsiniz. &ldquo;Hizmetler&rdquo; öğesi mega menü olarak
açılır.
</p>
<FormActions>
<PrimaryButton>
<Save className="size-4" /> Menüyü kaydet
</PrimaryButton>
</FormActions>
</FormShell>
</form>
);
}