kontenhumas-fe/components/landing-page/header.tsx

742 lines
22 KiB
TypeScript
Raw Normal View History

2025-09-16 08:29:07 +00:00
"use client";
2025-09-16 08:29:07 +00:00
import Image from "next/image";
import Link from "next/link";
import { ThumbsUp, ThumbsDown } from "lucide-react";
import { useEffect, useState } from "react";
import { useParams, useRouter } from "next/navigation";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { listData, listArticles } from "@/service/landing/landing";
import { getCookiesDecrypt } from "@/lib/utils";
import { toggleBookmark, getBookmarkSummaryForUser } from "@/service/content";
2025-09-16 08:29:07 +00:00
2025-10-31 16:21:05 +00:00
import { Swiper, SwiperSlide } from "swiper/react";
import { Navigation, Pagination } from "swiper/modules";
import "swiper/css";
import "swiper/css/navigation";
import "swiper/css/pagination";
import ImageBlurry from "../ui/image-blurry";
import { useTranslations } from "next-intl";
2026-03-06 04:19:17 +00:00
import { Reveal } from "../ui/Reveal";
import { RevealL } from "../ui/RevealL";
import { RevealR } from "../ui/RevealR";
2025-10-31 16:21:05 +00:00
const images = ["/PPS.png", "/PPS2.jpeg", "/PPS3.jpg", "/PPS4.png"];
2025-09-16 08:29:07 +00:00
export default function Header() {
2025-10-31 16:21:05 +00:00
const t = useTranslations("MediaUpdate");
2025-09-16 08:29:07 +00:00
const [data, setData] = useState<any[]>([]);
const [bookmarkedIds, setBookmarkedIds] = useState<Set<number>>(new Set());
const router = useRouter();
const params = useParams();
const MySwal = withReactContent(Swal);
2025-10-31 16:21:05 +00:00
const slug = params?.slug as string;
2025-09-16 08:29:07 +00:00
useEffect(() => {
const fetchData = async () => {
try {
2025-10-08 09:57:36 +00:00
const response = await listArticles(
1,
5,
2025-10-31 16:21:05 +00:00
1,
2025-10-08 09:57:36 +00:00
undefined,
undefined,
"createdAt",
2026-03-06 04:19:17 +00:00
slug,
2025-10-08 09:57:36 +00:00
);
let articlesData: any[] = [];
if (response?.error) {
2025-10-31 16:21:05 +00:00
const fallbackResponse = await listData(
"",
"",
"",
5,
0,
"createdAt",
"",
"",
2026-03-06 04:19:17 +00:00
"",
2025-10-31 16:21:05 +00:00
);
articlesData = (fallbackResponse?.data?.data?.content || []).filter(
2026-03-06 04:19:17 +00:00
(item: any) => item.typeId === 1,
);
} else {
2025-10-31 16:21:05 +00:00
articlesData = (response?.data?.data || []).filter(
2026-03-06 04:19:17 +00:00
(item: any) => item.typeId === 1,
2025-10-31 16:21:05 +00:00
);
}
2025-10-31 16:21:05 +00:00
const transformed = articlesData.map((article: any) => ({
id: article.id,
title: article.title,
categoryName:
article.categoryName ||
(article.categories && article.categories[0]?.title) ||
"",
createdAt: article.createdAt,
smallThumbnailLink: article.thumbnailUrl,
fileTypeId: article.typeId,
clientName: article.clientName,
categories: article.categories,
label: "Image",
}));
2025-10-08 09:57:36 +00:00
setData(transformed);
2025-09-16 08:29:07 +00:00
const roleId = Number(getCookiesDecrypt("urie"));
2025-10-31 16:21:05 +00:00
if (roleId && !isNaN(roleId)) {
const userId = getCookiesDecrypt("uie");
const localKey = `bookmarkedIds_${userId || "guest"}`;
const saved = localStorage.getItem(localKey);
2025-10-13 02:57:44 +00:00
2025-10-31 16:21:05 +00:00
let localSet = new Set<number>();
if (saved) {
localSet = new Set(JSON.parse(saved));
setBookmarkedIds(localSet);
}
2025-10-13 02:57:44 +00:00
const res = await getBookmarkSummaryForUser();
const bookmarks =
res?.data?.data?.recentBookmarks ||
res?.data?.data?.bookmarks ||
res?.data?.data ||
[];
const ids = new Set<number>(
(Array.isArray(bookmarks) ? bookmarks : [])
.map((b: any) => Number(b.articleId ?? b.id ?? b.article?.id))
2026-03-06 04:19:17 +00:00
.filter((x) => !isNaN(x)),
);
2025-10-13 02:57:44 +00:00
2025-10-31 16:21:05 +00:00
const merged = new Set([...localSet, ...ids]);
setBookmarkedIds(merged);
localStorage.setItem(
"bookmarkedIds",
2026-03-06 04:19:17 +00:00
JSON.stringify(Array.from(merged)),
);
}
2025-10-31 16:21:05 +00:00
} catch (error) {
console.error("Gagal memuat data:", error);
2025-10-13 02:57:44 +00:00
}
};
2025-10-31 16:21:05 +00:00
fetchData();
}, []);
2025-10-31 16:21:05 +00:00
useEffect(() => {
if (bookmarkedIds.size > 0) {
localStorage.setItem(
"bookmarkedIds",
2026-03-06 04:19:17 +00:00
JSON.stringify(Array.from(bookmarkedIds)),
2025-10-31 16:21:05 +00:00
);
}
}, [bookmarkedIds]);
2025-09-16 08:29:07 +00:00
return (
2026-03-06 04:19:17 +00:00
<RevealR>
<section className="max-w-[1350px] mx-auto px-4">
<div className="flex flex-col lg:flex-row gap-6 py-6">
{data.length > 0 && (
<Card
2026-03-06 04:19:17 +00:00
item={data[0]}
isBig
isInitiallyBookmarked={bookmarkedIds.has(Number(data[0].id))}
2025-10-31 16:21:05 +00:00
onSaved={(id) =>
setBookmarkedIds((prev) => new Set([...prev, Number(id)]))
}
/>
2026-03-06 04:19:17 +00:00
)}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 w-full">
{data.slice(1, 5).map((item) => (
<Card
key={item.id}
item={item}
isInitiallyBookmarked={bookmarkedIds.has(Number(item.id))}
onSaved={(id) =>
setBookmarkedIds((prev) => new Set([...prev, Number(id)]))
}
/>
))}
</div>
2025-09-16 08:29:07 +00:00
</div>
2026-03-06 04:19:17 +00:00
<div className="relative w-full h-48 sm:h-64 md:h-80 lg:h-[460px] mt-4 rounded-xl overflow-hidden">
<Swiper
modules={[Navigation, Pagination]}
navigation
pagination={{ clickable: true }}
spaceBetween={10}
slidesPerView={1}
loop={true}
className="w-full h-full"
>
{images.map((img, index) => (
<SwiperSlide key={index}>
<div className="relative w-full h-48 sm:h-64 md:h-80 lg:h-[460px]">
{/* <Image
2025-10-31 16:21:05 +00:00
src={img}
alt={`slide-${index}`}
fill
className="object-cover rounded-xl"
priority={index === 0}
/> */}
2026-03-06 04:19:17 +00:00
<ImageBlurry
priority
src={img}
alt="gambar"
style={{
objectFit: "contain",
width: "100%",
height: "100%",
}}
/>
</div>
</SwiperSlide>
))}
</Swiper>
</div>
</section>
</RevealR>
2025-09-16 08:29:07 +00:00
);
}
function Card({
item,
isBig = false,
2025-10-31 16:21:05 +00:00
isInitiallyBookmarked = false,
onSaved,
}: {
item: any;
isBig?: boolean;
2025-10-31 16:21:05 +00:00
isInitiallyBookmarked?: boolean;
onSaved?: (id: number) => void;
}) {
const router = useRouter();
2025-10-31 16:21:05 +00:00
const t = useTranslations("MediaUpdate");
const MySwal = withReactContent(Swal);
const [isSaving, setIsSaving] = useState(false);
2025-10-31 16:21:05 +00:00
const [isBookmarked, setIsBookmarked] = useState(isInitiallyBookmarked);
2026-01-05 12:28:35 +00:00
const DEFAULT_IMAGE = "/assets/logo1.png";
2026-03-06 04:19:17 +00:00
const [imageSrc, setImageSrc] = useState(DEFAULT_IMAGE);
2026-01-05 12:28:35 +00:00
useEffect(() => {
2026-03-06 04:19:17 +00:00
const src = item?.smallThumbnailLink;
if (!src) {
setImageSrc(DEFAULT_IMAGE);
return;
}
const img = new window.Image();
img.src = src;
img.onload = () => {
setImageSrc(src);
};
img.onerror = () => {
setImageSrc(DEFAULT_IMAGE);
};
2026-01-05 12:28:35 +00:00
}, [item?.smallThumbnailLink]);
2025-10-31 16:21:05 +00:00
useEffect(() => {
setIsBookmarked(isInitiallyBookmarked);
}, [isInitiallyBookmarked]);
const getLink = () => `/content/image/detail/${item?.id}`;
2025-10-31 16:21:05 +00:00
const handleSave = async () => {
const roleId = Number(getCookiesDecrypt("urie"));
2025-10-31 16:21:05 +00:00
if (!roleId || isNaN(roleId)) {
MySwal.fire({
icon: "warning",
title: "Login diperlukan",
text: "Silakan login terlebih dahulu untuk menyimpan artikel.",
confirmButtonText: "Login Sekarang",
confirmButtonColor: "#d33",
}).then(() => router.push("/auth"));
return;
}
try {
setIsSaving(true);
const res = await toggleBookmark(item.id);
if (res?.error) {
MySwal.fire({
icon: "error",
title: "Gagal",
2025-10-31 16:21:05 +00:00
text: "Gagal menyimpan artikel.",
confirmButtonColor: "#d33",
});
} else {
2025-10-31 16:21:05 +00:00
setIsBookmarked(true);
onSaved?.(item.id);
2025-10-31 16:21:05 +00:00
const saved = localStorage.getItem("bookmarkedIds");
const newSet = new Set<number>(saved ? JSON.parse(saved) : []);
newSet.add(Number(item.id));
localStorage.setItem(
"bookmarkedIds",
2026-03-06 04:19:17 +00:00
JSON.stringify(Array.from(newSet)),
);
MySwal.fire({
icon: "success",
2025-10-31 16:21:05 +00:00
title: "Berhasil",
text: "Artikel berhasil disimpan ke bookmark.",
confirmButtonColor: "#3085d6",
timer: 1500,
showConfirmButton: false,
});
}
} catch (err) {
console.error("Toggle bookmark error:", err);
MySwal.fire({
icon: "error",
title: "Kesalahan",
text: "Terjadi kesalahan saat menyimpan artikel.",
confirmButtonColor: "#d33",
});
} finally {
setIsSaving(false);
}
};
2025-09-16 08:29:07 +00:00
return (
2026-03-06 04:19:17 +00:00
<RevealL>
2025-10-31 16:21:05 +00:00
<div
2026-02-05 11:02:24 +00:00
className={`rounded-xl overflow-hidden shadow hover:shadow-lg transition-all bg-white dark:bg-black dark:border dark:border-slate-50 ${
2025-10-31 16:21:05 +00:00
isBig
? "w-full lg:max-w-[670px] lg:min-h-[680px]"
: "w-full h-[350px] md:h-[330px]"
}`}
>
<div
className={`relative ${
isBig ? "aspect-[3/2] lg:h-[525px]" : "aspect-video"
} w-full`}
>
<Link href={getLink()}>
2026-01-05 12:28:35 +00:00
{/* <Image
2025-10-31 16:21:05 +00:00
src={item.smallThumbnailLink || "/contributor.png"}
alt={item.title}
fill
className="object-cover"
2026-01-05 12:28:35 +00:00
/> */}
2026-03-06 04:19:17 +00:00
<ImageBlurry
2026-01-05 12:28:35 +00:00
src={imageSrc}
alt={item.title}
2026-03-06 04:19:17 +00:00
className="w-full h-full object-contain"
2025-10-31 16:21:05 +00:00
/>
</Link>
</div>
2025-09-16 08:29:07 +00:00
2025-10-31 16:21:05 +00:00
<div className="py-[26px] px-4 space-y-2">
2026-01-07 16:03:38 +00:00
<div className="flex justify-between items-center gap-2 text-xs font-semibold flex-row">
2025-09-16 08:29:07 +00:00
<span className="bg-emerald-600 text-white px-2 py-0.5 rounded">
{item.clientName}
2025-09-16 08:29:07 +00:00
</span>
2025-10-08 09:57:36 +00:00
<span className="text-orange-600">
{item.categories?.map((cat: any) => cat.title).join(", ")}
</span>
2025-09-16 08:29:07 +00:00
</div>
2025-09-16 08:29:07 +00:00
<div className="text-xs text-gray-500">
{new Date(item.createdAt)
.toLocaleString("id-ID", {
day: "2-digit",
month: "short",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
hour12: false,
timeZone: "Asia/Jakarta",
})
.replace(".", ":")}{" "}
WIB
</div>
<Link href={getLink()}>
<h3 className="text-sm font-semibold leading-snug line-clamp-2">
{item.title}
</h3>
</Link>
2025-09-16 08:29:07 +00:00
2025-10-31 16:21:05 +00:00
<div className="flex justify-between items-center pt-2">
<div className="flex gap-2 text-gray-500">
<ThumbsUp
size={18}
className="cursor-pointer hover:text-[#F60100]"
/>
<ThumbsDown
size={18}
className="cursor-pointer hover:text-red-600"
/>
</div>
<button
onClick={handleSave}
disabled={isSaving || isBookmarked}
className={`text-sm px-3 py-1 rounded-md transition-all duration-200 ${
isBookmarked
? "bg-gray-400 text-white cursor-not-allowed"
: "bg-[#F60100] text-white hover:bg-[#c90000]"
}`}
>
{isSaving ? t("saving") : isBookmarked ? t("saved") : t("save")}
</button>
2025-09-16 08:29:07 +00:00
</div>
</div>
</div>
2026-03-06 04:19:17 +00:00
</RevealL>
2025-09-16 08:29:07 +00:00
);
}
2025-10-13 02:57:44 +00:00
// "use client";
// import Image from "next/image";
// import Link from "next/link";
// import { ThumbsUp, ThumbsDown } from "lucide-react";
// import { useEffect, useState } from "react";
// import { useParams, useRouter } from "next/navigation";
// import Swal from "sweetalert2";
// import withReactContent from "sweetalert2-react-content";
// import { listData, listArticles } from "@/service/landing/landing";
// import { getCookiesDecrypt } from "@/lib/utils";
// import { toggleBookmark, getBookmarkSummaryForUser } from "@/service/content";
// export default function Header() {
// const [data, setData] = useState<any[]>([]);
// const [bookmarkedIds, setBookmarkedIds] = useState<Set<number>>(new Set());
// const router = useRouter();
// const params = useParams();
// const MySwal = withReactContent(Swal);
// const slug = params?.slug as string;
2025-10-31 16:21:05 +00:00
// // ✅ Ambil data artikel (khusus typeId = 1 -> Image)
2025-10-13 02:57:44 +00:00
// useEffect(() => {
// const fetchData = async () => {
// try {
// const response = await listArticles(
// 1,
// 5,
2025-10-31 16:21:05 +00:00
// 1, // hanya typeId = 1 (image)
2025-10-13 02:57:44 +00:00
// undefined,
// undefined,
// "createdAt",
// slug
// );
// let articlesData: any[] = [];
// if (response?.error) {
2025-10-31 16:21:05 +00:00
// const fallback = await listData(
2025-10-13 02:57:44 +00:00
// "",
// "",
// "",
// 5,
// 0,
// "createdAt",
// "",
// "",
2025-10-31 16:21:05 +00:00
// "1"
2025-10-13 02:57:44 +00:00
// );
2025-10-31 16:21:05 +00:00
// articlesData = fallback?.data?.data?.content || [];
2025-10-13 02:57:44 +00:00
// } else {
// articlesData = response?.data?.data || [];
// }
2025-10-31 16:21:05 +00:00
// const transformed = articlesData.map((article: any) =>
// itemTransform(article)
// );
2025-10-13 02:57:44 +00:00
// setData(transformed);
2025-10-31 16:21:05 +00:00
// } catch (error) {
// console.error("Gagal memuat data:", error);
// }
// };
// fetchData();
// }, [slug]);
2025-10-13 02:57:44 +00:00
2025-10-31 16:21:05 +00:00
// // ✅ Sinkronisasi bookmark: dari localStorage + backend user login
// useEffect(() => {
// const syncBookmarks = async () => {
// try {
2025-10-13 02:57:44 +00:00
// const roleId = Number(getCookiesDecrypt("urie"));
2025-10-31 16:21:05 +00:00
// let localSet = new Set<number>();
2025-10-13 02:57:44 +00:00
2025-10-31 16:21:05 +00:00
// const simpananLocal = localStorage.getItem("bookmarkedIds");
// if (simpananLocal) {
// localSet = new Set(JSON.parse(simpananLocal));
// }
2025-10-13 02:57:44 +00:00
2025-10-31 16:21:05 +00:00
// // Jika user login, gabungkan dengan data dari backend
// if (roleId && !isNaN(roleId)) {
2025-10-13 02:57:44 +00:00
// const res = await getBookmarkSummaryForUser();
// const bookmarks =
// res?.data?.data?.recentBookmarks ||
// res?.data?.data?.bookmarks ||
// res?.data?.data ||
// [];
// const ids = new Set<number>(
// (Array.isArray(bookmarks) ? bookmarks : [])
// .map((b: any) => Number(b.articleId ?? b.id ?? b.article?.id))
// .filter((x) => !isNaN(x))
// );
2025-10-31 16:21:05 +00:00
// const gabungan = new Set([...localSet, ...ids]);
// setBookmarkedIds(gabungan);
2025-10-13 02:57:44 +00:00
// localStorage.setItem(
// "bookmarkedIds",
2025-10-31 16:21:05 +00:00
// JSON.stringify(Array.from(gabungan))
2025-10-13 02:57:44 +00:00
// );
2025-10-31 16:21:05 +00:00
// } else {
// // Jika belum login, pakai local saja
// setBookmarkedIds(localSet);
2025-10-13 02:57:44 +00:00
// }
2025-10-31 16:21:05 +00:00
// } catch (err) {
// console.error("Gagal sinkronisasi bookmark:", err);
2025-10-13 02:57:44 +00:00
// }
// };
2025-10-31 16:21:05 +00:00
// syncBookmarks();
2025-10-13 02:57:44 +00:00
// }, []);
// return (
// <section className="max-w-[1350px] mx-auto px-4">
// <div className="flex flex-col lg:flex-row gap-6 py-6">
// {data.length > 0 && (
// <Card
2025-10-31 16:21:05 +00:00
// key={data[0].id}
2025-10-13 02:57:44 +00:00
// item={data[0]}
// isBig
2025-10-31 16:21:05 +00:00
// bookmarkedIds={bookmarkedIds}
// setBookmarkedIds={setBookmarkedIds}
2025-10-13 02:57:44 +00:00
// />
// )}
// <div className="grid grid-cols-1 sm:grid-cols-2 gap-4 w-full">
// {data.slice(1, 5).map((item) => (
// <Card
// key={item.id}
// item={item}
2025-10-31 16:21:05 +00:00
// bookmarkedIds={bookmarkedIds}
// setBookmarkedIds={setBookmarkedIds}
2025-10-13 02:57:44 +00:00
// />
// ))}
// </div>
// </div>
// <div className="relative w-full h-48 sm:h-64 md:h-80 lg:h-[460px] mt-4 rounded-xl">
// <Image
// src={"/PPS.png"}
// alt={"pps"}
// fill
// className="object-cover rounded-xl"
// />
// </div>
// </section>
// );
// }
2025-10-31 16:21:05 +00:00
// // 🔹 Helper function
// function itemTransform(article: any) {
// return {
// id: article.id,
// title: article.title,
// categoryName:
// article.categoryName ||
// (article.categories && article.categories[0]?.title) ||
// "",
// createdAt: article.createdAt,
// smallThumbnailLink: article.thumbnailUrl,
// fileTypeId: article.typeId,
// clientName: article.clientName,
// categories: article.categories,
// label:
// article.typeId === 1
// ? "Image"
// : article.typeId === 2
// ? "Video"
// : article.typeId === 3
// ? "Text"
// : article.typeId === 4
// ? "Audio"
// : "",
// };
// }
// // 🔹 Komponen Card
2025-10-13 02:57:44 +00:00
// function Card({
// item,
// isBig = false,
2025-10-31 16:21:05 +00:00
// bookmarkedIds,
// setBookmarkedIds,
2025-10-13 02:57:44 +00:00
// }: {
// item: any;
// isBig?: boolean;
2025-10-31 16:21:05 +00:00
// bookmarkedIds: Set<number>;
// setBookmarkedIds: React.Dispatch<React.SetStateAction<Set<number>>>;
2025-10-13 02:57:44 +00:00
// }) {
// const router = useRouter();
// const MySwal = withReactContent(Swal);
// const [isSaving, setIsSaving] = useState(false);
2025-10-31 16:21:05 +00:00
// const isBookmarked = bookmarkedIds.has(Number(item.id));
2025-10-13 02:57:44 +00:00
2025-10-31 16:21:05 +00:00
// const getLink = () => `/content/image/detail/${item?.id}`;
2025-10-13 02:57:44 +00:00
2025-10-31 16:21:05 +00:00
// const handleToggleBookmark = async () => {
// const roleId = Number(getCookiesDecrypt("urie"));
2025-10-13 02:57:44 +00:00
// if (!roleId || isNaN(roleId)) {
// MySwal.fire({
// icon: "warning",
// title: "Login diperlukan",
// text: "Silakan login terlebih dahulu untuk menyimpan artikel.",
// confirmButtonText: "Login Sekarang",
// confirmButtonColor: "#d33",
// }).then(() => router.push("/auth"));
// return;
// }
// try {
// setIsSaving(true);
// const res = await toggleBookmark(item.id);
// if (res?.error) {
// MySwal.fire({
// icon: "error",
// title: "Gagal",
2025-10-31 16:21:05 +00:00
// text: "Gagal memperbarui bookmark.",
2025-10-13 02:57:44 +00:00
// confirmButtonColor: "#d33",
// });
// } else {
2025-10-31 16:21:05 +00:00
// const updated = new Set(bookmarkedIds);
// let pesan = "";
// if (isBookmarked) {
// updated.delete(Number(item.id));
// pesan = "Dihapus dari bookmark.";
// } else {
// updated.add(Number(item.id));
// pesan = "Artikel disimpan ke bookmark.";
// }
2025-10-13 02:57:44 +00:00
2025-10-31 16:21:05 +00:00
// setBookmarkedIds(updated);
2025-10-13 02:57:44 +00:00
// localStorage.setItem(
// "bookmarkedIds",
2025-10-31 16:21:05 +00:00
// JSON.stringify(Array.from(updated))
2025-10-13 02:57:44 +00:00
// );
// MySwal.fire({
// icon: "success",
2025-10-31 16:21:05 +00:00
// title: isBookmarked ? "Dihapus!" : "Disimpan!",
// text: pesan,
2025-10-13 02:57:44 +00:00
// timer: 1500,
// showConfirmButton: false,
// });
// }
// } catch (err) {
// console.error("Toggle bookmark error:", err);
// MySwal.fire({
// icon: "error",
// title: "Kesalahan",
// text: "Terjadi kesalahan saat menyimpan artikel.",
// confirmButtonColor: "#d33",
// });
// } finally {
// setIsSaving(false);
// }
// };
// return (
2025-10-31 16:21:05 +00:00
// <div
// className={`rounded-xl overflow-hidden shadow hover:shadow-lg transition-all bg-white flex flex-col ${
// isBig
// ? "w-full lg:max-w-[670px] h-[680px]"
// : "w-full h-[360px] md:h-[340px]"
// }`}
// >
// <div className={`relative ${isBig ? "h-[420px]" : "h-[180px]"} w-full`}>
// <Link href={getLink()}>
// <Image
// src={item.smallThumbnailLink || "/contributor.png"}
// alt={item.title}
// fill
// className="object-cover"
// />
// </Link>
// </div>
2025-10-13 02:57:44 +00:00
2025-10-31 16:21:05 +00:00
// <div className="p-4 flex flex-col justify-between flex-1">
// <div className="space-y-2">
2025-10-13 02:57:44 +00:00
// <div className="flex items-center gap-2 text-xs font-semibold flex-wrap">
// <span className="bg-emerald-600 text-white px-2 py-0.5 rounded">
// {item.clientName}
// </span>
// <span className="text-orange-600">
// {item.categories?.map((cat: any) => cat.title).join(", ")}
// </span>
// </div>
// <div className="text-xs text-gray-500">
// {new Date(item.createdAt)
// .toLocaleString("id-ID", {
// day: "2-digit",
// month: "short",
// year: "numeric",
// hour: "2-digit",
// minute: "2-digit",
// hour12: false,
// timeZone: "Asia/Jakarta",
// })
// .replace(".", ":")}{" "}
// WIB
// </div>
// <Link href={getLink()}>
// <h3 className="text-sm font-semibold leading-snug line-clamp-2">
// {item.title}
// </h3>
// </Link>
2025-10-31 16:21:05 +00:00
// </div>
2025-10-13 02:57:44 +00:00
2025-10-31 16:21:05 +00:00
// <div className="flex justify-between items-center pt-4">
// <div className="flex gap-2 text-gray-500">
// <ThumbsUp
// size={18}
// className="cursor-pointer hover:text-[#F60100]"
// />
// <ThumbsDown
// size={18}
// className="cursor-pointer hover:text-red-600"
// />
2025-10-13 02:57:44 +00:00
// </div>
2025-10-31 16:21:05 +00:00
// <button
// onClick={handleToggleBookmark}
// disabled={isSaving || isBookmarked}
// className={`text-sm px-3 py-1 rounded-md transition-all duration-200 ${
// isBookmarked
// ? "bg-gray-400 text-white cursor-not-allowed"
// : "bg-[#F60100] text-white hover:bg-[#c90000]"
// }`}
// >
// {isSaving ? "Menyimpan" : isBookmarked ? "Tersimpan" : "Simpan"}
// </button>
2025-10-13 02:57:44 +00:00
// </div>
// </div>
// </div>
// );
// }