fix: all detail content

This commit is contained in:
Sabda Yagra 2025-10-19 23:07:42 +07:00
parent 1ce44acc3c
commit ef4887cfc1
8 changed files with 255 additions and 237 deletions

View File

@ -1,158 +1,152 @@
"use client"; "use client";
import { useState, useEffect } from "react"; import { useState, useEffect, useRef } from "react";
import { Calendar, Eye } from "lucide-react"; import { Calendar, Eye } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import Link from "next/link"; 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 { getDetail } from "@/service/landing/landing";
import { BarWave } from "react-cssfx-loading";
import WaveSurfer from "wavesurfer.js";
import { getArticleDetail } from "@/service/content/content"; import { getArticleDetail } from "@/service/content/content";
export default function AudioDetail({ id }: { id: number }) { export default function AudioDetail({ id }: { id: number }) {
const [data, setData] = useState<any>(null); const [data, setData] = useState<any>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [copied, setCopied] = useState(false); const [audioLoaded, setAudioLoaded] = useState(false);
const [showShareMenu, setShowShareMenu] = 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); const [selectedAudio, setSelectedAudio] = useState(0);
// Salin tautan // Ambil data audio dari API lama
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>
);
useEffect(() => { useEffect(() => {
const fetchDetail = async () => { const fetchData = async () => {
try {
setLoading(true); setLoading(true);
// 1⃣ Coba API baru
const res = await getArticleDetail(id); const res = await getArticleDetail(id);
console.log("Audio detail API response:", res); const detail = res?.data?.data;
if (detail) setData(detail);
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); setLoading(false);
}
}; };
fetchData();
if (id) fetchDetail();
}, [id]); }, [id]);
if (loading) { // Inisialisasi WaveSurfer
return ( useEffect(() => {
<div className="max-w-6xl mx-auto px-4 py-6"> if (!data?.files?.[selectedAudio]?.url) return;
<p>Memuat data audio...</p>
</div>
);
}
if (!data) { // Hancurkan instance sebelumnya jika ada
return ( if (wavesurfer.current) wavesurfer.current.destroy();
<div className="max-w-6xl mx-auto px-4 py-6">
<p>Data tidak ditemukan</p> const url = data.files[selectedAudio].url;
</div>
); 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 ( return (
<div className="max-w-6xl mx-auto px-4 py-6 space-y-6"> <div className="max-w-5xl mx-auto px-4 py-6 space-y-6">
{/* Pemutar Audio Utama */} {/* 🎵 Player */}
<div className="relative"> <div className="relative flex flex-col items-start">
<audio <div className="flex items-center gap-4">
controls <button
className="w-full" onClick={handlePlayPause}
src={data?.files?.[selectedAudio]?.url || ""} className="w-12 h-12 bg-yellow-500 rounded-full flex justify-center items-center text-white text-xl hover:bg-yellow-600"
> >
Browser Anda tidak mendukung elemen audio. {playing ? "❚❚" : "▶"}
</audio> </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> </div>
{/* Pilihan file audio */} {/* Volume control */}
<div className="py-4 px-1 flex flex-row gap-3 flex-wrap"> {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>
{/* 🔸 Daftar File Audio */}
<div className="py-4 flex flex-row gap-3 flex-wrap">
{data?.files?.map((file: any, index: number) => ( {data?.files?.map((file: any, index: number) => (
<button <button
key={file?.id} key={file?.id}
onClick={() => setSelectedAudio(index)} onClick={() => setSelectedAudio(index)}
className={`px-4 py-2 rounded-md border cursor-pointer hover:bg-gray-100 ${ 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> </button>
))} ))}
</div> </div>
{/* Informasi artikel */} {/* 🗓️ Informasi Artikel */}
<div className="flex flex-wrap items-center gap-2 text-sm text-muted-foreground"> <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"> <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>
<span className="flex items-center gap-1 text-black"> <span className="flex items-center gap-1 text-black">
<Calendar className="w-4 h-4" /> <Calendar className="w-4 h-4" />
@ -173,65 +167,16 @@ export default function AudioDetail({ id }: { id: number }) {
<Eye className="w-4 h-4" /> <Eye className="w-4 h-4" />
{data.clickCount || 0} {data.clickCount || 0}
</span> </span>
<span className="text-black"> <span className="text-black">Creator: {data.creatorName}</span>
Creator: {data.creatorGroupLevelName}
</span>
</div> </div>
{/* Deskripsi */} {/* 📝 Deskripsi */}
<div className="flex flex-col md:flex-row gap-6 mt-6"> <div className="mt-4">
<div className="flex-1 space-y-4">
<h1 className="text-xl font-bold">{data.title}</h1> <h1 className="text-xl font-bold">{data.title}</h1>
<div className="text-base text-gray-700 leading-relaxed space-y-3"> <div
<p>{data.description}</p> className="text-base text-gray-700 leading-relaxed space-y-3 mt-2"
</div> dangerouslySetInnerHTML={{ __html: data.htmlDescription }}
/>
{/* 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>
</div> </div>
</div> </div>
); );

View File

@ -70,14 +70,26 @@ export default function DocumentDetail({ id }: { id: number }) {
uploadedBy: { uploadedBy: {
publisher: article.createdByName || "MABES POLRI", 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: files:
article.files?.map((f: any) => ({ article.files?.map((f: any) => ({
id: f.id, id: f.id,
fileName: f.file_name, fileName: f.fileName || f.file_name,
url: f.file_url, url: f.fileUrl || f.file_url,
secondaryUrl: f.file_url, secondaryUrl: f.fileUrl || f.file_url, // 🟢 gunakan field yang benar
fileThumbnail: f.file_thumbnail, fileThumbnail: f.fileThumbnail || f.file_thumbnail,
fileAlt: f.file_alt, fileAlt: f.fileAlt || f.file_alt,
size: f.size, size: f.size,
createdAt: f.created_at, createdAt: f.created_at,
updatedAt: f.updated_at, updatedAt: f.updated_at,
@ -125,13 +137,45 @@ export default function DocumentDetail({ id }: { id: number }) {
return ( return (
<div className="max-w-6xl mx-auto px-4 py-6 space-y-6"> <div className="max-w-6xl mx-auto px-4 py-6 space-y-6">
{/* Viewer dokumen utama */} {/* Viewer dokumen utama */}
{/* <div className="relative"> <div className="relative">
{data?.files?.[selectedDoc]?.secondaryUrl ? (
<>
{console.log(
"📄 File URL:",
data?.files?.[selectedDoc]?.secondaryUrl
)}
<iframe <iframe
src={data?.files?.[selectedDoc]?.secondaryUrl || ""} key={selectedDoc}
className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-full" 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");
}}
/> />
</div> */}
<div className="bg-[#e0c350] flex items-center justify-center h-[170px] text-white"> <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 <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="150" 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" 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> </svg>
</div> </div> */}
{/* Pilihan dokumen */} {/* Pilihan dokumen */}
<div className="py-4 px-1 flex flex-row gap-3 flex-wrap"> <div className="py-4 px-1 flex flex-row gap-3 flex-wrap">

View File

@ -160,9 +160,11 @@ export default function ImageDetail({ id }: { id: number }) {
width={2560} width={2560}
height={1440} height={1440}
src={ src={
data?.files?.[selectedImage]?.url || data?.files?.[selectedImage]?.url?.trim()
data?.thumbnailUrl || // ✅ fallback ke thumbnailUrl ? data.files[selectedImage].url
"/nodata.png" : data?.thumbnailUrl?.trim()
? data.thumbnailUrl
: "/assets/empty-data.png"
} }
alt="Main" alt="Main"
className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-full object-contain" className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-full object-contain"

View File

@ -57,13 +57,13 @@ export default function VideoDetail({ id }: { id: string }) {
const response = await getArticleDetail(id); const response = await getArticleDetail(id);
console.log("Article Detail API response:", response); console.log("Article Detail API response:", response);
if (response?.error) { // if (response?.error) {
console.error("Articles API failed, falling back to old API"); // console.error("Articles API failed, falling back to old API");
// Fallback to old API // // Fallback to old API
const fallbackResponse = await getDetail(id); // const fallbackResponse = await getDetail(id);
setData(fallbackResponse?.data?.data); // setData(fallbackResponse?.data?.data);
return; // return;
} // }
// Handle new API response structure // Handle new API response structure
const articleData = response?.data?.data; const articleData = response?.data?.data;
@ -77,9 +77,10 @@ export default function VideoDetail({ id }: { id: string }) {
clickCount: articleData.viewCount, clickCount: articleData.viewCount,
creatorGroupLevelName: articleData.createdByName || "Unknown", creatorGroupLevelName: articleData.createdByName || "Unknown",
uploadedBy: { uploadedBy: {
publisher: articleData.createdByName || "MABES POLRI" publisher: articleData.createdByName || "MABES POLRI",
}, },
files: articleData.files?.map((file: any) => ({ files:
articleData.files?.map((file: any) => ({
id: file.id, id: file.id,
url: file.file_url, url: file.file_url,
fileName: file.file_name, fileName: file.file_name,
@ -92,10 +93,11 @@ export default function VideoDetail({ id }: { id: string }) {
downloadCount: file.download_count, downloadCount: file.download_count,
createdAt: file.created_at, createdAt: file.created_at,
updatedAt: file.updated_at, updatedAt: file.updated_at,
thumbnailFileUrl: file.file_thumbnail || articleData.thumbnailUrl, thumbnailFileUrl:
...file file.file_thumbnail || articleData.thumbnailUrl,
...file,
})) || [], })) || [],
...articleData ...articleData,
}; };
setData(transformedData); setData(transformedData);
@ -104,8 +106,8 @@ export default function VideoDetail({ id }: { id: string }) {
console.error("Error fetching detail:", error); console.error("Error fetching detail:", error);
// Try fallback to old API if new API fails // Try fallback to old API if new API fails
try { try {
const fallbackResponse = await getDetail(id); // const fallbackResponse = await getDetail(id);
setData(fallbackResponse?.data?.data); // setData(fallbackResponse?.data?.data);
} catch (fallbackError) { } catch (fallbackError) {
console.error("Fallback API also failed:", fallbackError); console.error("Fallback API also failed:", fallbackError);
} }
@ -134,28 +136,48 @@ export default function VideoDetail({ id }: { id: string }) {
return ( return (
<div className="max-w-6xl mx-auto px-4 py-6 space-y-6"> <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="relative max-h-screen overflow-hidden">
<div className="w-full max-h-screen aspect-video"> <div className="w-full max-h-screen aspect-video">
<div className="w-full h-full object-contain"> <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> </div>
<div className="absolute top-4 left-4"></div> <div className="absolute top-4 left-4"></div>
</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) => ( {data?.files?.map((file: any, index: number) => (
<div <div
key={file?.id} key={file?.id}
onClick={() => setSelectedVideo(index)} onClick={() => setSelectedVideo(index)}
className="cursor-pointer flex flex-col items-center gap-1" className="cursor-pointer flex flex-col items-center gap-1"
> >
<img <Image
src={file?.thumbnailFileUrl} src={
alt={file?.fileName} file?.thumbnailFileUrl?.trim()
className="w-32 h-20 object-cover rounded" ? 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>
))} ))}
</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"> <div className="flex flex-col md:flex-row gap-6 mt-6">
{/* Sidebar actions */} {/* 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"> <div className="flex gap-2 items-center">
<Button <Button
onClick={handleCopyLink} onClick={handleCopyLink}
@ -253,7 +275,7 @@ export default function VideoDetail({ id }: { id: string }) {
COMMENT COMMENT
</Link> </Link>
</div> </div>
</div> </div> */}
{/* Content */} {/* Content */}
<div className="flex-1 space-y-4"> <div className="flex-1 space-y-4">

10
package-lock.json generated
View File

@ -133,6 +133,7 @@
"tus-js-client": "^4.3.1", "tus-js-client": "^4.3.1",
"use-places-autocomplete": "^4.0.1", "use-places-autocomplete": "^4.0.1",
"uuid": "^13.0.0", "uuid": "^13.0.0",
"wavesurfer.js": "^7.11.0",
"yup": "^1.6.1", "yup": "^1.6.1",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
@ -18923,6 +18924,8 @@
}, },
"node_modules/react-cssfx-loading": { "node_modules/react-cssfx-loading": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/react-cssfx-loading/-/react-cssfx-loading-2.1.0.tgz",
"integrity": "sha512-0SnS6HpaeLSaTxNuND6sAKTQmoKgjwFb9G2ltyEMmA5ARNN6TRQfiJ8PfaYM9RwVEOhDxIzGI7whb2zeI1VRxw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"goober": "^2.1.10" "goober": "^2.1.10"
@ -22558,9 +22561,10 @@
} }
}, },
"node_modules/wavesurfer.js": { "node_modules/wavesurfer.js": {
"version": "7.8.4", "version": "7.11.0",
"license": "BSD-3-Clause", "resolved": "https://registry.npmjs.org/wavesurfer.js/-/wavesurfer.js-7.11.0.tgz",
"peer": true "integrity": "sha512-LOGdIBIKv/roYuQYClhoqhwbIdQL1GfobLnS2vx0heoLD9lu57OUHWE2DIsCNXBvCsmmbkUvJq9W8bPLPbikGw==",
"license": "BSD-3-Clause"
}, },
"node_modules/wcwidth": { "node_modules/wcwidth": {
"version": "1.0.1", "version": "1.0.1",

View File

@ -134,6 +134,7 @@
"tus-js-client": "^4.3.1", "tus-js-client": "^4.3.1",
"use-places-autocomplete": "^4.0.1", "use-places-autocomplete": "^4.0.1",
"uuid": "^13.0.0", "uuid": "^13.0.0",
"wavesurfer.js": "^7.11.0",
"yup": "^1.6.1", "yup": "^1.6.1",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

View File

@ -30,7 +30,7 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({ url }) => {
controls controls
playing playing
muted muted
pip={false} // pip={false}
config={{ config={{
file: { file: {
attributes: { attributes: {