feat: add full SEO (generateMetadata, OG, Twitter Card, JSON-LD, sitemap, robots, llms.txt)
This commit is contained in:
+136
-10
@@ -1,7 +1,7 @@
|
||||
import { prisma } from '@/lib/db';
|
||||
import { getSession } from '@/lib/auth';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { Save, Upload } from 'lucide-react';
|
||||
import { redirect } from 'next/navigation'
|
||||
import { Save } from 'lucide-react';
|
||||
import { updateSiteSettings } from '../actions';
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { uploadImage } from '@/lib/cloudinary';
|
||||
@@ -32,6 +32,16 @@ export default async function SettingsAdminPage() {
|
||||
facebookUrl: formData.get('facebookUrl') as string,
|
||||
youtubeUrl: formData.get('youtubeUrl') as string,
|
||||
whatsappNumber: formData.get('whatsappNumber') as string,
|
||||
// SEO fields
|
||||
siteUrl: formData.get('siteUrl') as string,
|
||||
metaTitleTr: formData.get('metaTitleTr') as string,
|
||||
metaTitleEn: formData.get('metaTitleEn') as string,
|
||||
metaDescTr: formData.get('metaDescTr') as string,
|
||||
metaDescEn: formData.get('metaDescEn') as string,
|
||||
metaKeywordsTr: formData.get('metaKeywordsTr') as string,
|
||||
metaKeywordsEn: formData.get('metaKeywordsEn') as string,
|
||||
twitterHandle: formData.get('twitterHandle') as string,
|
||||
googleVerify: formData.get('googleVerify') as string,
|
||||
};
|
||||
|
||||
const logoFile = formData.get('logo') as File;
|
||||
@@ -39,19 +49,22 @@ export default async function SettingsAdminPage() {
|
||||
data.logoUrl = await uploadImage(logoFile);
|
||||
}
|
||||
|
||||
const ogImageFile = formData.get('ogImage') as File;
|
||||
if (ogImageFile && ogImageFile.size > 0) {
|
||||
data.ogImage = await uploadImage(ogImageFile);
|
||||
}
|
||||
|
||||
await updateSiteSettings(data);
|
||||
revalidatePath('/', 'layout');
|
||||
redirect('/admin/settings');
|
||||
}} className="space-y-8">
|
||||
}} className="space-y-10">
|
||||
|
||||
{/* Logo Section */}
|
||||
<section>
|
||||
<h2 className="text-xl font-bold mb-4 border-b pb-2">Logo Ayarları</h2>
|
||||
<h2 className="text-xl font-bold mb-4 border-b border-slate-200 dark:border-slate-700 pb-2">Logo Ayarları</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
|
||||
Yeni Logo Yükle
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">Yeni Logo Yükle</label>
|
||||
<input
|
||||
type="file"
|
||||
name="logo"
|
||||
@@ -73,7 +86,7 @@ export default async function SettingsAdminPage() {
|
||||
|
||||
{/* Contact Section */}
|
||||
<section>
|
||||
<h2 className="text-xl font-bold mb-4 border-b pb-2">İletişim Bilgileri</h2>
|
||||
<h2 className="text-xl font-bold mb-4 border-b border-slate-200 dark:border-slate-700 pb-2">İletişim Bilgileri</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">Telefon</label>
|
||||
@@ -89,7 +102,7 @@ export default async function SettingsAdminPage() {
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">WhatsApp Numarası</label>
|
||||
<input type="text" name="whatsappNumber" defaultValue={settings?.whatsappNumber || ""} className="w-full px-4 py-2 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-blue-500" placeholder="905344656235 (Sadece rakam, + olmadan)" />
|
||||
<input type="text" name="whatsappNumber" defaultValue={settings?.whatsappNumber || ""} className="w-full px-4 py-2 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-blue-500" placeholder="905344656235" />
|
||||
</div>
|
||||
<div className="md:col-span-2">
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">Adres Satırı 1</label>
|
||||
@@ -104,7 +117,7 @@ export default async function SettingsAdminPage() {
|
||||
|
||||
{/* Social Media */}
|
||||
<section>
|
||||
<h2 className="text-xl font-bold mb-4 border-b pb-2">Sosyal Medya Linkleri</h2>
|
||||
<h2 className="text-xl font-bold mb-4 border-b border-slate-200 dark:border-slate-700 pb-2">Sosyal Medya Linkleri</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">Instagram URL</label>
|
||||
@@ -121,6 +134,119 @@ export default async function SettingsAdminPage() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* SEO Section */}
|
||||
<section>
|
||||
<h2 className="text-xl font-bold mb-1 border-b border-slate-200 dark:border-slate-700 pb-2 flex items-center gap-2">
|
||||
🔍 SEO & Metadata Ayarları
|
||||
</h2>
|
||||
<p className="text-sm text-slate-500 dark:text-slate-400 mb-6">
|
||||
Arama motorları ve sosyal medya paylaşımları için meta bilgileri.
|
||||
</p>
|
||||
|
||||
{/* Site URL */}
|
||||
<div className="mb-6">
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
|
||||
Site URL <span className="text-slate-400 font-normal">(canonical & sitemap için)</span>
|
||||
</label>
|
||||
<input type="url" name="siteUrl" defaultValue={settings?.siteUrl || ""} className="w-full px-4 py-2 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-blue-500" placeholder="https://moybeach.com" />
|
||||
</div>
|
||||
|
||||
{/* Meta Titles */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
|
||||
Meta Başlık — Türkçe
|
||||
</label>
|
||||
<input type="text" name="metaTitleTr" defaultValue={settings?.metaTitleTr || ""} maxLength={60} className="w-full px-4 py-2 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-blue-500" placeholder="Moy Beach Akyaka | Lüks & Huzur" />
|
||||
<p className="text-xs text-slate-400 mt-1">Max 60 karakter önerilir</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
|
||||
Meta Başlık — İngilizce
|
||||
</label>
|
||||
<input type="text" name="metaTitleEn" defaultValue={settings?.metaTitleEn || ""} maxLength={60} className="w-full px-4 py-2 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-blue-500" placeholder="Moy Beach Akyaka | Luxury & Serenity" />
|
||||
<p className="text-xs text-slate-400 mt-1">Max 60 characters recommended</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Meta Descriptions */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
|
||||
Meta Açıklama — Türkçe
|
||||
</label>
|
||||
<textarea name="metaDescTr" rows={3} defaultValue={settings?.metaDescTr || ""} maxLength={160} className="w-full px-4 py-2 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-blue-500 resize-none" placeholder="Akyaka'nın kalbinde doğayla iç içe lüks bir beach, restoran ve otel deneyimi." />
|
||||
<p className="text-xs text-slate-400 mt-1">Max 160 karakter önerilir</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
|
||||
Meta Açıklama — İngilizce
|
||||
</label>
|
||||
<textarea name="metaDescEn" rows={3} defaultValue={settings?.metaDescEn || ""} maxLength={160} className="w-full px-4 py-2 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-blue-500 resize-none" placeholder="A luxury beach, restaurant and hotel experience in the heart of Akyaka." />
|
||||
<p className="text-xs text-slate-400 mt-1">Max 160 characters recommended</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Keywords */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
|
||||
Anahtar Kelimeler — Türkçe
|
||||
</label>
|
||||
<input type="text" name="metaKeywordsTr" defaultValue={settings?.metaKeywordsTr || ""} className="w-full px-4 py-2 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-blue-500" placeholder="moy beach, akyaka, beach, restoran, otel, muğla" />
|
||||
<p className="text-xs text-slate-400 mt-1">Virgülle ayırın</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
|
||||
Keywords — English
|
||||
</label>
|
||||
<input type="text" name="metaKeywordsEn" defaultValue={settings?.metaKeywordsEn || ""} className="w-full px-4 py-2 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-blue-500" placeholder="moy beach, akyaka, beach, restaurant, hotel, mugla" />
|
||||
<p className="text-xs text-slate-400 mt-1">Separate with commas</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* OG Image */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
|
||||
OG / Paylaşım Görseli
|
||||
<span className="text-slate-400 font-normal ml-1">(1200×630px önerilir)</span>
|
||||
</label>
|
||||
<input type="file" name="ogImage" accept="image/*" className="w-full px-4 py-2 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-blue-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100" />
|
||||
</div>
|
||||
{settings?.ogImage && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">Mevcut OG Görseli</label>
|
||||
<div className="rounded-lg overflow-hidden border border-slate-200 dark:border-slate-700 w-full max-w-xs">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img src={settings.ogImage} alt="OG Image" className="w-full h-auto object-cover" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Twitter & Google Verify */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
|
||||
Twitter/X Handle
|
||||
<span className="text-slate-400 font-normal ml-1">(@işareti olmadan)</span>
|
||||
</label>
|
||||
<div className="relative">
|
||||
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 font-medium">@</span>
|
||||
<input type="text" name="twitterHandle" defaultValue={settings?.twitterHandle || ""} className="w-full pl-8 pr-4 py-2 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-blue-500" placeholder="moybeachakyaka" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
|
||||
Google Search Console Doğrulama Kodu
|
||||
</label>
|
||||
<input type="text" name="googleVerify" defaultValue={settings?.googleVerify || ""} className="w-full px-4 py-2 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-blue-500" placeholder="google-site-verification kodu" />
|
||||
<p className="text-xs text-slate-400 mt-1">Google Search Console → Doğrulama → HTML Meta Tag'deki content değeri</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="pt-4 flex justify-end">
|
||||
<button type="submit" className="flex items-center space-x-2 bg-blue-600 hover:bg-blue-700 text-white px-8 py-3 rounded-lg transition-colors font-medium">
|
||||
<Save className="w-5 h-5" />
|
||||
|
||||
Reference in New Issue
Block a user