2025-09-16 08:29:07 +00:00
"use client" ;
2025-12-11 04:45:26 +00:00
import { useState , useEffect } from "react" ;
2025-09-16 08:29:07 +00:00
import { Button } from "@/components/ui/button" ;
import { Textarea } from "@/components/ui/textarea" ;
2025-12-11 04:45:26 +00:00
import { MessageCircle , Share2 , Trash2 } from "lucide-react" ;
import { useRouter , useParams } from "next/navigation" ;
2025-09-16 08:29:07 +00:00
import {
2025-12-11 04:45:26 +00:00
getArticleDetail ,
createArticleComment ,
getArticleComments ,
deleteArticleComment ,
} from "@/service/content/content" ;
import { getCookiesDecrypt } from "@/lib/utils" ;
import Swal from "sweetalert2" ;
import withReactContent from "sweetalert2-react-content" ;
function getAvatarColor ( name : string ) {
const colors = [
"#F87171" ,
"#FB923C" ,
"#FACC15" ,
"#4ADE80" ,
"#60A5FA" ,
"#A78BFA" ,
"#F472B6" ,
] ;
const index = name . charCodeAt ( 0 ) % colors . length ;
return colors [ index ] ;
}
2025-09-16 08:29:07 +00:00
export default function DetailCommentVideo() {
const [ isLoggedIn , setIsLoggedIn ] = useState ( false ) ;
2025-12-11 04:45:26 +00:00
const [ currentUserId , setCurrentUserId ] = useState < number | null > ( null ) ;
const [ video , setVideo ] = useState < any > ( null ) ;
const [ comments , setComments ] = useState < any [ ] > ( [ ] ) ;
const [ newComment , setNewComment ] = useState ( "" ) ;
const [ replyParentId , setReplyParentId ] = useState < number | null > ( null ) ;
const [ replyMessage , setReplyMessage ] = useState ( "" ) ;
2025-09-16 08:29:07 +00:00
const router = useRouter ( ) ;
2025-12-11 04:45:26 +00:00
const params = useParams ( ) ;
const MySwal = withReactContent ( Swal ) ;
const id = Number ( params ? . id ) ;
useEffect ( ( ) = > {
checkLoginStatus ( ) ;
if ( id ) {
fetchVideoDetail ( id ) ;
fetchComments ( id ) ;
}
} , [ id ] ) ;
const checkLoginStatus = ( ) = > {
const userId = getCookiesDecrypt ( "urie" ) ;
if ( userId ) {
setIsLoggedIn ( true ) ;
setCurrentUserId ( Number ( userId ) ) ;
} else {
setIsLoggedIn ( false ) ;
setCurrentUserId ( null ) ;
}
} ;
const fetchVideoDetail = async ( videoId : number ) = > {
try {
const res = await getArticleDetail ( videoId ) ;
if ( res ? . data ? . data ) setVideo ( res . data . data ) ;
} catch ( error ) {
console . error ( "Gagal memuat video:" , error ) ;
}
} ;
const fetchComments = async ( videoId : number ) = > {
try {
const res = await getArticleComments ( videoId ) ;
if ( res ? . data ? . data ) {
const all = res . data . data . map ( ( c : any ) = > ( {
. . . c ,
parentId : c.parentId ? ? 0 ,
} ) ) ;
const structured = buildCommentTree ( all ) ;
setComments ( structured ) ;
}
} catch ( error ) {
console . error ( "Gagal memuat komentar:" , error ) ;
}
} ;
const buildCommentTree : any = ( comments : any [ ] , parentId = 0 ) = >
comments
. filter ( ( c ) = > c . parentId === parentId )
. map ( ( c ) = > ( {
. . . c ,
replies : buildCommentTree ( comments , c . id ) ,
} ) ) ;
const handlePostComment = async ( ) = > {
if ( ! newComment . trim ( ) ) {
MySwal . fire ( "Oops!" , "Komentar tidak boleh kosong." , "warning" ) ;
return ;
}
await sendComment ( {
articleId : id ,
message : newComment ,
isPublic : true ,
parentId : 0 ,
} ) ;
setNewComment ( "" ) ;
} ;
const handleReplySubmit = async ( parentId : number ) = > {
if ( ! replyMessage . trim ( ) ) {
MySwal . fire ( "Oops!" , "Balasan tidak boleh kosong." , "warning" ) ;
return ;
}
await sendComment ( {
articleId : id ,
message : replyMessage ,
isPublic : true ,
parentId ,
} ) ;
setReplyMessage ( "" ) ;
setReplyParentId ( null ) ;
} ;
const sendComment = async ( payload : any ) = > {
MySwal . fire ( {
title : "Mengirim komentar..." ,
didOpen : ( ) = > MySwal . showLoading ( ) ,
allowOutsideClick : false ,
showConfirmButton : false ,
} ) ;
try {
const res = await createArticleComment ( payload ) ;
if ( res ? . data ? . success || ! res ? . error ) {
MySwal . fire ( {
icon : "success" ,
title : "Komentar terkirim!" ,
timer : 1000 ,
showConfirmButton : false ,
} ) ;
fetchComments ( id ) ;
} else {
MySwal . fire (
"Gagal" ,
res . message || "Tidak dapat mengirim komentar." ,
"error"
) ;
}
} catch ( error ) {
console . error ( error ) ;
MySwal . fire (
"Error" ,
"Terjadi kesalahan saat mengirim komentar." ,
"error"
) ;
}
} ;
const handleDeleteComment = async ( commentId : number ) = > {
const confirm = await MySwal . fire ( {
title : "Hapus komentar ini?" ,
text : "Tindakan ini tidak dapat dibatalkan!" ,
icon : "warning" ,
showCancelButton : true ,
confirmButtonColor : "#d33" ,
cancelButtonColor : "#6b7280" ,
confirmButtonText : "Ya, hapus" ,
cancelButtonText : "Batal" ,
} ) ;
if ( ! confirm . isConfirmed ) return ;
MySwal . fire ( {
title : "Menghapus komentar..." ,
didOpen : ( ) = > MySwal . showLoading ( ) ,
allowOutsideClick : false ,
showConfirmButton : false ,
} ) ;
try {
const res = await deleteArticleComment ( commentId ) ;
if ( res ? . data ? . success || ! res ? . error ) {
MySwal . fire ( {
icon : "success" ,
title : "Komentar dihapus!" ,
timer : 1000 ,
showConfirmButton : false ,
} ) ;
fetchComments ( id ) ;
} else {
MySwal . fire (
"Gagal" ,
res . message || "Tidak dapat menghapus komentar." ,
"error"
) ;
}
} catch ( error ) {
console . error ( "Gagal menghapus komentar:" , error ) ;
MySwal . fire (
"Error" ,
"Terjadi kesalahan saat menghapus komentar." ,
"error"
) ;
}
} ;
2025-09-16 08:29:07 +00:00
return (
< div className = "max-w-5xl mx-auto p-4 space-y-6" >
< button
onClick = { ( ) = > router . back ( ) }
2025-12-11 04:45:26 +00:00
className = "text-sm text-gray-500 hover:underline cursor-pointer"
2025-09-16 08:29:07 +00:00
>
2025-12-11 04:45:26 +00:00
← Kembali ke Video
2025-09-16 08:29:07 +00:00
< / button >
< div >
< p className = "font-semibold text-sm uppercase text-gray-600 mb-1" >
Comments on :
< / p >
< h1 className = "text-lg font-bold" >
2025-12-11 04:45:26 +00:00
{ video ? . title || "Memuat judul video..." }
2025-09-16 08:29:07 +00:00
< / h1 >
< / div >
2025-12-11 04:45:26 +00:00
< div className = "rounded-md p-3 space-y-3 bg-gray-50 border border-gray-200 shadow-sm" >
2025-09-16 08:29:07 +00:00
{ isLoggedIn ? (
< >
< Textarea
2025-12-11 04:45:26 +00:00
placeholder = "Tulis komentar kamu di sini..."
2025-09-16 08:29:07 +00:00
className = "min-h-[80px] border border-[#C6A455]"
2025-12-11 04:45:26 +00:00
value = { newComment }
onChange = { ( e ) = > setNewComment ( e . target . value ) }
2025-09-16 08:29:07 +00:00
/ >
< div className = "flex justify-end" >
2025-12-11 04:45:26 +00:00
< Button size = "sm" onClick = { handlePostComment } >
Kirim Komentar
< / Button >
2025-09-16 08:29:07 +00:00
< / div >
< / >
) : (
< >
< Textarea
disabled
2025-12-11 04:45:26 +00:00
placeholder = "Tulis komentar kamu di sini..."
2025-09-16 08:29:07 +00:00
className = "min-h-[80px] opacity-70"
/ >
2025-12-11 04:45:26 +00:00
< Button
onClick = { ( ) = > router . push ( "/auth" ) }
className = "w-full bg-blue-600 hover:bg-blue-700 text-white"
>
2025-09-16 08:29:07 +00:00
Sign in and Join the Conversation
< / Button >
< / >
) }
< / div >
2025-12-11 04:45:26 +00:00
{ /* Daftar komentar */ }
2025-09-16 08:29:07 +00:00
< div className = "space-y-6" >
2025-12-11 04:45:26 +00:00
{ comments . length > 0 ? (
comments . map ( ( comment ) = > (
< CommentTree
key = { comment . id }
comment = { comment }
level = { 0 }
replyParentId = { replyParentId }
setReplyParentId = { setReplyParentId }
replyMessage = { replyMessage }
setReplyMessage = { setReplyMessage }
onReplySubmit = { handleReplySubmit }
onDelete = { handleDeleteComment }
currentUserId = { currentUserId }
/ >
) )
) : (
< p className = "text-sm text-gray-500 text-center py-4" >
Belum ada komentar .
< / p >
) }
2025-09-16 08:29:07 +00:00
< / div >
< / div >
) ;
}
2025-12-11 04:45:26 +00:00
function CommentTree ( {
comment ,
level ,
replyParentId ,
setReplyParentId ,
replyMessage ,
setReplyMessage ,
onReplySubmit ,
onDelete ,
currentUserId ,
} : any ) {
const color = getAvatarColor ( comment . commentFromName || "Anonim" ) ;
const canDelete =
currentUserId &&
( comment . commentFromId == currentUserId ||
comment . userId == currentUserId ||
comment . createdBy == currentUserId ) ;
2025-09-16 08:29:07 +00:00
return (
2025-12-11 04:45:26 +00:00
< div
className = { ` space-y-3 ${
level > 0 ? "ml-6 border-l-2 border-gray-200 pl-4" : ""
} ` }
>
< div className = "p-2 rounded-lg transition hover:bg-gray-50 bg-white" >
< div className = "flex items-start gap-2" >
< div
className = "w-8 h-8 rounded-full flex items-center justify-center text-white font-bold"
style = { { backgroundColor : color } }
>
{ comment . commentFromName ? . [ 0 ] ? . toUpperCase ( ) || "U" }
< / div >
< div className = "flex-1" >
< p className = "font-semibold text-sm" >
{ comment . commentFromName } { " " }
< span className = "text-gray-500 text-xs font-normal" >
{ new Date ( comment . createdAt ) . toLocaleString ( "id-ID" ) }
< / span >
< / p >
< p className = "text-gray-800 text-sm leading-snug mt-1" >
{ comment . message }
< / p >
2025-09-16 08:29:07 +00:00
2025-12-11 04:45:26 +00:00
< div className = "flex items-center gap-3 mt-1 text-xs text-gray-600" >
< button
onClick = { ( ) = >
setReplyParentId (
replyParentId === comment . id ? null : comment . id
)
}
className = "hover:underline flex items-center gap-1"
>
< MessageCircle className = "w-3 h-3" / > Reply
< / button >
< button className = "hover:underline flex items-center gap-1" >
< Share2 className = "w-3 h-3" / > Share
< / button >
{ canDelete && (
< button
onClick = { ( ) = > onDelete ( comment . id ) }
className = "flex items-center gap-1 px-2 py-1 text-xs bg-red-100 text-red-600 rounded-md hover:bg-red-200"
>
< Trash2 className = "w-3 h-3" / > Delete
2025-09-16 08:29:07 +00:00
< / button >
2025-12-11 04:45:26 +00:00
) }
2025-09-16 08:29:07 +00:00
< / div >
2025-12-11 04:45:26 +00:00
< / div >
< / div >
{ replyParentId === comment . id && (
< div className = "mt-3 ml-10 space-y-2" >
< Textarea
placeholder = { ` Balas komentar ${ comment . commentFromName } ` }
className = "min-h-[60px] border border-[#C6A455]"
value = { replyMessage }
onChange = { ( e ) = > setReplyMessage ( e . target . value ) }
/ >
< div className = "flex justify-end gap-2" >
< Button
variant = "outline"
size = "sm"
onClick = { ( ) = > setReplyParentId ( null ) }
>
Batal
< / Button >
< Button
variant = "outline"
size = "sm"
onClick = { ( ) = > onReplySubmit ( comment . id ) }
>
Kirim Balasan
< / Button >
< / div >
< / div >
) }
< / div >
{ comment . replies && comment . replies . length > 0 && (
< div className = "space-y-3" >
{ comment . replies . map ( ( reply : any ) = > (
< CommentTree
key = { reply . id }
comment = { reply }
level = { level + 1 }
replyParentId = { replyParentId }
setReplyParentId = { setReplyParentId }
replyMessage = { replyMessage }
setReplyMessage = { setReplyMessage }
onReplySubmit = { onReplySubmit }
onDelete = { onDelete }
currentUserId = { currentUserId }
/ >
2025-09-16 08:29:07 +00:00
) ) }
< / div >
) }
< / div >
) ;
}
2025-12-11 04:45:26 +00:00
// "use client";
// import { useState } from "react";
// import { Button } from "@/components/ui/button";
// import { Input } from "@/components/ui/input";
// import { Textarea } from "@/components/ui/textarea";
// import {
// ChevronDown,
// Flag,
// MessageCircle,
// Share2,
// ThumbsUp,
// } from "lucide-react";
// import { useRouter } from "next/navigation";
// export default function DetailCommentVideo() {
// const [isLoggedIn, setIsLoggedIn] = useState(false);
// const router = useRouter();
// return (
// <div className="max-w-5xl mx-auto p-4 space-y-6">
// <button
// onClick={() => router.back()}
// className="text-sm text-gray-500 hover:underline"
// >
// ← Kembali ke Article
// </button>
// <div>
// <p className="font-semibold text-sm uppercase text-gray-600 mb-1">
// Comments on:
// </p>
// <h1 className="text-lg font-bold">
// Kolaborasi dengan Ponpes Darunnajah, Pererat Sinergi Keamanan Hingga
// Pembinaan Generasi Muda
// </h1>
// </div>
// {/* Comment Form */}
// <div className=" rounded-md p-3 space-y-3">
// {isLoggedIn ? (
// <>
// <Textarea
// placeholder="Post a comment"
// className="min-h-[80px] border border-[#C6A455]"
// />
// <div className="flex justify-end">
// <Button size="sm">Post</Button>
// </div>
// </>
// ) : (
// <>
// <Textarea
// disabled
// placeholder="Post a comment"
// className="min-h-[80px] opacity-70"
// />
// <Button className="w-full bg-blue-600 hover:bg-blue-700 text-white">
// Sign in and Join the Conversation
// </Button>
// </>
// )}
// </div>
// {/* Sort & Comment Count */}
// <div className="flex items-center justify-between border-b pb-2">
// <p className="font-semibold">
// All Comments{" "}
// <span className="text-sm font-medium text-gray-500">4</span>
// </p>
// <div className="flex items-center gap-1 text-sm text-gray-600">
// Sort by
// <button className="flex items-center gap-1 border border-[#C6A455] px-2 py-1 rounded text-gray-700 hover:bg-gray-100">
// Newest <ChevronDown className="w-4 h-4" />
// </button>
// </div>
// </div>
// {/* Comments List */}
// <div className="space-y-6">
// <CommentItem
// name="Mabes Ferr"
// content="Lorem ipsum dolor sit amet consectetur. Adipiscing ut mi pretium elementum est. Euismod integer praesent lacus praesent lobortis. Eleifend."
// time="2 hours ago"
// />
// <CommentItem
// name="Mabes Jerr"
// content="Lorem ipsum dolor sit amet consectetur. Adipiscing ut mi pretium elementum est. Euismod integer praesent lacus praesent lobortis. Eleifend."
// time="2 hours ago"
// replies={[
// {
// name: "Mabes Herr",
// time: "2 hours ago",
// inReplyTo: "Mabes Jerr",
// content:
// "Lorem ipsum dolor sit amet consectetur. Adipiscing ut mi pretium elementum est. Euismod integer praesent lacus praesent lobortis. Eleifend.",
// },
// ]}
// />
// </div>
// </div>
// );
// }
// type Comment = {
// name: string;
// time: string;
// content: string;
// inReplyTo?: string;
// };
// function CommentItem({
// name,
// time,
// content,
// replies,
// }: Comment & { replies?: Comment[] }) {
// return (
// <div className="space-y-3">
// <div>
// <p className="font-semibold">
// {name}{" "}
// <span className="text-gray-500 text-sm font-normal">{time}</span>
// </p>
// <p className="text-gray-800 text-sm">{content}</p>
// <div className="flex items-center gap-4 mt-2 text-sm text-gray-600">
// <ThumbsUp className="w-4 h-4 cursor-pointer" />
// <button className="hover:underline flex items-center gap-1">
// <MessageCircle className="w-4 h-4" /> Reply
// </button>
// <button className="hover:underline flex items-center gap-1">
// <Share2 className="w-4 h-4" /> Share
// </button>
// <button className="ml-auto hover:underline text-gray-400 text-xs flex items-center gap-1">
// <Flag className="w-3 h-3" /> Report
// </button>
// </div>
// </div>
// {replies && replies.length > 0 && (
// <div className="ml-6 border-l pl-4 space-y-3">
// {replies.map((reply, idx) => (
// <div key={idx}>
// <p className="font-semibold">
// {reply.name}{" "}
// <span className="text-gray-500 text-sm font-normal">
// {reply.time}
// </span>
// </p>
// <p className="text-xs text-gray-500">
// In Reply To {reply.inReplyTo}
// </p>
// <p className="text-gray-800 text-sm">{reply.content}</p>
// <div className="flex items-center gap-4 mt-2 text-sm text-gray-600">
// <ThumbsUp className="w-4 h-4 cursor-pointer" />
// <button className="hover:underline flex items-center gap-1">
// <MessageCircle className="w-4 h-4" /> Reply
// </button>
// <button className="hover:underline flex items-center gap-1">
// <Share2 className="w-4 h-4" /> Share
// </button>
// <button className="ml-auto hover:underline text-gray-400 text-xs flex items-center gap-1">
// <Flag className="w-3 h-3" /> Report
// </button>
// </div>
// </div>
// ))}
// </div>
// )}
// </div>
// );
// }