Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Sidebar linked /blog and /contact pointed at public marketing pages, so clicking them dropped the user out of the dashboard shell. Extract page content into shared components (blog-content, contact-content) and add dashboard-wrapped routes /dashboard/blog, /dashboard/blog/$slug and /dashboard/contact; sidebar now links to those. Public SEO pages stay unchanged and remain the canonical URLs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
158 lines
5.7 KiB
TypeScript
158 lines
5.7 KiB
TypeScript
import { useBlogPost, useBlogPosts } from "@/hooks/use-blog";
|
||
import { usePageMeta } from "@/hooks/use-page-meta";
|
||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@sase/ui";
|
||
import { Link } from "@tanstack/react-router";
|
||
import { ChevronRight } from "lucide-react";
|
||
import ReactMarkdown from "react-markdown";
|
||
import remarkGfm from "remark-gfm";
|
||
|
||
// Blog content is rendered in two shells: the public marketing pages (/blog)
|
||
// and the dashboard-wrapped pages (/dashboard/blog) so logged-in users keep
|
||
// the sidebar. Only the internal link targets differ; the canonical URL always
|
||
// points at the public page.
|
||
|
||
type BlogListPath = "/blog" | "/dashboard/blog";
|
||
type BlogPostPath = "/blog/$slug" | "/dashboard/blog/$slug";
|
||
|
||
// ─── LIST ─────────────────────────────────────────────────────────────────────
|
||
|
||
export function BlogListContent({ postLinkTo }: { postLinkTo: BlogPostPath }) {
|
||
usePageMeta({
|
||
title: "Blog — Sase.tr | Şase & Yedek Parça Rehberi",
|
||
description: "Şase numarası okuma, OEM vs muadil parça, dijital dönüşüm ve daha fazlası.",
|
||
canonical: "https://sase.tr/blog",
|
||
});
|
||
|
||
// All posts come from the central Directus CMS through the API.
|
||
const { data: apiPosts, isLoading } = useBlogPosts();
|
||
|
||
const posts = (apiPosts ?? [])
|
||
.map((p) => ({
|
||
slug: p.slug,
|
||
title: p.title,
|
||
description: p.metaDescription ?? "",
|
||
date: p.publishedAt.slice(0, 10),
|
||
}))
|
||
.sort((a, b) => b.date.localeCompare(a.date));
|
||
|
||
return (
|
||
<>
|
||
<h1 className="text-4xl font-bold">Blog</h1>
|
||
<p className="mt-4 text-lg text-muted-foreground">
|
||
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={postLinkTo} params={{ slug: post.slug }}>
|
||
<Card className="h-full transition hover:border-foreground/20">
|
||
<CardHeader>
|
||
<CardDescription>{post.date}</CardDescription>
|
||
<CardTitle className="text-lg">{post.title}</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<p className="text-sm text-muted-foreground">{post.description}</p>
|
||
</CardContent>
|
||
</Card>
|
||
</Link>
|
||
))}
|
||
</div>
|
||
</>
|
||
);
|
||
}
|
||
|
||
// ─── POST ─────────────────────────────────────────────────────────────────────
|
||
// Posts live in the central Directus CMS and arrive as markdown via the API.
|
||
|
||
const mdComponents = {
|
||
h2: (props: React.ComponentProps<"h2">) => (
|
||
<h2 className="text-xl font-semibold text-foreground" {...props} />
|
||
),
|
||
h3: (props: React.ComponentProps<"h3">) => (
|
||
<h3 className="text-lg font-semibold text-foreground" {...props} />
|
||
),
|
||
ul: (props: React.ComponentProps<"ul">) => <ul className="list-disc space-y-2 pl-6" {...props} />,
|
||
ol: (props: React.ComponentProps<"ol">) => (
|
||
<ol className="list-decimal space-y-2 pl-6" {...props} />
|
||
),
|
||
strong: (props: React.ComponentProps<"strong">) => (
|
||
<strong className="text-foreground" {...props} />
|
||
),
|
||
a: (props: React.ComponentProps<"a">) => <a className="text-foreground underline" {...props} />,
|
||
};
|
||
|
||
function MarkdownBody({ markdown }: { markdown: string }) {
|
||
return (
|
||
<div className="space-y-6 leading-relaxed text-muted-foreground">
|
||
<ReactMarkdown remarkPlugins={[remarkGfm]} components={mdComponents}>
|
||
{markdown}
|
||
</ReactMarkdown>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function BlogPostContent({ slug, listLinkTo }: { slug: string; listLinkTo: BlogListPath }) {
|
||
const { data: post, isLoading, isError } = useBlogPost(slug);
|
||
|
||
const title = post?.title ?? "Blog";
|
||
const description = post?.metaDescription ?? "";
|
||
const date = post?.publishedAt?.slice(0, 10) ?? "";
|
||
|
||
usePageMeta({
|
||
title: `${title} | Sase.tr Blog`,
|
||
description,
|
||
canonical: `https://sase.tr/blog/${slug}`,
|
||
});
|
||
|
||
let body: React.ReactNode;
|
||
if (post) body = <MarkdownBody markdown={post.bodyMarkdown} />;
|
||
else if (isLoading) body = <p className="text-muted-foreground">Yükleniyor…</p>;
|
||
else
|
||
body = (
|
||
<p className="text-muted-foreground">{isError ? "Yazı bulunamadı." : "Yazı yüklenemedi."}</p>
|
||
);
|
||
|
||
return (
|
||
<>
|
||
{/* Breadcrumb */}
|
||
<nav className="mb-8 flex items-center gap-1.5 text-sm text-muted-foreground">
|
||
<Link to={listLinkTo} className="transition hover:text-foreground">
|
||
Blog
|
||
</Link>
|
||
<ChevronRight className="size-3.5" />
|
||
<span className="text-foreground">{title}</span>
|
||
</nav>
|
||
|
||
<article>
|
||
{date && <time className="text-sm text-muted-foreground">{date}</time>}
|
||
<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>
|
||
|
||
{/* Back link */}
|
||
<div className="mt-16 border-t pt-8">
|
||
<Link
|
||
to={listLinkTo}
|
||
className="inline-flex items-center gap-1.5 text-sm text-muted-foreground transition hover:text-foreground"
|
||
>
|
||
← Tüm yazılar
|
||
</Link>
|
||
</div>
|
||
</>
|
||
);
|
||
}
|