qudoco-fe/components/landing-page/headers-news-services.tsx

297 lines
10 KiB
TypeScript
Raw Normal View History

2026-02-17 09:05:22 +00:00
"use client";
import Link from "next/link";
2026-02-17 09:05:22 +00:00
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;
};
2026-02-17 09:05:22 +00:00
export default function NewsAndServicesHeader({
featured,
defaultSearch = "",
}: Props) {
2026-02-17 09:05:22 +00:00
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;
2026-02-17 09:05:22 +00:00
useEffect(() => {
setMounted(true);
}, []);
useEffect(() => {
if (!mounted) return;
const highlight = searchParams.get("highlight");
if (highlight === "1" && slideCount > 0) {
setActiveModal(activeHeader);
2026-02-17 09:05:22 +00:00
setOpen(true);
}
}, [mounted, searchParams, activeHeader, slideCount]);
2026-02-17 09:05:22 +00:00
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));
};
2026-02-17 09:05:22 +00:00
const modalNext = () => {
if (slideCount === 0) return;
setActiveModal((p) => (p === slideCount - 1 ? 0 : p + 1));
};
2026-02-17 09:05:22 +00:00
if (!mounted) return null;
const current = slides[activeHeader];
const modalArticle = slides[activeModal];
2026-02-17 09:05:22 +00:00
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>
2026-02-17 09:05:22 +00:00
<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}
2026-02-17 09:05:22 +00:00
{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>
2026-02-17 09:05:22 +00:00
</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>
2026-02-17 09:05:22 +00:00
<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>
2026-02-17 09:05:22 +00:00
<div className="w-full lg:w-1/2">
<h1 className="mb-6 text-4xl font-bold leading-tight lg:text-5xl">
{current.title}
</h1>
2026-02-17 09:05:22 +00:00
<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>
2026-02-17 09:05:22 +00:00
<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>
)
)}
2026-02-17 09:05:22 +00:00
<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"
>
2026-02-17 09:05:22 +00:00
<div className="px-4 text-gray-400">🔍</div>
<input
type="search"
name="q"
defaultValue={defaultSearch}
2026-02-17 09:05:22 +00:00
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"
>
2026-02-17 09:05:22 +00:00
Cari
</button>
</form>
2026-02-17 09:05:22 +00:00
</div>
</div>
</section>
<AnimatePresence>
{open && slideCount > 0 && modalArticle && (
2026-02-17 09:05:22 +00:00
<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]"
2026-02-17 09:05:22 +00:00
initial={{ scale: 0.95, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.95, opacity: 0 }}
>
<button
type="button"
2026-02-17 09:05:22 +00:00
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"
2026-02-17 09:05:22 +00:00
>
<X />
</button>
<div className="relative h-[520px]">
<ArticleThumbnail
src={modalArticle.thumbnailUrl}
alt={modalArticle.title}
sizes="100vw"
2026-02-17 09:05:22 +00:00
/>
<div className="absolute bottom-6 left-6 right-6 text-white">
<h2 className="text-xl font-semibold md:text-2xl">
{modalArticle.title}
2026-02-17 09:05:22 +00:00
</h2>
<Link
href={articleHref(modalArticle)}
className="mt-3 inline-block text-sm font-medium underline"
onClick={closeModal}
>
Buka halaman artikel
</Link>
2026-02-17 09:05:22 +00:00
</div>
<button
type="button"
2026-02-17 09:05:22 +00:00
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"
2026-02-17 09:05:22 +00:00
>
<ChevronLeft />
</button>
<button
type="button"
2026-02-17 09:05:22 +00:00
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"
2026-02-17 09:05:22 +00:00
>
<ChevronRight />
</button>
</div>
<div className="flex justify-center gap-2 py-4">
{slides.map((_, i) => (
2026-02-17 09:05:22 +00:00
<button
key={i}
type="button"
2026-02-17 09:05:22 +00:00
onClick={() => setActiveModal(i)}
className={`h-2.5 w-2.5 rounded-full ${
2026-02-17 09:05:22 +00:00
activeModal === i ? "bg-white" : "bg-white/40"
}`}
/>
))}
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</>
);
}