fix: all detail content
This commit is contained in:
parent
1ce44acc3c
commit
ef4887cfc1
|
|
@ -1,158 +1,152 @@
|
|||
"use client";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { Calendar, Eye } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
FaFacebookF,
|
||||
FaTiktok,
|
||||
FaYoutube,
|
||||
FaWhatsapp,
|
||||
FaInstagram,
|
||||
FaTwitter,
|
||||
FaCheck,
|
||||
FaLink,
|
||||
FaShareAlt,
|
||||
} from "react-icons/fa";
|
||||
import { getDetail } from "@/service/landing/landing";
|
||||
import { BarWave } from "react-cssfx-loading";
|
||||
import WaveSurfer from "wavesurfer.js";
|
||||
import { getArticleDetail } from "@/service/content/content";
|
||||
|
||||
export default function AudioDetail({ id }: { id: number }) {
|
||||
const [data, setData] = useState<any>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [showShareMenu, setShowShareMenu] = useState(false);
|
||||
const [audioLoaded, setAudioLoaded] = useState(false);
|
||||
const [playing, setPlaying] = useState(false);
|
||||
const [volume, setVolume] = useState(0.5);
|
||||
const waveformRef = useRef<HTMLDivElement>(null);
|
||||
const wavesurfer = useRef<any>(null);
|
||||
const [selectedAudio, setSelectedAudio] = useState(0);
|
||||
|
||||
// Salin tautan
|
||||
const handleCopyLink = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(window.location.href);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
} catch (err) {
|
||||
console.error("Gagal menyalin link:", 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 audio dari API lama
|
||||
useEffect(() => {
|
||||
const fetchDetail = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// 1️⃣ Coba API baru
|
||||
const res = await getArticleDetail(id);
|
||||
console.log("Audio detail API response:", res);
|
||||
|
||||
const article = res?.data?.data;
|
||||
|
||||
if (article) {
|
||||
const mappedData = {
|
||||
id: article.id,
|
||||
title: article.title,
|
||||
description: article.description,
|
||||
createdAt: article.createdAt,
|
||||
clickCount: article.viewCount,
|
||||
creatorGroupLevelName: article.createdByName || "Unknown",
|
||||
uploadedBy: {
|
||||
publisher: article.createdByName || "MABES POLRI",
|
||||
},
|
||||
files:
|
||||
article.files?.map((f: any) => ({
|
||||
id: f.id,
|
||||
fileName: f.file_name,
|
||||
url: f.file_url,
|
||||
secondaryUrl: f.file_url,
|
||||
fileAlt: f.file_alt,
|
||||
size: f.size,
|
||||
createdAt: f.created_at,
|
||||
updatedAt: f.updated_at,
|
||||
})) || [],
|
||||
};
|
||||
setData(mappedData);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2️⃣ Fallback ke API lama
|
||||
// const fallback = await getDetail(id);
|
||||
// setData(fallback?.data?.data);
|
||||
} catch (err) {
|
||||
console.error("Gagal memuat audio:", err);
|
||||
// try {
|
||||
// const fallback = await getDetail(id);
|
||||
// setData(fallback?.data?.data);
|
||||
// } catch (err2) {
|
||||
// console.error("Fallback gagal:", err2);
|
||||
// }
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
const res = await getArticleDetail(id);
|
||||
const detail = res?.data?.data;
|
||||
if (detail) setData(detail);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
if (id) fetchDetail();
|
||||
fetchData();
|
||||
}, [id]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto px-4 py-6">
|
||||
<p>Memuat data audio...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// Inisialisasi WaveSurfer
|
||||
useEffect(() => {
|
||||
if (!data?.files?.[selectedAudio]?.url) return;
|
||||
|
||||
if (!data) {
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto px-4 py-6">
|
||||
<p>Data tidak ditemukan</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// Hancurkan instance sebelumnya jika ada
|
||||
if (wavesurfer.current) wavesurfer.current.destroy();
|
||||
|
||||
const url = data.files[selectedAudio].url;
|
||||
|
||||
const options = {
|
||||
container: waveformRef.current!,
|
||||
waveColor: "#E0E0E0",
|
||||
progressColor: "#FFC831",
|
||||
cursorColor: "#000",
|
||||
barWidth: 2,
|
||||
barRadius: 2,
|
||||
responsive: true,
|
||||
height: 80,
|
||||
normalize: true,
|
||||
partialRender: true,
|
||||
};
|
||||
|
||||
const ws = WaveSurfer.create(options);
|
||||
wavesurfer.current = ws;
|
||||
ws.load(url);
|
||||
|
||||
ws.on("ready", () => {
|
||||
setAudioLoaded(true);
|
||||
ws.setVolume(volume);
|
||||
});
|
||||
|
||||
ws.on("finish", () => {
|
||||
setPlaying(false);
|
||||
});
|
||||
|
||||
return () => {
|
||||
ws.destroy();
|
||||
};
|
||||
}, [data?.files, selectedAudio]);
|
||||
|
||||
const handlePlayPause = () => {
|
||||
if (!wavesurfer.current) return;
|
||||
wavesurfer.current.playPause();
|
||||
setPlaying(!playing);
|
||||
};
|
||||
|
||||
const handleVolumeChange = (e: any) => {
|
||||
const vol = parseFloat(e.target.value);
|
||||
setVolume(vol);
|
||||
wavesurfer.current?.setVolume(vol);
|
||||
};
|
||||
|
||||
if (loading) return <p className="p-6">Memuat audio...</p>;
|
||||
if (!data) return <p className="p-6">Data tidak ditemukan</p>;
|
||||
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto px-4 py-6 space-y-6">
|
||||
{/* Pemutar Audio Utama */}
|
||||
<div className="relative">
|
||||
<audio
|
||||
controls
|
||||
className="w-full"
|
||||
src={data?.files?.[selectedAudio]?.url || ""}
|
||||
>
|
||||
Browser Anda tidak mendukung elemen audio.
|
||||
</audio>
|
||||
<div className="max-w-5xl mx-auto px-4 py-6 space-y-6">
|
||||
{/* 🎵 Player */}
|
||||
<div className="relative flex flex-col items-start">
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={handlePlayPause}
|
||||
className="w-12 h-12 bg-yellow-500 rounded-full flex justify-center items-center text-white text-xl hover:bg-yellow-600"
|
||||
>
|
||||
{playing ? "❚❚" : "▶"}
|
||||
</button>
|
||||
|
||||
{/* Loading sebelum waveform siap */}
|
||||
{!audioLoaded && (
|
||||
<BarWave
|
||||
color="#ffc831"
|
||||
width="60px"
|
||||
height="25px"
|
||||
duration="2s"
|
||||
className="ml-5"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Waveform */}
|
||||
<div ref={waveformRef} className="w-[300px] sm:w-[600px] ml-4"></div>
|
||||
</div>
|
||||
|
||||
{/* Volume control */}
|
||||
{audioLoaded && (
|
||||
<div className="flex items-center gap-2 mt-2">
|
||||
<span>🔊</span>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.05"
|
||||
value={volume}
|
||||
onChange={handleVolumeChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Pilihan file audio */}
|
||||
<div className="py-4 px-1 flex flex-row gap-3 flex-wrap">
|
||||
{/* 🔸 Daftar File Audio */}
|
||||
<div className="py-4 flex flex-row gap-3 flex-wrap">
|
||||
{data?.files?.map((file: any, index: number) => (
|
||||
<button
|
||||
key={file?.id}
|
||||
onClick={() => setSelectedAudio(index)}
|
||||
className={`px-4 py-2 rounded-md border cursor-pointer hover:bg-gray-100 ${
|
||||
selectedAudio === index ? "border-red-600 bg-gray-50" : ""
|
||||
selectedAudio === index ? "border-yellow-600 bg-yellow-50" : ""
|
||||
}`}
|
||||
>
|
||||
🎵 {file?.fileName || `Audio ${index + 1}`}
|
||||
🎧 {file?.fileName || `Audio ${index + 1}`}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Informasi artikel */}
|
||||
{/* 🗓️ Informasi Artikel */}
|
||||
<div className="flex flex-wrap items-center gap-2 text-sm text-muted-foreground">
|
||||
<span className="font-semibold text-black border-r-2 pr-2 border-black">
|
||||
by {data?.uploadedBy?.publisher || "MABES POLRI"}
|
||||
by {data?.uploadedBy?.userLevel?.name || "MABES POLRI"}
|
||||
</span>
|
||||
<span className="flex items-center gap-1 text-black">
|
||||
<Calendar className="w-4 h-4" />
|
||||
|
|
@ -173,65 +167,16 @@ export default function AudioDetail({ id }: { id: number }) {
|
|||
<Eye className="w-4 h-4" />
|
||||
{data.clickCount || 0}
|
||||
</span>
|
||||
<span className="text-black">
|
||||
Creator: {data.creatorGroupLevelName}
|
||||
</span>
|
||||
<span className="text-black">Creator: {data.creatorName}</span>
|
||||
</div>
|
||||
|
||||
{/* Deskripsi */}
|
||||
<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 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 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 z-10">
|
||||
<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/audio/comment/${id}`}>
|
||||
<Button
|
||||
variant="default"
|
||||
size="lg"
|
||||
className="justify-start bg-[#FFAD10] rounded-full mr-2 text-white"
|
||||
>
|
||||
💬
|
||||
</Button>
|
||||
COMMENT
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* 📝 Deskripsi */}
|
||||
<div className="mt-4">
|
||||
<h1 className="text-xl font-bold">{data.title}</h1>
|
||||
<div
|
||||
className="text-base text-gray-700 leading-relaxed space-y-3 mt-2"
|
||||
dangerouslySetInnerHTML={{ __html: data.htmlDescription }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -70,14 +70,26 @@ export default function DocumentDetail({ id }: { id: number }) {
|
|||
uploadedBy: {
|
||||
publisher: article.createdByName || "MABES POLRI",
|
||||
},
|
||||
// files:
|
||||
// article.files?.map((f: any) => ({
|
||||
// id: f.id,
|
||||
// fileName: f.file_name,
|
||||
// url: f.file_url,
|
||||
// secondaryUrl: f.file_url,
|
||||
// fileThumbnail: f.file_thumbnail,
|
||||
// fileAlt: f.file_alt,
|
||||
// size: f.size,
|
||||
// createdAt: f.created_at,
|
||||
// updatedAt: f.updated_at,
|
||||
// })) || [],
|
||||
files:
|
||||
article.files?.map((f: any) => ({
|
||||
id: f.id,
|
||||
fileName: f.file_name,
|
||||
url: f.file_url,
|
||||
secondaryUrl: f.file_url,
|
||||
fileThumbnail: f.file_thumbnail,
|
||||
fileAlt: f.file_alt,
|
||||
fileName: f.fileName || f.file_name,
|
||||
url: f.fileUrl || f.file_url,
|
||||
secondaryUrl: f.fileUrl || f.file_url, // 🟢 gunakan field yang benar
|
||||
fileThumbnail: f.fileThumbnail || f.file_thumbnail,
|
||||
fileAlt: f.fileAlt || f.file_alt,
|
||||
size: f.size,
|
||||
createdAt: f.created_at,
|
||||
updatedAt: f.updated_at,
|
||||
|
|
@ -125,13 +137,45 @@ export default function DocumentDetail({ id }: { id: number }) {
|
|||
return (
|
||||
<div className="max-w-6xl mx-auto px-4 py-6 space-y-6">
|
||||
{/* Viewer dokumen utama */}
|
||||
{/* <div className="relative">
|
||||
<iframe
|
||||
src={data?.files?.[selectedDoc]?.secondaryUrl || ""}
|
||||
className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-full"
|
||||
/>
|
||||
</div> */}
|
||||
<div className="bg-[#e0c350] flex items-center justify-center h-[170px] text-white">
|
||||
<div className="relative">
|
||||
{data?.files?.[selectedDoc]?.secondaryUrl ? (
|
||||
<>
|
||||
{console.log(
|
||||
"📄 File URL:",
|
||||
data?.files?.[selectedDoc]?.secondaryUrl
|
||||
)}
|
||||
|
||||
<iframe
|
||||
key={selectedDoc}
|
||||
src={`https://docs.google.com/gview?url=${encodeURIComponent(
|
||||
data?.files?.[selectedDoc]?.secondaryUrl || ""
|
||||
)}&embedded=true`}
|
||||
className="rounded-lg w-full h-[600px] border"
|
||||
onError={(e) => {
|
||||
console.error("❌ Gagal load iframe:", e);
|
||||
window.open(data?.files?.[selectedDoc]?.secondaryUrl, "_blank");
|
||||
}}
|
||||
/>
|
||||
|
||||
<p className="text-center text-xs text-gray-500 mt-2">
|
||||
Jika preview tidak muncul,{" "}
|
||||
<a
|
||||
href={data?.files?.[selectedDoc]?.secondaryUrl}
|
||||
target="_blank"
|
||||
className="text-blue-600 underline"
|
||||
>
|
||||
klik di sini untuk buka file
|
||||
</a>
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<div className="w-full h-[300px] flex items-center justify-center bg-gray-100 text-gray-500 rounded-lg">
|
||||
Tidak ada file untuk ditampilkan.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* <div className="bg-[#e0c350] flex items-center justify-center h-[170px] text-white">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="150"
|
||||
|
|
@ -143,7 +187,7 @@ export default function DocumentDetail({ id }: { id: number }) {
|
|||
d="M5 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V5.414a1.5 1.5 0 0 0-.44-1.06L9.647 1.439A1.5 1.5 0 0 0 8.586 1zM4 3a1 1 0 0 1 1-1h3v2.5A1.5 1.5 0 0 0 9.5 6H12v7a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1zm7.793 2H9.5a.5.5 0 0 1-.5-.5V2.207zM7 7.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5M7.5 9a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1zM7 11.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5M5.5 8a.5.5 0 1 0 0-1a.5.5 0 0 0 0 1M6 9.5a.5.5 0 1 1-1 0a.5.5 0 0 1 1 0M5.5 12a.5.5 0 1 0 0-1a.5.5 0 0 0 0 1"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
{/* Pilihan dokumen */}
|
||||
<div className="py-4 px-1 flex flex-row gap-3 flex-wrap">
|
||||
|
|
|
|||
|
|
@ -160,9 +160,11 @@ export default function ImageDetail({ id }: { id: number }) {
|
|||
width={2560}
|
||||
height={1440}
|
||||
src={
|
||||
data?.files?.[selectedImage]?.url ||
|
||||
data?.thumbnailUrl || // ✅ fallback ke thumbnailUrl
|
||||
"/nodata.png"
|
||||
data?.files?.[selectedImage]?.url?.trim()
|
||||
? data.files[selectedImage].url
|
||||
: data?.thumbnailUrl?.trim()
|
||||
? data.thumbnailUrl
|
||||
: "/assets/empty-data.png"
|
||||
}
|
||||
alt="Main"
|
||||
className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-full object-contain"
|
||||
|
|
|
|||
|
|
@ -52,18 +52,18 @@ export default function VideoDetail({ id }: { id: string }) {
|
|||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
|
@ -77,35 +77,37 @@ export default function VideoDetail({ id }: { id: string }) {
|
|||
clickCount: articleData.viewCount,
|
||||
creatorGroupLevelName: articleData.createdByName || "Unknown",
|
||||
uploadedBy: {
|
||||
publisher: articleData.createdByName || "MABES POLRI"
|
||||
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,
|
||||
thumbnailFileUrl: file.file_thumbnail || articleData.thumbnailUrl,
|
||||
...file
|
||||
})) || [],
|
||||
...articleData
|
||||
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,
|
||||
thumbnailFileUrl:
|
||||
file.file_thumbnail || articleData.thumbnailUrl,
|
||||
...file,
|
||||
})) || [],
|
||||
...articleData,
|
||||
};
|
||||
|
||||
|
||||
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);
|
||||
// const fallbackResponse = await getDetail(id);
|
||||
// setData(fallbackResponse?.data?.data);
|
||||
} catch (fallbackError) {
|
||||
console.error("Fallback API also failed:", fallbackError);
|
||||
}
|
||||
|
|
@ -134,28 +136,48 @@ export default function VideoDetail({ id }: { id: string }) {
|
|||
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto px-4 py-6 space-y-6">
|
||||
{/* Bagian Video Utama */}
|
||||
<div className="relative max-h-screen overflow-hidden">
|
||||
<div className="w-full max-h-screen aspect-video">
|
||||
<div className="w-full h-full object-contain">
|
||||
<VideoPlayer url={data?.files[selectedVideo]?.url} />
|
||||
<VideoPlayer
|
||||
url={
|
||||
// data?.files?.[selectedVideo]?.secondaryUrl?.trim()
|
||||
// ? data.files[selectedVideo].secondaryUrl
|
||||
// : data?.files?.[selectedVideo]?.fileUrl?.includes("viewer/")
|
||||
// ? `${process.env.NEXT_PUBLIC_API}/media/view?id=${data.files[selectedVideo].id}&operation=file&type=video`
|
||||
// : data?.files?.[selectedVideo]?.fileUrl?.trim()
|
||||
// ?
|
||||
data.files[selectedVideo].fileUrl
|
||||
// : "/notfound.mp4"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute top-4 left-4"></div>
|
||||
</div>
|
||||
|
||||
<div className="py-2 flex flex-row gap-3">
|
||||
{/* Thumbnail bawah */}
|
||||
<div className="py-2 flex flex-row gap-3 flex-wrap">
|
||||
{data?.files?.map((file: any, index: number) => (
|
||||
<div
|
||||
key={file?.id}
|
||||
onClick={() => setSelectedVideo(index)}
|
||||
className="cursor-pointer flex flex-col items-center gap-1"
|
||||
>
|
||||
<img
|
||||
src={file?.thumbnailFileUrl}
|
||||
alt={file?.fileName}
|
||||
className="w-32 h-20 object-cover rounded"
|
||||
<Image
|
||||
src={
|
||||
file?.thumbnailFileUrl?.trim()
|
||||
? file.thumbnailFileUrl
|
||||
: "/notfound.png"
|
||||
}
|
||||
alt={file?.fileName || "thumbnail"}
|
||||
width={160}
|
||||
height={90}
|
||||
className={`w-32 h-20 object-cover rounded-md hover:ring-2 ${
|
||||
selectedVideo === index ? "ring-2 ring-red-600" : ""
|
||||
}`}
|
||||
/>
|
||||
{/* <p className="text-sm text-center">{file?.fileName}</p> */}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -197,7 +219,7 @@ export default function VideoDetail({ id }: { id: string }) {
|
|||
|
||||
<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="hidden md:flex flex-col gap-4 relative z-10">
|
||||
<div className="flex gap-2 items-center">
|
||||
<Button
|
||||
onClick={handleCopyLink}
|
||||
|
|
@ -253,7 +275,7 @@ export default function VideoDetail({ id }: { id: string }) {
|
|||
COMMENT
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 space-y-4">
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@
|
|||
"tus-js-client": "^4.3.1",
|
||||
"use-places-autocomplete": "^4.0.1",
|
||||
"uuid": "^13.0.0",
|
||||
"wavesurfer.js": "^7.11.0",
|
||||
"yup": "^1.6.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
|
|
@ -18923,6 +18924,8 @@
|
|||
},
|
||||
"node_modules/react-cssfx-loading": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-cssfx-loading/-/react-cssfx-loading-2.1.0.tgz",
|
||||
"integrity": "sha512-0SnS6HpaeLSaTxNuND6sAKTQmoKgjwFb9G2ltyEMmA5ARNN6TRQfiJ8PfaYM9RwVEOhDxIzGI7whb2zeI1VRxw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"goober": "^2.1.10"
|
||||
|
|
@ -22558,9 +22561,10 @@
|
|||
}
|
||||
},
|
||||
"node_modules/wavesurfer.js": {
|
||||
"version": "7.8.4",
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/wavesurfer.js/-/wavesurfer.js-7.11.0.tgz",
|
||||
"integrity": "sha512-LOGdIBIKv/roYuQYClhoqhwbIdQL1GfobLnS2vx0heoLD9lu57OUHWE2DIsCNXBvCsmmbkUvJq9W8bPLPbikGw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/wcwidth": {
|
||||
"version": "1.0.1",
|
||||
|
|
|
|||
|
|
@ -134,6 +134,7 @@
|
|||
"tus-js-client": "^4.3.1",
|
||||
"use-places-autocomplete": "^4.0.1",
|
||||
"uuid": "^13.0.0",
|
||||
"wavesurfer.js": "^7.11.0",
|
||||
"yup": "^1.6.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 149 KiB |
|
|
@ -30,7 +30,7 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({ url }) => {
|
|||
controls
|
||||
playing
|
||||
muted
|
||||
pip={false} //
|
||||
pip={false}
|
||||
config={{
|
||||
file: {
|
||||
attributes: {
|
||||
|
|
|
|||
Loading…
Reference in New Issue