qudoco-fe/app/news/detail/[idSlug]/page.tsx

120 lines
3.9 KiB
TypeScript

import Link from "next/link";
import type { Metadata } from "next";
import { notFound, redirect } from "next/navigation";
import Footer from "@/components/landing-page/footer";
import LandingSiteNav from "@/components/landing-page/landing-site-nav";
import ArticleThumbnail from "@/components/landing-page/article-thumbnail";
import { ARTICLE_TYPE } from "@/constants/article-content-types";
import { fetchArticlePublic } from "@/lib/articles-public";
import { formatDate } from "@/utils/format-date";
type Props = {
params: Promise<{ idSlug: string }>;
};
function parseIdSlug(idSlug: string): { id: number; slug: string } | null {
const match = idSlug.match(/^(\d+)-(.*)$/);
if (!match) return null;
const id = parseInt(match[1], 10);
if (Number.isNaN(id)) return null;
return { id, slug: match[2] };
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { idSlug } = await params;
const parsed = parseIdSlug(idSlug);
if (!parsed) return { title: "Artikel" };
const article = await fetchArticlePublic(parsed.id);
if (!article || article.isPublish !== true) {
return { title: "Artikel tidak ditemukan" };
}
return {
title: article.title,
description:
article.description?.slice(0, 160) ||
article.title,
};
}
export default async function NewsDetailPage({ params }: Props) {
const { idSlug } = await params;
const parsed = parseIdSlug(idSlug);
if (!parsed) notFound();
const article = await fetchArticlePublic(parsed.id);
if (!article || article.isPublish !== true) notFound();
if (article.slug !== parsed.slug) {
redirect(`/news/detail/${article.id}-${article.slug}`);
}
const dateLabel = formatDate(article.publishedAt || article.createdAt);
const primaryFile = article.files?.[0];
const mediaUrl = primaryFile?.fileUrl?.trim() || "";
return (
<div className="relative min-h-screen bg-white">
<LandingSiteNav
newsHub
searchFormAction="/news-services"
searchPlaceholder="Cari berita, artikel, atau topik..."
/>
<article className="container mx-auto max-w-4xl px-6 pt-28 pb-16 md:pt-36">
<Link
href="/news-services"
className="mb-8 inline-block text-sm font-medium text-[#b07c18] hover:underline"
>
Kembali ke Berita & Layanan
</Link>
<p className="mb-2 text-sm text-muted-foreground">
{dateLabel}
{article.categoryName ? ` · ${article.categoryName}` : ""}
</p>
<h1 className="mb-8 text-3xl font-bold leading-tight md:text-4xl">
{article.title}
</h1>
<div className="relative mb-10 h-[320px] w-full overflow-hidden rounded-2xl md:h-[420px]">
<ArticleThumbnail
src={article.thumbnailUrl}
alt={article.title}
sizes="(max-width: 896px) 100vw, 896px"
/>
</div>
{article.typeId === ARTICLE_TYPE.VIDEO && mediaUrl ? (
<div className="mb-10">
<video
src={mediaUrl}
controls
className="w-full max-w-3xl rounded-xl bg-black"
/>
</div>
) : null}
{article.typeId === ARTICLE_TYPE.AUDIO && mediaUrl ? (
<div className="mb-10">
<audio src={mediaUrl} controls className="w-full max-w-xl" />
</div>
) : null}
{article.description ? (
<p className="mb-8 text-lg leading-relaxed text-gray-700">
{article.description}
</p>
) : null}
{article.htmlDescription ? (
<div
className="max-w-none space-y-4 text-gray-800 [&_a]:text-[#b07c18] [&_a]:underline [&_img]:max-w-full [&_img]:rounded-lg [&_p]:leading-relaxed"
dangerouslySetInnerHTML={{ __html: article.htmlDescription }}
/>
) : null}
</article>
<Footer />
</div>
);
}