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

645 lines
22 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import Image from "next/image";
import { Calendar, 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 } from "@/service/landing/landing";
import { toBase64, shimmer } from "@/utils/globals";
import { Skeleton } from "@/components/ui/skeleton";
import { getArticleDetail } from "@/service/content/content";
export default function ImageDetail({ id }: { id: number }) {
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);
// animasi skeleton loading
useEffect(() => {
const timer = setTimeout(() => {
setIsLoading(false);
}, 3000);
return () => clearTimeout(timer);
}, []);
// salin link ke clipboard
const handleCopyLink = async () => {
try {
await navigator.clipboard.writeText(window.location.href);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error("Gagal menyalin:", 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>
);
// 🔹 Ambil data detail dari API
useEffect(() => {
const fetchDetail = async () => {
try {
setLoading(true);
// 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,
})) || [],
};
setData(mappedData);
} catch (err) {
console.error("Gagal ambil detail artikel:", err);
// fallback ke API lama
// try {
// const fallback = await getDetail(id);
// setData(fallback?.data?.data);
// } catch (err2) {
// console.error("Fallback API juga gagal:", err2);
// }
} finally {
setLoading(false);
}
};
if (id) fetchDetail();
}, [id]);
// 🔹 UI Loading
if (loading) {
return (
<div className="max-w-6xl mx-auto px-4 py-6">
<p>Memuat data...</p>
</div>
);
}
// 🔹 Jika data kosong
if (!data) {
return (
<div className="max-w-6xl mx-auto px-4 py-6">
<p>Data tidak ditemukan</p>
</div>
);
}
// 🔹 Render konten utama
return (
<div className="max-w-6xl mx-auto px-4 py-6 space-y-6">
{/* Gambar Utama */}
{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 ||
data?.thumbnailUrl || // ✅ fallback ke thumbnailUrl
"/nodata.png"
}
alt="Main"
className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-full object-contain"
/>
</div>
)}
{/* Thumbnail bawah */}
{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>
)}
{/* Informasi artikel */}
<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">
<Eye className="w-4 h-4" />
{data.viewCount || 0}
</span>
<span className="text-black">
Creator: {data.creatorGroupLevelName}
</span>
</div>
</div>
{/* Konten artikel */}
<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>
{/* Tombol aksi 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"
>
💬
</Button>
COMMENT
</Link>
</div>
</div>
</div>
</div>
</div>
);
}
// "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>
// );
// }