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
This commit is contained in:
+119
@@ -0,0 +1,119 @@
|
||||
// Üst menü (header + mobil) düzeni. Öğeler sabit bir kayıttan gelir; admin
|
||||
// panelinden yalnızca SIRA, GÖRÜNÜRLÜK ve (opsiyonel) ETİKET düzenlenir.
|
||||
// Bu modül hem sunucu (header) hem istemci (admin formu) tarafında import
|
||||
// edilebilir — bu yüzden "server-only" YOK.
|
||||
|
||||
export type NavKey =
|
||||
| "home"
|
||||
| "services"
|
||||
| "solutions"
|
||||
| "projects"
|
||||
| "blog"
|
||||
| "about"
|
||||
| "contact";
|
||||
|
||||
export interface NavRegistryEntry {
|
||||
key: NavKey;
|
||||
label: string; // varsayılan etiket
|
||||
href: string;
|
||||
mega?: boolean; // Hizmetler — mega menü olarak render edilir
|
||||
}
|
||||
|
||||
export const NAV_REGISTRY: Record<NavKey, NavRegistryEntry> = {
|
||||
home: { key: "home", label: "Anasayfa", href: "/" },
|
||||
services: { key: "services", label: "Hizmetler", href: "/hizmetler", mega: true },
|
||||
solutions: { key: "solutions", label: "Çözümler", href: "/cozumler" },
|
||||
projects: { key: "projects", label: "Projeler", href: "/projeler" },
|
||||
blog: { key: "blog", label: "Blog", href: "/blog" },
|
||||
about: { key: "about", label: "Hakkımızda", href: "/hakkimizda" },
|
||||
contact: { key: "contact", label: "İletişim", href: "/iletisim" },
|
||||
};
|
||||
|
||||
export const DEFAULT_NAV_ORDER: NavKey[] = [
|
||||
"home",
|
||||
"services",
|
||||
"solutions",
|
||||
"projects",
|
||||
"blog",
|
||||
"about",
|
||||
"contact",
|
||||
];
|
||||
|
||||
export interface NavItem {
|
||||
key: NavKey;
|
||||
label: string; // çözülmüş etiket (override veya varsayılan)
|
||||
href: string;
|
||||
mega: boolean;
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
interface StoredNavItem {
|
||||
key: string;
|
||||
visible?: boolean;
|
||||
label?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* site_settings.nav_items içindeki JSON'ı kayıt ile birleştirir.
|
||||
* - Kayıtlı sıra önceliklidir, geçersiz/silinmiş key'ler atlanır.
|
||||
* - Kayıtta olmayan (yeni eklenen) öğeler varsayılan sırayla sona eklenir.
|
||||
* - JSON yoksa/bozuksa tam varsayılan menü döner.
|
||||
*/
|
||||
export function resolveNavItems(navItemsJson?: string | null): NavItem[] {
|
||||
let stored: StoredNavItem[] = [];
|
||||
if (navItemsJson) {
|
||||
try {
|
||||
const parsed = JSON.parse(navItemsJson);
|
||||
if (Array.isArray(parsed)) stored = parsed as StoredNavItem[];
|
||||
} catch {
|
||||
/* bozuk JSON — varsayılanlara düş */
|
||||
}
|
||||
}
|
||||
|
||||
const seen = new Set<NavKey>();
|
||||
const ordered: NavItem[] = [];
|
||||
|
||||
for (const item of stored) {
|
||||
const reg = NAV_REGISTRY[item.key as NavKey];
|
||||
if (!reg || seen.has(reg.key)) continue;
|
||||
seen.add(reg.key);
|
||||
ordered.push({
|
||||
key: reg.key,
|
||||
label: item.label?.trim() || reg.label,
|
||||
href: reg.href,
|
||||
mega: !!reg.mega,
|
||||
visible: item.visible !== false,
|
||||
});
|
||||
}
|
||||
|
||||
for (const key of DEFAULT_NAV_ORDER) {
|
||||
if (seen.has(key)) continue;
|
||||
const reg = NAV_REGISTRY[key];
|
||||
ordered.push({
|
||||
key: reg.key,
|
||||
label: reg.label,
|
||||
href: reg.href,
|
||||
mega: !!reg.mega,
|
||||
visible: true,
|
||||
});
|
||||
}
|
||||
|
||||
return ordered;
|
||||
}
|
||||
|
||||
/** Admin formundan gelen düzeni depolanacak kompakt JSON'a çevirir. */
|
||||
export function serializeNavItems(
|
||||
items: { key: NavKey; visible: boolean; label?: string }[],
|
||||
): string {
|
||||
return JSON.stringify(
|
||||
items.map((i) => {
|
||||
const reg = NAV_REGISTRY[i.key];
|
||||
const out: StoredNavItem = { key: i.key, visible: i.visible };
|
||||
// Sadece varsayılandan farklıysa etiketi sakla
|
||||
if (i.label && i.label.trim() && i.label.trim() !== reg.label) {
|
||||
out.label = i.label.trim();
|
||||
}
|
||||
return out;
|
||||
}),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user