feat: TR reklam trafiği için anasayfa CRO optimizasyonu

Yeni bölümler ve component'ler:
- WhatsAppFloat: sağ altta her sayfada görünen 'pulse' animasyonlu WhatsApp butonu
- MobileCtaBar: mobilde alt sabit bar — Ara / WhatsApp / Teklif Al üç buton
- TrustBand: hero altı 4 trust kartı (Google ★, proje sayısı, dönüş süresi, garanti)
  + Google rating + yorum sayısı satırı
- LogoCloud: müşteri logoları grayscale strip
- QuickLeadForm: ad + telefon iki alanlı inline mini form (anasayfada)
  - app/actions submitContact 'source' alanını destekliyor (quick lead → message zorunlu değil)
- WhyUs: 4 USP kartı (Hızlı teslim, Yerel destek, Modern tech, Satış sonrası)
- ProcessSteps: 4 adımlı 'nasıl çalışıyoruz' süreç akışı (numaralı timeline)

Schema (JSON-LD):
- OrganizationLd: LocalBusiness + Address + AggregateRating (Google review puanı/sayısı)
- ServiceLd: hizmet detay sayfaları için
- FaqLd: hizmet FAQ'leri için
- BreadcrumbLd, ArticleLd: hazır
Anasayfaya OrganizationLd ekli — Google Ads quality score + organic rich results.

Performans:
- REST GET çağrıları cache:'no-store' yerine next.revalidate=60 (ISR)
- Public sayfalar artık static rendering — LCP düşer
- Mutations ve session GET'ler hâlâ no-store

site_settings yeni alanları (panelden yönetilebilir):
- whatsapp_message (default WhatsApp opener)
- client_logos[] (logo URL listesi)
- trust_items[] (JSON: icon|value|label)
- why_us[] (JSON: icon, title, description)
- process_steps[] (JSON: title, description)
- lead_form_title, lead_form_description
- google_rating, google_review_count, google_review_url

Admin /admin/site formuna yeni 'Conversion / reklam optimizasyonu',
'Neden Biz?' ve 'Süreç adımları' bölümleri eklendi.

Mevcut anasayfa yapısı (üstten alta):
1. Hero
2. TrustBand (mini güven sinyalleri)
3. LogoCloud (varsa müşteri logoları)
4. Hızlı iletişim + QuickLeadForm (2 sütun: tel/WA CTA + mini form)
5. Hizmetler
6. WhyUs (Neden Biz?)
7. ProcessSteps (Nasıl çalışıyoruz?)
8. Projeler
9. Testimonials
10. CTA (Final + WhatsApp)
This commit is contained in:
Ege Can Komur
2026-05-20 03:08:05 +03:00
parent 1444aa3995
commit aa2b7280b6
16 changed files with 1024 additions and 23 deletions
+62
View File
@@ -297,6 +297,57 @@ export async function saveSiteSettings(formData: FormData) {
})
.filter((x): x is string => x !== null);
// Trust items: "icon|value|label" satırlar
const trustRaw = String(formData.get("trust_items") ?? "");
const trust = trustRaw
.split("\n")
.map((line) => {
const [icon, value, label] = line.split("|").map((s) => s.trim());
if (!value || !label) return null;
return JSON.stringify({ icon: icon || "Sparkles", value, label });
})
.filter((x): x is string => x !== null);
// Why us / Process: blok '---' ile ayrılır; her blok için:
// why_us: ilk satır = icon|title, kalanı description
// process: ilk satır = title, kalanı description
function parseBlocks(raw: string, withIcon: boolean): string[] {
return raw
.split("\n---\n")
.map((block) => {
const lines = block.trim().split("\n");
if (lines.length < 2) return null;
if (withIcon) {
const [icon, title] = lines[0].split("|").map((s) => s.trim());
const description = lines.slice(1).join("\n").trim();
if (!title || !description) return null;
return JSON.stringify({
icon: icon || "Sparkles",
title,
description,
});
}
const title = lines[0].trim();
const description = lines.slice(1).join("\n").trim();
if (!title || !description) return null;
return JSON.stringify({ title, description });
})
.filter((x): x is string => x !== null);
}
const whyUs = parseBlocks(String(formData.get("why_us") ?? ""), true);
const processSteps = parseBlocks(
String(formData.get("process_steps") ?? ""),
false,
);
// Client logos: her satıra bir URL
const logosRaw = String(formData.get("client_logos") ?? "");
const logos = logosRaw
.split("\n")
.map((s) => s.trim())
.filter(Boolean);
const data = {
hero_badge: str(formData.get("hero_badge")),
hero_title: str(formData.get("hero_title")),
@@ -337,6 +388,17 @@ export async function saveSiteSettings(formData: FormData) {
social_facebook: str(formData.get("social_facebook")),
footer_tagline: str(formData.get("footer_tagline")),
whatsapp_message: str(formData.get("whatsapp_message")),
client_logos: logos.length > 0 ? logos : null,
trust_items: trust.length > 0 ? trust : null,
why_us: whyUs.length > 0 ? whyUs : null,
process_steps: processSteps.length > 0 ? processSteps : null,
lead_form_title: str(formData.get("lead_form_title")),
lead_form_description: str(formData.get("lead_form_description")),
google_review_url: str(formData.get("google_review_url")),
google_rating: num(formData.get("google_rating")),
google_review_count: num(formData.get("google_review_count")),
};
try {
+13 -4
View File
@@ -67,12 +67,21 @@ async function awRaw(path: string, opts: FetchOpts = {}): Promise<Response> {
body = JSON.stringify(opts.body);
}
const res = await fetch(url, {
method: opts.method ?? "GET",
// GET requests are cached (ISR-style) with 60s revalidate so public pages
// stay fast for ad traffic; mutations and session calls bypass cache.
const method = opts.method ?? "GET";
const isAuthenticated = !!opts.session;
const fetchInit: RequestInit & { next?: { revalidate?: number; tags?: string[] } } = {
method,
headers,
body,
cache: "no-store",
});
};
if (method === "GET" && !isAuthenticated) {
fetchInit.next = { revalidate: 60 };
} else {
fetchInit.cache = "no-store";
}
const res = await fetch(url, fetchInit);
if (!res.ok) {
let data: { message?: string; type?: string } = {};
+28
View File
@@ -131,6 +131,34 @@ export interface SiteSettingsRow extends AwRow {
social_facebook?: string | null;
footer_tagline?: string | null;
whatsapp_message?: string | null;
client_logos?: string[] | null;
trust_items?: string[] | null; // JSON {"icon":"Star","value":"4.9","label":"..."}
why_us?: string[] | null; // JSON {"icon":"Zap","title":"...","description":"..."}
process_steps?: string[] | null; // JSON {"title":"...","description":"..."}
lead_form_title?: string | null;
lead_form_description?: string | null;
google_review_url?: string | null;
google_rating?: number | null;
google_review_count?: number | null;
}
export interface TrustItem {
icon: string;
value: string;
label: string;
}
export interface WhyUsItem {
icon: string;
title: string;
description: string;
}
export interface ProcessStep {
title: string;
description: string;
}
export interface ContactMessageRow extends AwRow {