kontenhumas-fe/components/main/content/image-detail.tsx

647 lines
22 KiB
TypeScript
Raw Normal View History

2025-09-16 08:29:07 +00:00
"use client";
import Image from "next/image";
2025-10-12 15:12:07 +00:00
import { Calendar, Eye } from "lucide-react";
2025-09-16 08:29:07 +00:00
import { Button } from "@/components/ui/button";
import Link from "next/link";
import { useState, useEffect } from "react";
import {
FaFacebookF,
FaTiktok,
FaYoutube,
FaWhatsapp,
FaInstagram,
FaTwitter,
FaCheck,
FaLink,
FaShareAlt,
} from "react-icons/fa";
2025-10-12 15:12:07 +00:00
import { getDetail } from "@/service/landing/landing";
2025-09-16 08:29:07 +00:00
import { toBase64, shimmer } from "@/utils/globals";
import { Skeleton } from "@/components/ui/skeleton";
2025-10-12 15:12:07 +00:00
import { getArticleDetail } from "@/service/content/content";
2025-09-16 08:29:07 +00:00
2025-10-12 16:07:32 +00:00
export default function ImageDetail({ id }: { id: number }) {
2025-09-16 08:29:07 +00:00
const [copied, setCopied] = useState(false);
const [showShareMenu, setShowShareMenu] = useState(false);
const [data, setData] = useState<any>(null);
const [loading, setLoading] = useState(true);
const [selectedImage, setSelectedImage] = useState(0);
const [isLoading, setIsLoading] = useState<any>(true);
2025-10-12 15:12:07 +00:00
// animasi skeleton loading
2025-09-16 08:29:07 +00:00
useEffect(() => {
const timer = setTimeout(() => {
setIsLoading(false);
}, 3000);
return () => clearTimeout(timer);
}, []);
2025-10-12 15:12:07 +00:00
// salin link ke clipboard
2025-09-16 08:29:07 +00:00
const handleCopyLink = async () => {
try {
await navigator.clipboard.writeText(window.location.href);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
2025-10-12 15:12:07 +00:00
console.error("Gagal menyalin:", err);
2025-09-16 08:29:07 +00:00
}
};
const SocialItem = ({
icon,
label,
}: {
icon: React.ReactNode;
label: string;
}) => (
<div className="flex items-center gap-3 cursor-pointer hover:opacity-80">
<div className="bg-[#C6A455] p-2 rounded-full text-white">{icon}</div>
<span className="text-sm">{label}</span>
</div>
);
2025-10-12 15:12:07 +00:00
// 🔹 Ambil data detail dari API
2025-09-16 08:29:07 +00:00
useEffect(() => {
const fetchDetail = async () => {
try {
setLoading(true);
2025-10-12 15:12:07 +00:00
// 1⃣ Coba ambil dari API baru /articles/{id}
const res = await getArticleDetail(id);
console.log("Response dari /articles/{id}:", res);
// if (res?.error || !res?.data?.data) {
// console.warn("Gagal ambil dari API baru, coba fallback ke API lama");
// const fallback = await getArticleDetail(id);
// setData(fallback?.data?.data);
// return;
// }
// 2⃣ Transformasi struktur agar kompatibel dengan UI lama
const article = res?.data?.data;
const mappedData = {
id: article.id,
title: article.title,
description: article.description,
createdAt: article.createdAt,
clickCount: article.viewCount,
creatorGroupLevelName: article.createdByName || "Unknown",
thumbnailUrl: article.thumbnailUrl || "", // ✅ tambahkan ini
uploadedBy: {
publisher: article.createdByName || "MABES POLRI",
},
files:
article.files?.map((f: any) => ({
id: f.id,
url: f.file_url,
fileName: f.file_name,
filePath: f.file_path,
fileThumbnail: f.file_thumbnail,
fileAlt: f.file_alt,
widthPixel: f.width_pixel,
heightPixel: f.height_pixel,
size: f.size,
downloadCount: f.download_count,
createdAt: f.created_at,
updatedAt: f.updated_at,
})) || [],
2025-10-12 15:12:07 +00:00
};
2025-10-12 15:12:07 +00:00
setData(mappedData);
} catch (err) {
console.error("Gagal ambil detail artikel:", err);
// fallback ke API lama
2025-10-12 16:07:32 +00:00
// try {
// const fallback = await getDetail(id);
// setData(fallback?.data?.data);
// } catch (err2) {
// console.error("Fallback API juga gagal:", err2);
// }
2025-09-16 08:29:07 +00:00
} finally {
setLoading(false);
}
};
2025-10-12 15:12:07 +00:00
2025-09-16 08:29:07 +00:00
if (id) fetchDetail();
}, [id]);
2025-10-12 15:12:07 +00:00
// 🔹 UI Loading
2025-09-16 08:29:07 +00:00
if (loading) {
return (
<div className="max-w-6xl mx-auto px-4 py-6">
2025-10-12 15:12:07 +00:00
<p>Memuat data...</p>
2025-09-16 08:29:07 +00:00
</div>
);
}
2025-10-12 15:12:07 +00:00
// 🔹 Jika data kosong
2025-09-16 08:29:07 +00:00
if (!data) {
return (
<div className="max-w-6xl mx-auto px-4 py-6">
<p>Data tidak ditemukan</p>
</div>
);
}
2025-10-12 15:12:07 +00:00
// 🔹 Render konten utama
2025-09-16 08:29:07 +00:00
return (
<div className="max-w-6xl mx-auto px-4 py-6 space-y-6">
2025-10-12 15:12:07 +00:00
{/* Gambar Utama */}
2025-09-16 08:29:07 +00:00
{isLoading ? (
<div className="relative">
<Skeleton className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-[900px]" />
</div>
) : (
<div className="relative">
<Image
placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
width={2560}
height={1440}
2025-10-12 15:12:07 +00:00
src={
2025-10-19 16:07:42 +00:00
data?.files?.[selectedImage]?.url?.trim()
? data.files[selectedImage].url
: data?.thumbnailUrl?.trim()
? data.thumbnailUrl
: "/assets/empty-data.png"
2025-10-12 15:12:07 +00:00
}
2025-09-16 08:29:07 +00:00
alt="Main"
className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-full object-contain"
/>
</div>
)}
2025-10-12 15:12:07 +00:00
{/* Thumbnail bawah */}
2025-09-16 08:29:07 +00:00
{isLoading ? (
<div className="py-4 flex flex-row gap-3">
<Skeleton className="rounded-lg w-[120px] h-[90px]" />
<Skeleton className="rounded-lg w-[120px] h-[90px]" />
<Skeleton className="rounded-lg w-[120px] h-[90px]" />
</div>
) : (
<div className="py-4 px-1 flex flex-row gap-3 flex-wrap">
{data?.files?.map((file: any, index: number) => (
<a onClick={() => setSelectedImage(index)} key={file?.id}>
<Image
placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
width={1920}
height={1080}
alt="image-small"
src={file?.url}
className={`w-[120px] h-[90px] object-cover rounded-md cursor-pointer hover:ring-2 ${
selectedImage === index ? "ring-2 ring-red-600" : ""
}`}
/>
</a>
))}
</div>
)}
2025-10-12 15:12:07 +00:00
{/* Informasi artikel */}
2025-09-16 08:29:07 +00:00
<div className="flex flex-col md:flex-row md:items-center md:justify-between text-sm text-muted-foreground">
<div className="flex flex-wrap items-center gap-2">
2025-10-12 15:12:07 +00:00
{/* <span className="font-semibold text-black border-r-2 pr-2 border-black">
2025-09-16 08:29:07 +00:00
by {data?.uploadedBy?.publisher || "MABES POLRI"}
2025-10-12 15:12:07 +00:00
</span> */}
2025-09-16 08:29:07 +00:00
<span className="flex items-center gap-1 text-black">
<Calendar className="w-4 h-4" />
{new Date(data.createdAt)
.toLocaleString("id-ID", {
day: "2-digit",
month: "short",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
hour12: false,
timeZone: "Asia/Jakarta",
})
.replace(".", ":")}{" "}
2025-10-12 15:12:07 +00:00
WIB |
2025-09-16 08:29:07 +00:00
</span>
<span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black">
<Eye className="w-4 h-4" />
2025-10-12 15:12:07 +00:00
{data.viewCount || 0}
2025-09-16 08:29:07 +00:00
</span>
<span className="text-black">
Creator: {data.creatorGroupLevelName}
</span>
</div>
</div>
2025-10-12 15:12:07 +00:00
{/* Konten artikel */}
2025-09-16 08:29:07 +00:00
<div className="flex flex-col md:flex-row gap-6 mt-6">
<div className="flex-1 space-y-4">
<h1 className="text-xl font-bold">{data.title}</h1>
<div className="text-base text-gray-700 leading-relaxed space-y-3">
<p>{data.description}</p>
</div>
2025-10-12 15:12:07 +00:00
{/* Tombol aksi bawah */}
<div className="flex flex-wrap md:flex-row justify-center gap-4 my-20">
2025-09-16 08:29:07 +00:00
<div className="flex gap-2 items-center">
<Button
onClick={handleCopyLink}
size="lg"
className="justify-start bg-black text-white rounded-full"
>
{copied ? <FaCheck /> : <FaLink />}
</Button>
<span>COPY LINK</span>
</div>
<div className="flex gap-2 items-center">
<Button
onClick={() => setShowShareMenu(!showShareMenu)}
size="lg"
className="justify-start bg-[#C6A455] text-white rounded-full"
>
<FaShareAlt />
</Button>
<span>SHARE</span>
</div>
<div className="flex gap-2 items-center">
<Link href={`/content/video/comment/${id}`}>
2025-09-16 08:29:07 +00:00
<Button
variant="default"
size="lg"
className="justify-start bg-[#FFAD10] rounded-full mr-2 text-white"
>
2025-10-12 15:12:07 +00:00
💬
2025-09-16 08:29:07 +00:00
</Button>
COMMENT
</Link>
</div>
</div>
</div>
</div>
</div>
);
}
2025-10-12 15:12:07 +00:00
// "use client";
// import Image from "next/image";
// import { Calendar, Clock, Eye } from "lucide-react";
// import { Button } from "@/components/ui/button";
// import Link from "next/link";
// import { useState, useEffect } from "react";
// import {
// FaFacebookF,
// FaTiktok,
// FaYoutube,
// FaWhatsapp,
// FaInstagram,
// FaTwitter,
// FaCheck,
// FaLink,
// FaShareAlt,
// } from "react-icons/fa";
// import { getDetail, getArticleDetail } from "@/service/landing/landing";
// import VideoPlayer from "@/utils/video-player";
// import { toBase64, shimmer } from "@/utils/globals";
// import { Skeleton } from "@/components/ui/skeleton";
// export default function ImageDetail({ id }: { id: string }) {
// const [copied, setCopied] = useState(false);
// const [showShareMenu, setShowShareMenu] = useState(false);
// const [data, setData] = useState<any>(null);
// const [loading, setLoading] = useState(true);
// const [selectedImage, setSelectedImage] = useState(0);
// const [isLoading, setIsLoading] = useState<any>(true);
// useEffect(() => {
// const timer = setTimeout(() => {
// setIsLoading(false);
// }, 3000);
// return () => clearTimeout(timer);
// }, []);
// const handleCopyLink = async () => {
// try {
// await navigator.clipboard.writeText(window.location.href);
// setCopied(true);
// setTimeout(() => setCopied(false), 2000);
// } catch (err) {
// console.error("Failed to copy: ", err);
// }
// };
// const SocialItem = ({
// icon,
// label,
// }: {
// icon: React.ReactNode;
// label: string;
// }) => (
// <div className="flex items-center gap-3 cursor-pointer hover:opacity-80">
// <div className="bg-[#C6A455] p-2 rounded-full text-white">{icon}</div>
// <span className="text-sm">{label}</span>
// </div>
// );
// useEffect(() => {
// const fetchDetail = async () => {
// try {
// setLoading(true);
// // Try new Articles API first
// const response = await getArticleDetail(id);
// console.log("Article Detail API response:", response);
// if (response?.error) {
// console.error("Articles API failed, falling back to old API");
// // Fallback to old API
// const fallbackResponse = await getDetail(id);
// setData(fallbackResponse?.data?.data);
// return;
// }
// // Handle new API response structure
// const articleData = response?.data?.data;
// if (articleData) {
// // Transform article data to match old structure for backward compatibility
// const transformedData = {
// id: articleData.id,
// title: articleData.title,
// description: articleData.description,
// createdAt: articleData.createdAt,
// clickCount: articleData.viewCount,
// creatorGroupLevelName: articleData.createdByName || "Unknown",
// uploadedBy: {
// publisher: articleData.createdByName || "MABES POLRI"
// },
// files: articleData.files?.map((file: any) => ({
// id: file.id,
// url: file.file_url,
// fileName: file.file_name,
// filePath: file.file_path,
// fileThumbnail: file.file_thumbnail,
// fileAlt: file.file_alt,
// widthPixel: file.width_pixel,
// heightPixel: file.height_pixel,
// size: file.size,
// downloadCount: file.download_count,
// createdAt: file.created_at,
// updatedAt: file.updated_at,
// ...file
// })) || [],
// };
// console.log("transformedData : ", transformedData.files);
// setData(transformedData);
// }
// } catch (error) {
// console.error("Error fetching detail:", error);
// // Try fallback to old API if new API fails
// try {
// const fallbackResponse = await getDetail(id);
// setData(fallbackResponse?.data?.data);
// } catch (fallbackError) {
// console.error("Fallback API also failed:", fallbackError);
// }
// } finally {
// setLoading(false);
// }
// };
// if (id) fetchDetail();
// }, [id]);
// if (loading) {
// return (
// <div className="max-w-6xl mx-auto px-4 py-6">
// <p>Loading...</p>
// </div>
// );
// }
// if (!data) {
// return (
// <div className="max-w-6xl mx-auto px-4 py-6">
// <p>Data tidak ditemukan</p>
// </div>
// );
// }
// return (
// <div className="max-w-6xl mx-auto px-4 py-6 space-y-6">
// {isLoading ? (
// <div className="relative">
// <Skeleton className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-[900px]" />
// </div>
// ) : (
// <div className="relative">
// <Image
// placeholder={`data:image/svg+xml;base64,${toBase64(
// shimmer(700, 475)
// )}`}
// width={2560}
// height={1440}
// src={data?.files[selectedImage]?.url || "/nodata.png"}
// alt="Main"
// className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-full object-contain"
// />
// <div className="absolute top-4 right-4"></div>
// </div>
// )}
// {/* Gambar bawah Kecil */}
// {isLoading ? (
// <div className="py-4 flex flex-row gap-3">
// <Skeleton className="rounded-lg w-[120px] h-[90px]" />
// <Skeleton className="rounded-lg w-[120px] h-[90px]" />
// <Skeleton className="rounded-lg w-[120px] h-[90px]" />
// </div>
// ) : (
// // <div className="py-4 flex flex-row gap-3">
// // {detailDataImage?.files?.map((file: any, index: number) => (
// // <a onClick={() => setSelectedImage(index)} key={file?.id}>
// // <Image
// // placeholder={`data:image/svg+xml;base64,${toBase64(
// // shimmer(700, 475)
// // )}`}
// // width={1920}
// // height={1080}
// // alt="image-small"
// // src={file?.url}
// // className="w-[120px] h-[90px] object-cover rounded-md cursor-pointer hover:ring-2 hover:ring-red-600"
// // />
// // </a>
// // ))}
// // </div>
// <div className="py-4 px-1 flex flex-row gap-3 flex-wrap">
// {data?.files?.map((file: any, index: number) => (
// <a onClick={() => setSelectedImage(index)} key={file?.id}>
// <Image
// placeholder={`data:image/svg+xml;base64,${toBase64(
// shimmer(700, 475)
// )}`}
// width={1920}
// height={1080}
// alt="image-small"
// src={file?.url}
// className={`w-[120px] h-[90px] object-cover rounded-md cursor-pointer hover:ring-2 ${
// selectedImage === index ? "ring-2 ring-red-600" : ""
// }`}
// />
// </a>
// ))}
// </div>
// )}
// <div className="flex flex-col md:flex-row md:items-center md:justify-between text-sm text-muted-foreground">
// <div className="flex flex-wrap items-center gap-2">
// <span className="font-semibold text-black border-r-2 pr-2 border-black">
// by {data?.uploadedBy?.publisher || "MABES POLRI"}
// </span>
// <span className="flex items-center gap-1 text-black">
// <Calendar className="w-4 h-4" />
// {new Date(data.createdAt)
// .toLocaleString("id-ID", {
// day: "2-digit",
// month: "short",
// year: "numeric",
// hour: "2-digit",
// minute: "2-digit",
// hour12: false,
// timeZone: "Asia/Jakarta",
// })
// .replace(".", ":")}{" "}
// WIB
// </span>
// {/* <span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black">
// <Clock className="w-4 h-4" />
// {data.time || "-"}
// </span> */}
// <span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black">
// <Eye className="w-4 h-4" />
// {data.clickCount || 0}
// </span>
// <span className="text-black">
// {" "}
// Creator: {data.creatorGroupLevelName}
// </span>
// </div>
// </div>
// <div className="flex flex-col md:flex-row gap-6 mt-6">
// {/* Sidebar actions */}
// {/* <div className="hidden md:flex flex-col gap-4 relative z-10">
// <div className="flex gap-2 items-center">
// <Button
// onClick={handleCopyLink}
// size="lg"
// className="justify-start bg-black text-white rounded-full"
// >
// {copied ? <FaCheck /> : <FaLink />}
// </Button>
// <span>COPY LINK</span>
// </div>
// <div className="flex gap-2 items-center relative">
// <Button
// onClick={() => setShowShareMenu(!showShareMenu)}
// size="lg"
// className="justify-start bg-[#C6A455] text-white rounded-full"
// >
// <FaShareAlt />
// </Button>
// <span>SHARE</span>
// {showShareMenu && (
// <div className="absolute left-16 top-0 bg-white p-4 rounded-lg shadow-lg flex flex-col gap-3 w-48">
// <SocialItem icon={<FaFacebookF />} label="Facebook" />
// <SocialItem icon={<FaTiktok />} label="TikTok" />
// <SocialItem icon={<FaYoutube />} label="YouTube" />
// <SocialItem icon={<FaWhatsapp />} label="WhatsApp" />
// <SocialItem icon={<FaInstagram />} label="Instagram" />
// <SocialItem icon={<FaTwitter />} label="Twitter" />
// </div>
// )}
// </div>
// <div className="flex gap-2 items-center">
// <Link href={`/content/video/comment/${id}`}>
// <Button
// variant="default"
// size="lg"
// className="justify-start bg-[#FFAD10] rounded-full mr-2 text-white"
// >
// <svg
// xmlns="http://www.w3.org/2000/svg"
// width="15"
// height="15"
// viewBox="0 0 24 24"
// >
// <path
// fill="currentColor"
// d="M10 3h4a8 8 0 1 1 0 16v3.5c-5-2-12-5-12-11.5a8 8 0 0 1 8-8"
// />
// </svg>
// </Button>
// COMMENT
// </Link>
// </div>
// </div> */}
// {/* Content */}
// <div className="flex-1 space-y-4">
// <h1 className="text-xl font-bold">{data.title}</h1>
// <div className="text-base text-gray-700 leading-relaxed space-y-3">
// <p>{data.description}</p>
// </div>
// {/* Actions bawah */}
// <div className="flex flex-wrap md:flex-row justify-center gap-4 my-20">
// <div className="flex gap-2 items-center">
// <Button
// onClick={handleCopyLink}
// size="lg"
// className="justify-start bg-black text-white rounded-full"
// >
// {copied ? <FaCheck /> : <FaLink />}
// </Button>
// <span>COPY LINK</span>
// </div>
// <div className="flex gap-2 items-center">
// <Button
// onClick={() => setShowShareMenu(!showShareMenu)}
// size="lg"
// className="justify-start bg-[#C6A455] text-white rounded-full"
// >
// <FaShareAlt />
// </Button>
// <span>SHARE</span>
// </div>
// <div className="flex gap-2 items-center">
// <Link href={`/content/video/comment/${id}`}>
// <Button
// variant="default"
// size="lg"
// className="justify-start bg-[#FFAD10] rounded-full mr-2 text-white"
// >
// <svg
// xmlns="http://www.w3.org/2000/svg"
// width="15"
// height="15"
// viewBox="0 0 24 24"
// >
// <path
// fill="currentColor"
// d="M10 3h4a8 8 0 1 1 0 16v3.5c-5-2-12-5-12-11.5a8 8 0 0 1 8-8"
// />
// </svg>
// </Button>
// COMMENT
// </Link>
// </div>
// </div>
// </div>
// </div>
// </div>
// );
// }