193 lines
6.6 KiB
TypeScript
193 lines
6.6 KiB
TypeScript
/**
|
|
* File: page.tsx
|
|
* Created by: AI Assistant
|
|
* Date: 2025-11-29
|
|
* Purpose: Video player page for kreatiVortex platform
|
|
* Part of: kreatiVortex - Platform Pembelajaran Tari Online
|
|
*/
|
|
|
|
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import Link from 'next/link';
|
|
import { useParams } from 'next/navigation';
|
|
import ActionButton from '@/components/ActionButton';
|
|
import { useFetch } from '@/hooks/useFetch';
|
|
import { getYouTubeId } from '@/lib/utils';
|
|
|
|
interface VideoDetail {
|
|
id: string;
|
|
title: string;
|
|
description: string;
|
|
videoUrl: string;
|
|
viewCount: number;
|
|
createdAt: string;
|
|
uploader: {
|
|
user: {
|
|
name: string;
|
|
};
|
|
};
|
|
comments: Comment[];
|
|
}
|
|
|
|
interface Comment {
|
|
id: string;
|
|
content: string;
|
|
createdAt: string;
|
|
author: {
|
|
user: {
|
|
name: string;
|
|
};
|
|
};
|
|
}
|
|
|
|
export default function VideoDetailPage() {
|
|
const params = useParams();
|
|
const id = params?.id as string;
|
|
const { data: video, loading, refetch } = useFetch<VideoDetail>(`/api/videos/${id}`);
|
|
const [commentText, setCommentText] = useState('');
|
|
const [submittingComment, setSubmittingComment] = useState(false);
|
|
|
|
const handleSubmitComment = async () => {
|
|
if (!commentText.trim()) return;
|
|
|
|
setSubmittingComment(true);
|
|
try {
|
|
const response = await fetch('/api/comments', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
content: commentText,
|
|
videoId: id,
|
|
}),
|
|
});
|
|
|
|
if (response.ok) {
|
|
setCommentText('');
|
|
refetch(); // Refresh comments
|
|
} else {
|
|
console.error('Failed to post comment');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error posting comment:', error);
|
|
} finally {
|
|
setSubmittingComment(false);
|
|
}
|
|
};
|
|
|
|
const youtubeId = video?.videoUrl ? getYouTubeId(video.videoUrl) : null;
|
|
const embedUrl = youtubeId ? `https://www.youtube.com/embed/${youtubeId}` : video?.videoUrl;
|
|
|
|
if (loading) {
|
|
return <div className="text-center text-gray-400 py-12">Loading...</div>;
|
|
}
|
|
|
|
if (!video) {
|
|
return <div className="text-center text-gray-400 py-12">Video not found</div>;
|
|
}
|
|
|
|
return (
|
|
<div className="max-w-4xl mx-auto space-y-6">
|
|
<div className="flex justify-between items-center">
|
|
<Link href="/dashboard/videos" className="text-gray-400 hover:text-white flex items-center">
|
|
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
|
</svg>
|
|
Kembali ke Daftar Video
|
|
</Link>
|
|
<Link href={`/dashboard/videos/${id}/edit`}>
|
|
<ActionButton variant="outline" size="sm">
|
|
Edit Video
|
|
</ActionButton>
|
|
</Link>
|
|
</div>
|
|
|
|
{/* Video Player */}
|
|
<div className="aspect-video bg-black rounded-xl overflow-hidden border border-white/10 shadow-2xl">
|
|
{youtubeId ? (
|
|
<iframe
|
|
src={embedUrl}
|
|
title={video.title}
|
|
className="w-full h-full"
|
|
allowFullScreen
|
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
|
></iframe>
|
|
) : (
|
|
<div className="flex items-center justify-center h-full text-gray-500">
|
|
Video format not supported or invalid URL
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Video Info */}
|
|
<div className="bg-white/10 backdrop-blur-sm rounded-xl p-6 border border-white/20">
|
|
<div className="flex justify-between items-start mb-4">
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-white mb-2">{video.title}</h1>
|
|
<div className="flex items-center text-sm text-gray-400 space-x-4">
|
|
<span>Diunggah oleh <span className="text-gold-400">{video.uploader.user.name}</span></span>
|
|
<span>•</span>
|
|
<span>{new Date(video.createdAt).toLocaleDateString()}</span>
|
|
<span>•</span>
|
|
<span>{video.viewCount} kali ditonton</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="prose prose-invert max-w-none">
|
|
<div className="text-gray-300 leading-relaxed" dangerouslySetInnerHTML={{ __html: video.description.replace(/\n/g, '<br/>') }}>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Comments Section */}
|
|
<div className="bg-white/10 backdrop-blur-sm rounded-xl p-6 border border-white/20">
|
|
<h3 className="text-lg font-semibold text-white mb-4">Komentar</h3>
|
|
<div className="space-y-4">
|
|
{video.comments?.map((comment) => (
|
|
<div key={comment.id} className="flex space-x-4">
|
|
<div className="w-10 h-10 bg-gold-400 rounded-full flex items-center justify-center flex-shrink-0">
|
|
<span className="text-navy-900 font-bold">{comment.author.user.name.charAt(0)}</span>
|
|
</div>
|
|
<div className="flex-1">
|
|
<div className="bg-white/5 rounded-lg p-3">
|
|
<div className="flex justify-between items-start mb-1">
|
|
<span className="font-medium text-white">{comment.author.user.name}</span>
|
|
<span className="text-xs text-gray-500">{new Date(comment.createdAt).toLocaleDateString()}</span>
|
|
</div>
|
|
<p className="text-gray-300 text-sm">{comment.content}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
{(!video.comments || video.comments.length === 0) && (
|
|
<p className="text-gray-400 text-sm">Belum ada komentar.</p>
|
|
)}
|
|
|
|
{/* Comment Form */}
|
|
<div className="mt-6 flex space-x-4">
|
|
<div className="w-10 h-10 bg-gray-600 rounded-full flex items-center justify-center flex-shrink-0">
|
|
<span className="text-white font-bold">ME</span>
|
|
</div>
|
|
<div className="flex-1">
|
|
<textarea
|
|
rows={2}
|
|
value={commentText}
|
|
onChange={(e) => setCommentText(e.target.value)}
|
|
className="w-full px-4 py-2 border rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-gold-400 placeholder-gray-500"
|
|
placeholder="Tulis komentar..."
|
|
></textarea>
|
|
<div className="mt-2 flex justify-end">
|
|
<ActionButton size="sm" onClick={handleSubmitComment} loading={submittingComment} disabled={!commentText.trim()}>
|
|
Kirim
|
|
</ActionButton>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
} |