Files
kovakyazilim/app/admin/(protected)/site/page.tsx
T
Ege Can Komur cf46e30a7e feat: TR rakip analizi → satış blokerleri düzeltildi
Rakip analizi (kocaelidijital.com, promedyanet.com, lf.com.tr) sonrası
satış blokerleri tespit edildi ve aşağıdaki bölümler eklendi:

1. ANASAYFADA SSS (8 hazır soru, admin'den düzenlenebilir)
   - Fiyat, süre, ödeme, garanti, hosting, SEO, mevcut site yenileme,
     sadece tasarım hizmeti gibi en sık sorulan sorular
   - HomepageFaq component (sticky sol + accordion sağ)
   - site_settings.homepage_faq[] (JSON {q,a})

2. RISK REVERSAL bölümü (Guarantee component)
   - 'İlk taslak ücretsiz, memnun değilseniz devam etmiyoruz'
   - 4 garanti maddesi checklist
   - site_settings.guarantee_title/description/items

3. PROJE METRİKLERİ (vaka çalışması güçlendirme)
   - projects.metrics[] (JSON {value,label})
   - Detay sayfada büyük metric kartları
   - Admin formda 'değer | etiket' satır formatı

4. HERO COPY GÜNCELLEMESİ (admin'den düzenlenebilir)
   - 'Kocaeli'de 2-3 hafta içinde yayında olan, satan kurumsal web siteleri'
   - 'İlk tasarım taslakı ücretsiz' vurgusu
   - Trust band: 30 dk yanıt + ücretsiz taslak + 4.9 memnuniyet

5. /SITE-ANALIZI LEAD MAGNET SAYFASI
   - URL + ad + email + telefon formu
   - 6 analiz başlığı (CWV, mobil, SEO, güvenlik, içerik, rakip)
   - contact_messages'a source=quick-site-audit ile yazılır
   - 'subject' alanı ile inbox'ta ayırt edilebilir

6. EKİP BÖLÜMÜ (Hakkımızda sayfasında)
   - Yeni team_members tablosu (name, role, bio, photo, linkedin)
   - /admin/ekip CRUD sayfası
   - TeamGrid component

7. SEKTÖR LANDING SAYFALARI (/sektor/[slug])
   - Yeni industries tablosu (slug, title, content, features, faq, SEO)
   - /admin/sektorler CRUD sayfası
   - SEO + ad-targeted landing template
   - Hero + trust + features + content + garanti + projeler + hizmetler + FAQ + JSON-LD

Admin /admin/site formuna yeni bölümler:
- 'Risk reversal / Garanti' (title + description + items)
- 'Anasayfa SSS' (---' bloklarla)

App sidebar'a 'Sektörler' ve 'Ekip' linkleri eklendi.
Footer'a 'Ücretsiz Site Analizi' linki eklendi.

36 route üretiliyor (önceki 31'den +5: /site-analizi, /sektor/[slug],
/admin/ekip + alt, /admin/sektorler + alt).
2026-05-20 04:03:21 +03:00

529 lines
18 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 type { Metadata } from "next";
import { Save } from "lucide-react";
import {
Field,
FormActions,
FormShell,
PageHeader,
PrimaryButton,
Textarea,
} from "@/components/admin/form";
import { getSiteSettings } from "@/lib/data";
import { saveSiteSettings } from "@/lib/admin-actions";
import type {
FaqItem,
ProcessStep,
StatItem,
TrustItem,
WhyUsItem,
} from "@/lib/types";
export const metadata: Metadata = { title: "Site Ayarları" };
function statsToText(items?: string[] | null): string {
if (!items) return "";
const parsed: StatItem[] = [];
for (const raw of items) {
try {
const obj = JSON.parse(raw) as Partial<StatItem>;
if (obj.value && obj.label) parsed.push({ value: obj.value, label: obj.label });
} catch {
/* ignore */
}
}
return parsed.map((s) => `${s.value} | ${s.label}`).join("\n");
}
function trustToText(items?: string[] | null): string {
if (!items) return "";
const parsed: TrustItem[] = [];
for (const raw of items) {
try {
const obj = JSON.parse(raw) as Partial<TrustItem>;
if (obj.value && obj.label)
parsed.push({
icon: obj.icon ?? "Sparkles",
value: obj.value,
label: obj.label,
});
} catch {
/* ignore */
}
}
return parsed.map((t) => `${t.icon} | ${t.value} | ${t.label}`).join("\n");
}
function whyUsToText(items?: string[] | null): string {
if (!items) return "";
const parsed: WhyUsItem[] = [];
for (const raw of items) {
try {
const obj = JSON.parse(raw) as Partial<WhyUsItem>;
if (obj.title && obj.description)
parsed.push({
icon: obj.icon ?? "Sparkles",
title: obj.title,
description: obj.description,
});
} catch {
/* ignore */
}
}
return parsed
.map((w) => `${w.icon} | ${w.title}\n${w.description}`)
.join("\n---\n");
}
function processToText(items?: string[] | null): string {
if (!items) return "";
const parsed: ProcessStep[] = [];
for (const raw of items) {
try {
const obj = JSON.parse(raw) as Partial<ProcessStep>;
if (obj.title && obj.description)
parsed.push({ title: obj.title, description: obj.description });
} catch {
/* ignore */
}
}
return parsed.map((p) => `${p.title}\n${p.description}`).join("\n---\n");
}
function faqToText(items?: string[] | null): string {
if (!items) return "";
const parsed: FaqItem[] = [];
for (const raw of items) {
try {
const obj = JSON.parse(raw) as Partial<FaqItem>;
if (obj.q && obj.a) parsed.push({ q: obj.q, a: obj.a });
} catch {
/* ignore */
}
}
return parsed.map((it) => `${it.q}\n${it.a}`).join("\n---\n");
}
function Section({
title,
description,
children,
}: {
title: string;
description?: string;
children: React.ReactNode;
}) {
return (
<section className="border-t border-[var(--border)] pt-8 first:border-0 first:pt-0">
<h2 className="text-base font-semibold text-[var(--navy)]">{title}</h2>
{description && (
<p className="mt-1 text-xs text-[var(--muted)]">{description}</p>
)}
<div className="mt-5 space-y-5">{children}</div>
</section>
);
}
export default async function SiteSettingsPage() {
const s = await getSiteSettings();
return (
<div>
<PageHeader
title="Site Ayarları"
description="Anasayfa içeriği, iletişim bilgileri ve sosyal medya — tek yerden yönet."
/>
<form action={saveSiteSettings}>
<FormShell>
<div className="space-y-10">
<Section
title="Hero (anasayfa üst alan)"
description="İlk açılışta görünen ana başlık ve buton metinleri."
>
<Field
label="Rozet metni"
name="hero_badge"
defaultValue={s?.hero_badge}
placeholder="Kocaeli'nin teknoloji ajansı"
/>
<Field
label="Ana başlık"
name="hero_title"
defaultValue={s?.hero_title}
/>
<Textarea
label="Alt metin"
name="hero_subtitle"
rows={2}
defaultValue={s?.hero_subtitle}
/>
<div className="grid gap-5 md:grid-cols-2">
<Field
label="Birincil buton metni"
name="hero_cta_primary_label"
defaultValue={s?.hero_cta_primary_label}
/>
<Field
label="Birincil buton URL"
name="hero_cta_primary_href"
defaultValue={s?.hero_cta_primary_href}
placeholder="/iletisim"
/>
<Field
label="İkincil buton metni"
name="hero_cta_secondary_label"
defaultValue={s?.hero_cta_secondary_label}
/>
<Field
label="İkincil buton URL"
name="hero_cta_secondary_href"
defaultValue={s?.hero_cta_secondary_href}
placeholder="/hizmetler"
/>
</div>
<Textarea
label="İstatistikler"
name="hero_stats"
rows={3}
defaultValue={statsToText(s?.hero_stats)}
placeholder={"50+ | Tamamlanan proje\n10+ | Yıllık deneyim\n24/7 | Teknik destek"}
help='Her satıra "değer | etiket" formatında.'
/>
</Section>
<Section
title="Hizmetler bölümü başlığı"
description="Anasayfadaki hizmet kartlarının üstündeki yazı."
>
<div className="grid gap-5 md:grid-cols-3">
<Field
label="Eyebrow"
name="services_eyebrow"
defaultValue={s?.services_eyebrow}
/>
<Field
label="Başlık"
name="services_title"
defaultValue={s?.services_title}
/>
<Field
label="Açıklama"
name="services_description"
defaultValue={s?.services_description}
/>
</div>
</Section>
<Section
title="Projeler bölümü başlığı"
description="Anasayfadaki proje kartlarının üstündeki yazı."
>
<div className="grid gap-5 md:grid-cols-3">
<Field
label="Eyebrow"
name="projects_eyebrow"
defaultValue={s?.projects_eyebrow}
/>
<Field
label="Başlık"
name="projects_title"
defaultValue={s?.projects_title}
/>
<Field
label="Açıklama"
name="projects_description"
defaultValue={s?.projects_description}
/>
</div>
</Section>
<Section
title="Referanslar bölümü başlığı"
description="Anasayfadaki yorumların üstündeki yazı."
>
<div className="grid gap-5 md:grid-cols-3">
<Field
label="Eyebrow"
name="testimonials_eyebrow"
defaultValue={s?.testimonials_eyebrow}
/>
<Field
label="Başlık"
name="testimonials_title"
defaultValue={s?.testimonials_title}
/>
<Field
label="Açıklama"
name="testimonials_description"
defaultValue={s?.testimonials_description}
/>
</div>
</Section>
<Section
title="Alt CTA bölümü"
description="Anasayfanın altındaki büyük çağrı bölümü."
>
<Field
label="Başlık"
name="cta_title"
defaultValue={s?.cta_title}
/>
<Textarea
label="Açıklama"
name="cta_description"
rows={2}
defaultValue={s?.cta_description}
/>
<div className="grid gap-5 md:grid-cols-2">
<Field
label="Buton metni"
name="cta_button_label"
defaultValue={s?.cta_button_label}
/>
<Field
label="Buton URL"
name="cta_button_href"
defaultValue={s?.cta_button_href}
/>
</div>
</Section>
<Section
title="İletişim bilgileri"
description="Footer ve iletişim sayfasında gösterilir."
>
<div className="grid gap-5 md:grid-cols-2">
<Field
label="Telefon (görünen)"
name="contact_phone"
defaultValue={s?.contact_phone}
placeholder="+90 551 590 29 35"
/>
<Field
label="Telefon (ham — tel: linki için)"
name="contact_phone_raw"
defaultValue={s?.contact_phone_raw}
placeholder="+905515902935"
/>
<Field
label="E-posta"
name="contact_email"
type="email"
defaultValue={s?.contact_email}
/>
<Field
label="Çalışma saatleri (hafta içi)"
name="contact_hours_weekday"
defaultValue={s?.contact_hours_weekday}
/>
<Field
label="Çalışma saatleri (hafta sonu)"
name="contact_hours_weekend"
defaultValue={s?.contact_hours_weekend}
/>
</div>
<Textarea
label="Adres"
name="contact_address"
rows={2}
defaultValue={s?.contact_address}
/>
</Section>
<Section title="Sosyal medya">
<div className="grid gap-5 md:grid-cols-2">
<Field
label="LinkedIn URL"
name="social_linkedin"
type="url"
defaultValue={s?.social_linkedin}
/>
<Field
label="Instagram URL"
name="social_instagram"
type="url"
defaultValue={s?.social_instagram}
/>
<Field
label="Twitter / X URL"
name="social_twitter"
type="url"
defaultValue={s?.social_twitter}
/>
<Field
label="Facebook URL"
name="social_facebook"
type="url"
defaultValue={s?.social_facebook}
/>
</div>
</Section>
<Section title="Footer">
<Textarea
label="Footer tagline"
name="footer_tagline"
rows={2}
defaultValue={s?.footer_tagline}
help="Logo altındaki kısa açıklama metni."
/>
</Section>
<Section
title="Conversion / reklam optimizasyonu"
description="Trust bandı, mini lead form ve WhatsApp metni."
>
<div className="grid gap-5 md:grid-cols-2">
<Field
label="Lead form başlığı"
name="lead_form_title"
defaultValue={s?.lead_form_title}
placeholder="Ücretsiz teklif alın"
/>
<Field
label="Lead form açıklaması"
name="lead_form_description"
defaultValue={s?.lead_form_description}
/>
</div>
<Textarea
label="WhatsApp varsayılan mesajı"
name="whatsapp_message"
rows={2}
defaultValue={s?.whatsapp_message}
placeholder="Merhaba, web siteniz üzerinden ulaşıyorum…"
help="Kullanıcı WhatsApp butonuna tıkladığında otomatik açılan mesaj."
/>
<Textarea
label="Trust bandı (hero altı 4 kart)"
name="trust_items"
rows={4}
defaultValue={trustToText(s?.trust_items)}
placeholder={
"Star | 4.9 | Google yıldızı\nBriefcase | 50+ | Tamamlanan proje\nClock | 24 saat | İçinde dönüş\nShield | 100% | Memnuniyet garantisi"
}
help='Her satır: "İkonAdı | Değer | Etiket" (örn. Star | 4.9 | Google yıldızı)'
/>
<Textarea
label="Müşteri logoları"
name="client_logos"
rows={4}
defaultValue={s?.client_logos?.join("\n")}
placeholder={"https://example.com/logo1.png\nhttps://example.com/logo2.png"}
help="Her satıra bir URL. Logoların grayscale + opaque versiyonu gösterilir."
/>
<div className="grid gap-5 md:grid-cols-3">
<Field
label="Google Rating (0-5)"
name="google_rating"
type="number"
defaultValue={s?.google_rating ?? ""}
placeholder="4.9"
/>
<Field
label="Yorum sayısı"
name="google_review_count"
type="number"
defaultValue={s?.google_review_count ?? ""}
placeholder="47"
/>
<Field
label="Google review URL"
name="google_review_url"
type="url"
defaultValue={s?.google_review_url}
placeholder="https://g.page/r/..."
/>
</div>
</Section>
<Section
title="Neden Biz? kartları"
description="Anasayfada görünen 4 USP kartı."
>
<Textarea
label="Neden Biz?"
name="why_us"
rows={12}
defaultValue={whyUsToText(s?.why_us)}
placeholder={
"Zap | Hızlı teslim\n2-3 hafta içinde…\n---\nAward | Yerel destek\nKocaeli ofisimizde…"
}
help='Her blok "---" ile ayrılır. İlk satır: "İkon | Başlık". Sonraki satırlar: açıklama.'
/>
</Section>
<Section
title="Nasıl çalışıyoruz? adımları"
description="4 adımlı süreç akışı."
>
<Textarea
label="Süreç adımları"
name="process_steps"
rows={10}
defaultValue={processToText(s?.process_steps)}
placeholder={
"Ücretsiz keşif görüşmesi\n30 dakika dinleme.\n---\nTeklif ve plan\nYazılı teklif sunuyoruz."
}
help='Her blok "---" ile ayrılır. İlk satır başlık, sonrası açıklama.'
/>
</Section>
<Section
title="Risk reversal / Garanti bölümü"
description="Anasayfada güven yaratan büyük garanti satırı."
>
<Field
label="Garanti başlığı"
name="guarantee_title"
defaultValue={s?.guarantee_title}
placeholder="İlk taslak ücretsiz, memnun değilseniz devam etmiyoruz"
/>
<Textarea
label="Garanti açıklaması"
name="guarantee_description"
rows={3}
defaultValue={s?.guarantee_description}
/>
<Textarea
label="Garanti maddeleri"
name="guarantee_items"
rows={5}
defaultValue={s?.guarantee_items?.join("\n")}
placeholder={"İlk tasarım taslağı ücretsiz\n1 yıl ücretsiz teknik destek\nKaynak kodlar size aittir"}
help="Her satır bir madde. Checklist olarak gösterilir."
/>
</Section>
<Section
title="Anasayfa SSS"
description="Reklam trafiği için kritik — fiyat, süre, ödeme gibi en sık soruları yanıtlar."
>
<Textarea
label="SSS"
name="homepage_faq"
rows={20}
defaultValue={faqToText(s?.homepage_faq)}
placeholder={
"Bir web sitesi ne kadar sürer?\n2-3 hafta…\n---\nFiyatlar ne kadar?\n15.000₺'den başlar…"
}
help='Her blok "---" ile ayrılır. İlk satır soru, kalanı cevap.'
/>
</Section>
</div>
<FormActions>
<PrimaryButton>
<Save className="size-4" /> Tüm değişiklikleri kaydet
</PrimaryButton>
</FormActions>
</FormShell>
</form>
</div>
);
}