297 lines
10 KiB
TypeScript
297 lines
10 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { motion, AnimatePresence } from "framer-motion";
|
|
import { X, ChevronLeft, ChevronRight } from "lucide-react";
|
|
import { useEffect, useState } from "react";
|
|
import { useRouter, useSearchParams } from "next/navigation";
|
|
import type { PublicArticle } from "@/lib/articles-public";
|
|
import { formatDate } from "@/utils/global";
|
|
import ArticleThumbnail from "@/components/landing-page/article-thumbnail";
|
|
|
|
function firstTag(tags: string | undefined): string {
|
|
if (!tags?.trim()) return "";
|
|
const t = tags
|
|
.split(",")
|
|
.map((s) => s.trim())
|
|
.filter(Boolean)[0];
|
|
return t ?? "";
|
|
}
|
|
|
|
function articleHref(a: PublicArticle) {
|
|
return `/news/detail/${a.id}-${a.slug}`;
|
|
}
|
|
|
|
type Props = {
|
|
featured: PublicArticle[];
|
|
defaultSearch?: string;
|
|
};
|
|
|
|
export default function NewsAndServicesHeader({
|
|
featured,
|
|
defaultSearch = "",
|
|
}: Props) {
|
|
const [activeHeader, setActiveHeader] = useState(0);
|
|
const [activeModal, setActiveModal] = useState(0);
|
|
const [open, setOpen] = useState(false);
|
|
const [mounted, setMounted] = useState(false);
|
|
|
|
const searchParams = useSearchParams();
|
|
const router = useRouter();
|
|
|
|
const slides = featured.length > 0 ? featured : [];
|
|
const slideCount = slides.length;
|
|
|
|
useEffect(() => {
|
|
setMounted(true);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!mounted) return;
|
|
|
|
const highlight = searchParams.get("highlight");
|
|
if (highlight === "1" && slideCount > 0) {
|
|
setActiveModal(activeHeader);
|
|
setOpen(true);
|
|
}
|
|
}, [mounted, searchParams, activeHeader, slideCount]);
|
|
|
|
const closeModal = () => {
|
|
setOpen(false);
|
|
router.replace("/news-services");
|
|
};
|
|
|
|
const headerPrev = () => {
|
|
if (slideCount === 0) return;
|
|
setActiveHeader((p) => (p === 0 ? slideCount - 1 : p - 1));
|
|
};
|
|
|
|
const headerNext = () => {
|
|
if (slideCount === 0) return;
|
|
setActiveHeader((p) => (p === slideCount - 1 ? 0 : p + 1));
|
|
};
|
|
|
|
const modalPrev = () => {
|
|
if (slideCount === 0) return;
|
|
setActiveModal((p) => (p === 0 ? slideCount - 1 : p - 1));
|
|
};
|
|
|
|
const modalNext = () => {
|
|
if (slideCount === 0) return;
|
|
setActiveModal((p) => (p === slideCount - 1 ? 0 : p + 1));
|
|
};
|
|
|
|
if (!mounted) return null;
|
|
|
|
const current = slides[activeHeader];
|
|
const modalArticle = slides[activeModal];
|
|
|
|
return (
|
|
<>
|
|
<section className="relative w-full bg-[#f8f8f8] py-24">
|
|
<div className="container relative mx-auto px-6">
|
|
{slideCount > 0 ? (
|
|
<>
|
|
<button
|
|
type="button"
|
|
onClick={headerPrev}
|
|
className="absolute top-1/2 -left-6 z-10 hidden h-12 w-12 -translate-y-1/2 items-center justify-center rounded-full bg-white shadow-md lg:flex"
|
|
>
|
|
<ChevronLeft />
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={headerNext}
|
|
className="absolute top-1/2 -right-6 z-10 hidden h-12 w-12 -translate-y-1/2 items-center justify-center rounded-full bg-white shadow-md lg:flex"
|
|
>
|
|
<ChevronRight />
|
|
</button>
|
|
</>
|
|
) : null}
|
|
|
|
{slideCount === 0 ? (
|
|
<div className="mx-auto max-w-2xl text-center">
|
|
<h1 className="mb-4 text-3xl font-bold lg:text-4xl">
|
|
Berita & Layanan
|
|
</h1>
|
|
<p className="text-muted-foreground">
|
|
Konten unggulan akan tampil di sini setelah artikel dipublikasikan
|
|
dari CMS.
|
|
</p>
|
|
</div>
|
|
) : (
|
|
current && (
|
|
<div className="flex flex-col items-center gap-14 lg:flex-row">
|
|
<div className="relative w-full lg:w-1/2">
|
|
<div className="relative h-[420px] overflow-hidden rounded-3xl">
|
|
<ArticleThumbnail
|
|
src={current.thumbnailUrl}
|
|
alt={current.title}
|
|
sizes="(max-width: 1024px) 100vw, 50vw"
|
|
/>
|
|
</div>
|
|
|
|
<div className="mt-4 flex justify-center gap-2">
|
|
{slides.map((_, i) => (
|
|
<span
|
|
key={i}
|
|
className={`h-2.5 rounded-full transition-all ${
|
|
activeHeader === i
|
|
? "w-8 bg-[#b07c18]"
|
|
: "w-2.5 bg-gray-300"
|
|
}`}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="w-full lg:w-1/2">
|
|
<h1 className="mb-6 text-4xl font-bold leading-tight lg:text-5xl">
|
|
{current.title}
|
|
</h1>
|
|
|
|
<div className="mb-5 flex flex-wrap items-center gap-3 text-sm text-gray-500">
|
|
<span>
|
|
{formatDate(current.publishedAt || current.createdAt)}
|
|
</span>
|
|
<span>•</span>
|
|
<span>{current.categoryName?.trim() || "Berita"}</span>
|
|
{firstTag(current.tags) ? (
|
|
<>
|
|
<span>•</span>
|
|
<span className="rounded bg-[#f2c94c] px-2 py-0.5 text-xs font-semibold text-black">
|
|
{firstTag(current.tags)}
|
|
</span>
|
|
</>
|
|
) : null}
|
|
</div>
|
|
|
|
<p className="mb-8 leading-relaxed text-gray-700 line-clamp-5">
|
|
{current.description}
|
|
</p>
|
|
|
|
<div className="flex flex-wrap gap-3">
|
|
<Link
|
|
href={articleHref(current)}
|
|
className="inline-flex items-center justify-center rounded-xl bg-[#b07c18] px-7 py-3 font-medium text-white transition hover:opacity-90"
|
|
>
|
|
Baca Selengkapnya
|
|
</Link>
|
|
<button
|
|
type="button"
|
|
onClick={() => setOpen(true)}
|
|
className="inline-flex items-center justify-center rounded-xl border border-[#b07c18] px-7 py-3 font-medium text-[#b07c18] transition hover:bg-[#b07c18]/10"
|
|
>
|
|
Pratinjau
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
)}
|
|
|
|
<div className="mt-20">
|
|
<form
|
|
action="/news-services"
|
|
method="get"
|
|
className="mx-auto flex max-w-3xl items-center overflow-hidden rounded-2xl border bg-white shadow-md"
|
|
>
|
|
<div className="px-4 text-gray-400">🔍</div>
|
|
<input
|
|
type="search"
|
|
name="q"
|
|
defaultValue={defaultSearch}
|
|
placeholder="Cari berita, artikel, atau topik..."
|
|
className="flex-1 px-4 py-4 outline-none"
|
|
/>
|
|
<button
|
|
type="submit"
|
|
className="bg-[#b07c18] px-8 py-4 font-medium text-white"
|
|
>
|
|
Cari
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<AnimatePresence>
|
|
{open && slideCount > 0 && modalArticle && (
|
|
<motion.div
|
|
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm"
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
>
|
|
<motion.div
|
|
className="relative w-[90%] max-w-5xl overflow-hidden rounded-3xl bg-[#9c8414]"
|
|
initial={{ scale: 0.95, opacity: 0 }}
|
|
animate={{ scale: 1, opacity: 1 }}
|
|
exit={{ scale: 0.95, opacity: 0 }}
|
|
>
|
|
<button
|
|
type="button"
|
|
onClick={closeModal}
|
|
className="absolute top-4 right-4 z-10 flex h-10 w-10 items-center justify-center rounded-full bg-black/40 text-white"
|
|
>
|
|
<X />
|
|
</button>
|
|
|
|
<div className="relative h-[520px]">
|
|
<ArticleThumbnail
|
|
src={modalArticle.thumbnailUrl}
|
|
alt={modalArticle.title}
|
|
sizes="100vw"
|
|
/>
|
|
|
|
<div className="absolute bottom-6 left-6 right-6 text-white">
|
|
<h2 className="text-xl font-semibold md:text-2xl">
|
|
{modalArticle.title}
|
|
</h2>
|
|
<Link
|
|
href={articleHref(modalArticle)}
|
|
className="mt-3 inline-block text-sm font-medium underline"
|
|
onClick={closeModal}
|
|
>
|
|
Buka halaman artikel
|
|
</Link>
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={modalPrev}
|
|
className="absolute top-1/2 left-4 flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-full bg-black/40 text-white"
|
|
>
|
|
<ChevronLeft />
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={modalNext}
|
|
className="absolute top-1/2 right-4 flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-full bg-black/40 text-white"
|
|
>
|
|
<ChevronRight />
|
|
</button>
|
|
</div>
|
|
|
|
<div className="flex justify-center gap-2 py-4">
|
|
{slides.map((_, i) => (
|
|
<button
|
|
key={i}
|
|
type="button"
|
|
onClick={() => setActiveModal(i)}
|
|
className={`h-2.5 w-2.5 rounded-full ${
|
|
activeModal === i ? "bg-white" : "bg-white/40"
|
|
}`}
|
|
/>
|
|
))}
|
|
</div>
|
|
</motion.div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</>
|
|
);
|
|
}
|