feat(blog): CMS cover images via MinIO, instant cache purge, drop embedded posts

- cover_file uploads in Directus land in the public blog-assets MinIO bucket;
  API resolves them to storage.sase.tr URLs (Directus itself is Tailscale-only)
- POST /blog/cache/purge (automation token) lets a Directus Flow drop the
  30-min list cache the moment a post changes in the CMS
- blog list + detail pages now render purely from the API; the 4 hand-authored
  posts were migrated to Directus earlier and the JSX copies are removed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 00:17:40 +03:00
parent 07cf710bc3
commit 572337ba62
4 changed files with 86 additions and 413 deletions

View File

@@ -1,7 +1,6 @@
import { usePageMeta } from "@/hooks/use-page-meta";
import { SiteHeader } from "@/components/site-header";
import { useBlogPosts } from "@/hooks/use-blog";
import { Button } from "@sase/ui";
import { usePageMeta } from "@/hooks/use-page-meta";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@sase/ui";
import { Link, createFileRoute } from "@tanstack/react-router";
@@ -9,41 +8,6 @@ export const Route = createFileRoute("/blog")({
component: BlogPage,
});
type PostCard = { title: string; description: string; date: string; slug: string };
// Original hand-authored posts. Kept so they never disappear; auto-published
// posts from the content pipeline are merged in from the API below.
const staticPosts: PostCard[] = [
{
title: "Şase Numarası (VIN) Nedir? Nasıl Okunur?",
description:
"17 haneli VIN kodunun her bir bölümünün ne anlama geldiğini, araç geçmişini nasıl ortaya çıkarabileceğinizi adım adım anlatıyoruz.",
date: "2026-02-10",
slug: "vin-nedir-nasil-okunur",
},
{
title: "Orijinal Parça mı, Muadil Parça mı?",
description:
"OEM ve aftermarket parçalar arasındaki farkları, avantaj ve dezavantajlarını karşılaştırmalı olarak inceliyoruz.",
date: "2026-01-28",
slug: "orijinal-mi-muadil-mi",
},
{
title: "Yedek Parça Aramada Dijital Dönüşüm",
description:
"Geleneksel katalog yöntemlerinden dijital platformlara geçiş sürecini ve sektöre etkilerini ele alıyoruz.",
date: "2026-01-15",
slug: "dijital-donusum",
},
{
title: "Sase.tr ile Doğru Parçayı İlk Seferde Bulun",
description:
"Platform özelliklerini kullanarak VIN bazlı arama, şema görüntüleme ve parça eşleştirme rehberi.",
date: "2026-01-05",
slug: "dogru-parcayi-bulun",
},
];
function BlogPage() {
usePageMeta({
title: "Blog — Sase.tr | Şase & Yedek Parça Rehberi",
@@ -51,21 +15,17 @@ function BlogPage() {
canonical: "https://sase.tr/blog",
});
const { data: apiPosts } = useBlogPosts();
// All posts come from the central Directus CMS through the API.
const { data: apiPosts, isLoading } = useBlogPosts();
// Merge static + API posts, dedupe by slug (static wins), newest first.
const bySlug = new Map<string, PostCard>();
for (const p of staticPosts) bySlug.set(p.slug, p);
for (const p of apiPosts ?? []) {
if (bySlug.has(p.slug)) continue;
bySlug.set(p.slug, {
const posts = (apiPosts ?? [])
.map((p) => ({
slug: p.slug,
title: p.title,
description: p.metaDescription ?? "",
date: p.publishedAt.slice(0, 10),
});
}
const posts = [...bySlug.values()].sort((a, b) => b.date.localeCompare(a.date));
}))
.sort((a, b) => b.date.localeCompare(a.date));
return (
<div className="min-h-screen">
@@ -77,6 +37,10 @@ function BlogPage() {
Yedek parça sektörü, araç bakımı ve Sase.tr hakkında güncel yazılar.
</p>
{isLoading && posts.length === 0 && (
<p className="mt-12 text-muted-foreground">Yükleniyor</p>
)}
<div className="mt-12 grid gap-6 sm:grid-cols-2">
{posts.map((post) => (
<Link key={post.slug} to="/blog/$slug" params={{ slug: post.slug }}>

View File

@@ -1,352 +1,13 @@
import { SiteHeader } from "@/components/site-header";
import { useBlogPost } from "@/hooks/use-blog";
import { usePageMeta } from "@/hooks/use-page-meta";
import { Button } from "@sase/ui";
import { Link, createFileRoute } from "@tanstack/react-router";
import { ChevronRight } from "lucide-react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
// ─── BLOG POST DATA ───────────────────────────────────────────────────────────
interface BlogPost {
slug: string;
title: string;
description: string;
date: string;
content: React.ReactNode;
}
const POSTS: Record<string, BlogPost> = {
"vin-nedir-nasil-okunur": {
slug: "vin-nedir-nasil-okunur",
title: "Şase Numarası (VIN) Nedir? Nasıl Okunur?",
description:
"17 haneli VIN kodunun her bir bölümünün ne anlama geldiğini, araç geçmişini nasıl ortaya çıkarabileceğinizi adım adım anlatıyoruz.",
date: "2026-02-10",
content: (
<div className="space-y-6 leading-relaxed text-muted-foreground">
<p>
VIN (Vehicle Identification Number), yani Araç Tanımlama Numarası, her motorlu taşıta
üretim aşamasında atanan 17 karakterlik benzersiz bir koddur. Türkiye'de "şase numarası"
olarak da bilinen bu kod, aracın üretiminden imhasına kadar tüm yaşam döngüsünü takip
etmeye yarar.
</p>
<h2 className="text-xl font-semibold text-foreground">VIN Nereden Okunur?</h2>
<p>
VIN numarasına birçok yerden ulaşabilirsiniz: ön camın sol alt köşesindeki plaka, sürücü
kapısının iç kısmındaki etiket, motor bölmesi veya araç ruhsatı ve fatura bunların başında
gelir. Bazı araçlarda bagaj kapısı iç kısmında da bulunur.
</p>
<h2 className="text-xl font-semibold text-foreground">17 Karakterin Anlamı</h2>
<p>VIN üç ana bölüme ayrılır:</p>
<ul className="list-disc space-y-2 pl-6">
<li>
<strong className="text-foreground">
WMI (1-3. karakterler) — Dünya Üretici Kodu:
</strong>{" "}
Aracın hangi ülkede ve hangi fabrikada üretildiğini gösterir. Örneğin "WVW" Volkswagen
Almanya, "ZFA" Fiat İtalya demektir.
</li>
<li>
<strong className="text-foreground">
VDS (4-9. karakterler) — Araç Tanımlayıcı Bölüm:
</strong>{" "}
Model, kasa tipi, motor hacmi, yakıt türü ve güvenlik donanımları hakkında bilgi içerir.
9. karakter her zaman kontrol karakteridir ve matematiksel bir doğrulama amacı taşır.
</li>
<li>
<strong className="text-foreground">
VIS (10-17. karakterler) — Araç Tanımlama Bölümü:
</strong>{" "}
Model yılı (10. karakter), üretim fabrikası (11. karakter) ve üretim sıra numarası
(12-17. karakterler) bilgilerini içerir.
</li>
</ul>
<h2 className="text-xl font-semibold text-foreground">Örnek: WVWZZZ1JZ3W597935</h2>
<p>
Bu VIN'i inceleyelim: <strong className="font-mono text-foreground">WVW</strong>
Volkswagen Almanya, <strong className="font-mono text-foreground">ZZZ</strong> pazar
tanımlayıcı, <strong className="font-mono text-foreground">1J</strong> Golf modeli,{" "}
<strong className="font-mono text-foreground">Z</strong> motor tipi,{" "}
<strong className="font-mono text-foreground">3</strong> model yılı 2003,{" "}
<strong className="font-mono text-foreground">W</strong> Wolfsburg fabrikası,{" "}
<strong className="font-mono text-foreground">597935</strong> sıra numarası.
</p>
<h2 className="text-xl font-semibold text-foreground">VIN ile Ne Öğrenebilirsiniz?</h2>
<ul className="list-disc space-y-2 pl-6">
<li>Aracın üretim yılı ve fabrikası</li>
<li>Motor hacmi, yakıt türü ve şanzıman tipi</li>
<li>Kasa tipi ve donanım paketi</li>
<li>Kaza ve hasar geçmişi (resmi kayıtlarda)</li>
<li>Kilometre manipülasyonu şüphesi</li>
<li>Orijinal parça uyumluluğu</li>
</ul>
<h2 className="text-xl font-semibold text-foreground">VIN ile Doğru Parça Bulma</h2>
<p>
VIN'in en kritik kullanım alanlarından biri yedek parça aramadır. Aynı model arabada bile
üretim yılı, motor tipi veya donanım paketine göre farklı parçalar kullanılmış olabilir.
Şase numarasıyla sorgulama yaparak yalnızca araca tam uyumlu OEM parçalara ulaşabilir,
yanlış parça sipariş etme riskini sıfıra indirebilirsiniz.
</p>
<p>
Sase.tr platformunda şase numarasını girerek saniyeler içinde aracın tam teknik
bilgilerine ve orijinal parça kataloğuna erişebilirsiniz. 50'den fazla markayı destekleyen
platformumuzla yanlış parça siparişlerine son verin.
</p>
</div>
),
},
"orijinal-mi-muadil-mi": {
slug: "orijinal-mi-muadil-mi",
title: "Orijinal Parça mı, Muadil Parça mı?",
description:
"OEM ve aftermarket parçalar arasındaki farkları, avantaj ve dezavantajlarını karşılaştırmalı olarak inceliyoruz.",
date: "2026-01-28",
content: (
<div className="space-y-6 leading-relaxed text-muted-foreground">
<p>
Araç bakımı ve onarımında en sık sorulan sorulardan biri şudur: "Orijinal mi alsam, muadil
mi?" Bu soru, hem maliyet hem de güvenlik ısından kritik öneme sahiptir. İki seçeneği
tüm boyutlarıyla inceleyelim.
</p>
<h2 className="text-xl font-semibold text-foreground">OEM Parça Nedir?</h2>
<p>
OEM (Original Equipment Manufacturer), aracın üretiminde kullanılan ya da araç
üreticisinin onayladığı orijinal parçalardır. Bu parçalar araç fabrikasında kullanılan
parçalarla aynı spesifikasyonlara sahiptir ve genellikle aynı tedarikçilerden gelir.
Üzerinde araç markasının logosu bulunabilir ya da yalnızca parça numarasıyla satılabilir.
</p>
<h2 className="text-xl font-semibold text-foreground">Aftermarket (Muadil) Parça Nedir?</h2>
<p>
Muadil parçalar, araç üreticisinden bağımsız üçüncü taraf firmalar tarafından üretilir.
Kalite seviyeleri geniş bir yelpazede değişir: premium muadil parçalar OEM kalitesine
yaklaşırken, düşük kaliteli kopya ürünler araç güvenliğini tehlikeye atabilir.
</p>
<h2 className="text-xl font-semibold text-foreground">OEM Parçaların Avantajları</h2>
<ul className="list-disc space-y-2 pl-6">
<li>Araca tam uyum garantisi montaj sorunu yaşanmaz</li>
<li>Fabrika spesifikasyonlarına uygunluk</li>
<li>Araç garantisini etkilemez</li>
<li>Standart kalite tutarlılığı</li>
<li>Orijinal parça numarasıyla kolay temin</li>
</ul>
<h2 className="text-xl font-semibold text-foreground">OEM Parçaların Dezavantajları</h2>
<ul className="list-disc space-y-2 pl-6">
<li>Genellikle muadile göre %20-50 daha pahalı</li>
<li>Her markada ve her bölgede stok bulunmayabilir</li>
<li>Bazı modeller için yüksek bekleme süresi</li>
</ul>
<h2 className="text-xl font-semibold text-foreground">Muadil Parçaların Avantajları</h2>
<ul className="list-disc space-y-2 pl-6">
<li>Genellikle daha düşük fiyat</li>
<li>Daha kolay erişilebilirlik</li>
<li>Bazı premium muadil markalar üstün performans sunar</li>
<li>Üretimi durdurulmuş eski araçlarda tek seçenek olabilir</li>
</ul>
<h2 className="text-xl font-semibold text-foreground">Muadil Parçaların Riskleri</h2>
<ul className="list-disc space-y-2 pl-6">
<li>Kalite tutarsızlığı marka ve fiyata göre büyük farklar</li>
<li>Uyumsuzluk riski görünürde aynı ama teknik farklılıklar olabilir</li>
<li>Araç garantisini geçersiz kılabilir</li>
<li>Güvenlik sistemlerini olumsuz etkileyebilir</li>
</ul>
<h2 className="text-xl font-semibold text-foreground">
Hangi Durumlarda Orijinal, Hangisinde Muadil?
</h2>
<p>
Fren sistemi, hava yastığı, direksiyon ve motor parçaları gibi güvenlik kritik
bileşenlerde kesinlikle OEM tercih edilmelidir. Kaporta, döşeme veya aksesuar
niteliğindeki parçalarda güvenilir bir muadil marka değerlendirilebilir.
</p>
<h2 className="text-xl font-semibold text-foreground">Doğru Parçayı Nasıl Bulursunuz?</h2>
<p>
İster OEM ister muadil tercih edin, en önemli adım doğru OEM parça numarasını bilmektir.
Sase.tr ile araç şase numarasından yola çıkarak orijinal parça kodlarına ulaşın. Bu kodu
elinizde bulundurmak, hem servisle hem de tedarikçiyle iletişimi kolaylaştırır, yanlış
parça siparişini önler.
</p>
</div>
),
},
"dijital-donusum": {
slug: "dijital-donusum",
title: "Yedek Parça Aramada Dijital Dönüşüm",
description:
"Geleneksel katalog yöntemlerinden dijital platformlara geçiş sürecini ve sektöre etkilerini ele alıyoruz.",
date: "2026-01-15",
content: (
<div className="space-y-6 leading-relaxed text-muted-foreground">
<p>
Türkiye'nin otomotiv yedek parça sektörü onlarca yıldır basılı kataloglar, telefon
görüşmeleri ve tecrübeye dayalı bilgiyle ayakta durdu. Ancak 2020'lerin ortasına
gelindiğinde bu yöntemler artık yetersiz kalmaktadır. Dijital dönüşüm, sektörde kaçınılmaz
bir zorunluluk hâline gelmiştir.
</p>
<h2 className="text-xl font-semibold text-foreground">Geleneksel Yöntemlerin Sorunları</h2>
<ul className="list-disc space-y-2 pl-6">
<li>
<strong className="text-foreground">Zaman kaybı:</strong> Tek bir parça için birden
fazla katalog taramak ortalama 15-20 dakika sürebilir.
</li>
<li>
<strong className="text-foreground">Hata oranı:</strong> Elle yapılan arama ve
karşılaştırma işlemlerinde yanlış parça sipariş riski yüksektir.
</li>
<li>
<strong className="text-foreground">Güncellik sorunu:</strong> Basılı kataloglar yeni
model güncellemelerini yavaş takip eder.
</li>
<li>
<strong className="text-foreground">Ölçeklenemezlik:</strong> Büyüyen filo veya artan
müşteri sayısıyla geleneksel yöntemler çökmektedir.
</li>
</ul>
<h2 className="text-xl font-semibold text-foreground">Dijital Dönüşümün Faydaları</h2>
<p>Dijital platformlar, yedek parça arama sürecini kökten değiştirmektedir:</p>
<ul className="list-disc space-y-2 pl-6">
<li>Şase bazlı anlık araç tanımlama saniyeler içinde doğru araç tespiti</li>
<li>Çoklu katalog çapraz sorgulama tek arayüzde birden fazla kaynak</li>
<li>Otomatik parça eşleştirme insan hatasını minimuma indirir</li>
<li>İnteraktif teknik şemalar parçayı görsel bul</li>
<li>27 marka orijinal parça kataloğu tek çatı altında</li>
</ul>
<h2 className="text-xl font-semibold text-foreground">Sektörde Sayısal Dönüşüm</h2>
<p>Dijital platforma geçiş yapan işletmelerin deneyimlerine göre:</p>
<ul className="list-disc space-y-2 pl-6">
<li>Parça arama süresi ortalama %85 kısalmaktadır</li>
<li>Yanlış parça iade oranları %60-80 düşmektedir</li>
<li>Müşteri memnuniyeti belirgin biçimde artmaktadır</li>
<li>Personel kapasitesi daha katma değerli işlere yönlendirilebilmektedir</li>
</ul>
<h2 className="text-xl font-semibold text-foreground">
Küçük ve Orta Ölçekli İşletmeler İçin Fırsatlar
</h2>
<p>
Dijital dönüşüm artık yalnızca büyük zincirlerin ayrıcalığı değildir. Aylık sabit
maliyetli abonelik modelleri sayesinde küçük oto yedek parçacılar ve servisler de kurumsal
araçlara erişebilmektedir. Bu durum, rekabet eşitliğini kısmen sağlamaktadır.
</p>
<h2 className="text-xl font-semibold text-foreground">Sase.tr'nin Rolü</h2>
<p>
Sase.tr, Türkiye'nin yedek parça sektörüne özgü geliştirilen bu dijital dönüşümün
öncüsüdür. Şase numarası sorgulama, çoklu katalog entegrasyonu ve interaktif şema
görüntüleme özellikleriyle geleneksel yapış biçimlerini dönüştürmektedir. Platform, 27
marka ve 1 milyondan fazla OEM parça numarasıyla sektörün en kapsamlı dijital kataloğunu
sunmaktadır.
</p>
</div>
),
},
"dogru-parcayi-bulun": {
slug: "dogru-parcayi-bulun",
title: "Sase.tr ile Doğru Parçayı İlk Seferde Bulun",
description:
"Platform özelliklerini kullanarak şase bazlı arama, şema görüntüleme ve parça eşleştirme rehberi.",
date: "2026-01-05",
content: (
<div className="space-y-6 leading-relaxed text-muted-foreground">
<p>
Yanlış parça sipariş etmek hem zaman hem para kaybettirdiği gibi, müşteri memnuniyetini de
olumsuz etkiler. Sase.tr, bu sorunu kökten çözmek için tasarlanmıştır. Bu rehberde
platformu nasıl en verimli şekilde kullanacağınızı adım adım anlatıyoruz.
</p>
<h2 className="text-xl font-semibold text-foreground">Adım 1: Şase Numarasını Girin</h2>
<p>
Ana sayfadaki arama kutusuna aracın 17 haneli şase numarasını girin. Numara girilirken
anlık doğrulama çubuğu dolmaya başlar 17. karaktere ulaştığınızda sistem otomatik olarak
araç bilgilerini çeker.
</p>
<p>
Sistem saniyeler içinde araç markasını, modelini, yılını ve motor bilgilerini gösterir.
Bilgileri doğrulayın ve "Parça Kataloğuna Devam Et" butonuna tıklayın.
</p>
<h2 className="text-xl font-semibold text-foreground">Adım 2: Kategori Seçin</h2>
<p>
Araç tanımlandıktan sonra karşınıza o araca özgü parça kategorileri çıkar. Motor, Şasi &
Süspansiyon, Elektrik, Karoseri, Klima & Isıtma gibi ana kategorilerden ihtiyacınız olan
bölümü seçin.
</p>
<p>
Her ana kategorinin altında detaylı alt kategoriler bulunur. Örneğin "Motor" altında
Silindir Kapağı, Krank Mili, Piston, Yağ Pompası gibi bölümler yer alır.
</p>
<h2 className="text-xl font-semibold text-foreground">
Adım 3: İnteraktif Şemayı Kullanın
</h2>
<p>
Kategori seçildikten sonra o bölgеnin teknik çizimi interaktif şema ekranda ılır.
Şema üzerindeki parçalara tıklayarak OEM kodunu, ıklamasını ve gerekli miktarını
görebilirsiniz.
</p>
<p>
Şemalar zoom ve pan desteğiyle büyütülebilir, yatay veya dikey kaydırılabilir. Karmaşık
motor veya şasi bölgelerinde parça konumunu görsel olarak tespit etmek iade oranlarını
ciddi ölçüde düşürmektedir.
</p>
<h2 className="text-xl font-semibold text-foreground">Adım 4: OEM Kodunu Kopyalayın</h2>
<p>
Doğru parçayı bulduktan sonra OEM kodunu panoya kopyalayın. Bu kodu tedarikçinize, servis
faturanıza veya online sipariş formunuza yapıştırarak yanlış parça riskini tamamen ortadan
kaldırın.
</p>
<h2 className="text-xl font-semibold text-foreground">İpuçları</h2>
<ul className="list-disc space-y-2 pl-6">
<li>
Birden fazla araç için sorgu yapıyorsanız, her birini şase ile kaydedin; geçmiş
aramalarınıza hızla dönebilirsiniz.
</li>
<li>
Aynı parça farklı kataloglarda listelenebilir çapraz sorgulama her zaman en güncel OEM
kodunu getirir.
</li>
<li>
Parça bulamadığınızda kategori ağacında bir seviye yukarı çıkarak daha geniş bir arama
yapabilirsiniz.
</li>
<li>27 markanın tamamına erişmek için Full Paket aboneliği en avantajlı seçenektir.</li>
</ul>
<h2 className="text-xl font-semibold text-foreground">Sonuç</h2>
<p>
Sase.tr ile tek bir yanlış parça iadesinden tasarruf ettiğiniz para, aylık abonelik
ücretini karşılar. 30 günlük ücretsiz deneme süresiyle platformu bugün deneyin kredi
kartı gerektirmez.
</p>
</div>
),
},
};
// ─── MARKDOWN RENDERING (API-backed posts) ─────────────────────────────────────
// Maps markdown to the same prose styling the hand-authored posts use.
// ─── MARKDOWN RENDERING ───────────────────────────────────────────────────────
// Posts live in the central Directus CMS and arrive as markdown via the API.
const mdComponents = {
h2: (props: React.ComponentProps<"h2">) => (
@@ -383,12 +44,11 @@ export const Route = createFileRoute("/blog_/$slug")({
function BlogPostPage() {
const { slug } = Route.useParams();
const staticPost = POSTS[slug];
const { data: apiPost, isLoading, isError } = useBlogPost(slug, !staticPost);
const { data: post, isLoading, isError } = useBlogPost(slug);
const title = staticPost?.title ?? apiPost?.title ?? "Blog";
const description = staticPost?.description ?? apiPost?.metaDescription ?? "";
const date = staticPost?.date ?? apiPost?.publishedAt?.slice(0, 10) ?? "";
const title = post?.title ?? "Blog";
const description = post?.metaDescription ?? "";
const date = post?.publishedAt?.slice(0, 10) ?? "";
usePageMeta({
title: `${title} | Sase.tr Blog`,
@@ -397,8 +57,7 @@ function BlogPostPage() {
});
let body: React.ReactNode;
if (staticPost) body = staticPost.content;
else if (apiPost) body = <MarkdownBody markdown={apiPost.bodyMarkdown} />;
if (post) body = <MarkdownBody markdown={post.bodyMarkdown} />;
else if (isLoading) body = <p className="text-muted-foreground">Yükleniyor</p>;
else
body = (
@@ -424,6 +83,14 @@ function BlogPostPage() {
<h1 className="mt-3 text-3xl font-bold leading-tight sm:text-4xl">{title}</h1>
{description && <p className="mt-4 text-lg text-muted-foreground">{description}</p>}
{post?.coverImage && (
<img
src={post.coverImage}
alt={title}
className="mt-8 w-full rounded-lg border object-cover"
/>
)}
<div className="mt-10">{body}</div>
</article>