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).
This commit is contained in:
@@ -180,6 +180,17 @@ export async function deleteService(formData: FormData) {
|
||||
|
||||
// ─── Projects ────────────────────────────────────────────────────
|
||||
|
||||
function parseMetricsInput(raw: string): string[] {
|
||||
return raw
|
||||
.split("\n")
|
||||
.map((line) => {
|
||||
const [value, label] = line.split("|").map((s) => s.trim());
|
||||
if (!value || !label) return null;
|
||||
return JSON.stringify({ value, label });
|
||||
})
|
||||
.filter((x): x is string => x !== null);
|
||||
}
|
||||
|
||||
export async function saveProject(formData: FormData) {
|
||||
const secret = await requireSessionSecret();
|
||||
const id = str(formData.get("id"));
|
||||
@@ -212,6 +223,10 @@ export async function saveProject(formData: FormData) {
|
||||
industry: str(formData.get("industry")),
|
||||
duration: str(formData.get("duration")),
|
||||
service_slug: str(formData.get("service_slug")),
|
||||
metrics: (() => {
|
||||
const m = parseMetricsInput(String(formData.get("metrics") ?? ""));
|
||||
return m.length > 0 ? m : null;
|
||||
})(),
|
||||
};
|
||||
|
||||
if (id) {
|
||||
@@ -497,6 +512,97 @@ export async function deleteSeoPage(formData: FormData) {
|
||||
revalidatePath("/admin/seo");
|
||||
}
|
||||
|
||||
// ─── Team Members ────────────────────────────────────────────────
|
||||
|
||||
export async function saveTeamMember(formData: FormData) {
|
||||
const secret = await requireSessionSecret();
|
||||
const id = str(formData.get("id"));
|
||||
const name = str(formData.get("name"));
|
||||
if (!name) throw new Error("İsim zorunlu");
|
||||
|
||||
const data = {
|
||||
name,
|
||||
role: str(formData.get("role")),
|
||||
bio: str(formData.get("bio")),
|
||||
photo_url: str(formData.get("photo_url")),
|
||||
linkedin_url: str(formData.get("linkedin_url")),
|
||||
order: num(formData.get("order")) ?? 0,
|
||||
};
|
||||
|
||||
if (id) {
|
||||
await tablesDB.updateRow(DATABASE_ID, TABLES.teamMembers, id, data, secret);
|
||||
} else {
|
||||
await tablesDB.createRow(
|
||||
DATABASE_ID,
|
||||
TABLES.teamMembers,
|
||||
ID.unique(),
|
||||
data,
|
||||
secret,
|
||||
);
|
||||
}
|
||||
revalidatePath("/admin/ekip");
|
||||
revalidatePath("/hakkimizda");
|
||||
}
|
||||
|
||||
export async function deleteTeamMember(formData: FormData) {
|
||||
const secret = await requireSessionSecret();
|
||||
const id = String(formData.get("id"));
|
||||
await tablesDB.deleteRow(DATABASE_ID, TABLES.teamMembers, id, secret);
|
||||
revalidatePath("/admin/ekip");
|
||||
revalidatePath("/hakkimizda");
|
||||
}
|
||||
|
||||
// ─── Industries ──────────────────────────────────────────────────
|
||||
|
||||
export async function saveIndustry(formData: FormData) {
|
||||
const secret = await requireSessionSecret();
|
||||
const id = str(formData.get("id"));
|
||||
const title = str(formData.get("title"));
|
||||
if (!title) throw new Error("Başlık zorunlu");
|
||||
const slug = str(formData.get("slug")) || slugify(title);
|
||||
|
||||
const faqRaw = String(formData.get("faq") ?? "");
|
||||
const faq = faqRaw
|
||||
.split("\n---\n")
|
||||
.map((block) => {
|
||||
const lines = block.trim().split("\n");
|
||||
const q = lines[0]?.trim();
|
||||
const a = lines.slice(1).join("\n").trim();
|
||||
if (!q || !a) return null;
|
||||
return JSON.stringify({ q, a });
|
||||
})
|
||||
.filter((x): x is string => x !== null);
|
||||
|
||||
const data = {
|
||||
slug,
|
||||
title,
|
||||
subtitle: str(formData.get("subtitle")),
|
||||
content: str(formData.get("content")),
|
||||
hero_image: str(formData.get("hero_image")),
|
||||
features: strArr(formData.get("features")),
|
||||
faq: faq.length > 0 ? faq : null,
|
||||
seo_title: str(formData.get("seo_title")),
|
||||
seo_description: str(formData.get("seo_description")),
|
||||
featured: bool(formData.get("featured")),
|
||||
order: num(formData.get("order")) ?? 0,
|
||||
};
|
||||
|
||||
if (id) {
|
||||
await tablesDB.updateRow(DATABASE_ID, TABLES.industries, id, data, secret);
|
||||
} else {
|
||||
await tablesDB.createRow(DATABASE_ID, TABLES.industries, slug, data, secret);
|
||||
}
|
||||
revalidatePath("/admin/sektorler");
|
||||
revalidatePath(`/sektor/${slug}`);
|
||||
}
|
||||
|
||||
export async function deleteIndustry(formData: FormData) {
|
||||
const secret = await requireSessionSecret();
|
||||
const id = String(formData.get("id"));
|
||||
await tablesDB.deleteRow(DATABASE_ID, TABLES.industries, id, secret);
|
||||
revalidatePath("/admin/sektorler");
|
||||
}
|
||||
|
||||
// ─── Contact ─────────────────────────────────────────────────────
|
||||
|
||||
export async function updateMessageStatus(formData: FormData) {
|
||||
|
||||
Reference in New Issue
Block a user