'use client'; import { useState, useRef, useCallback, useEffect } from 'react'; import Link from 'next/link'; import { useParams } from 'next/navigation'; import { ArrowLeft, Play, Pause, Volume2, VolumeX, Maximize, MessageSquare, Mic, Send, Clock, CheckCircle2, Circle, ChevronDown, MoreVertical, User, SkipBack, SkipForward } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Card, CardContent } from '@/components/ui/card'; import { Textarea } from '@/components/ui/textarea'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Separator } from '@/components/ui/separator'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { cn } from '@/lib/utils'; // Mock data const mockVideo = { id: 'v1', title: 'Main Product Walkthrough', description: 'Complete walkthrough of the new product features', projectId: '1', projectName: 'Product Demo v2', versions: [ { id: 'ver3', number: 3, label: 'Final Cut', isActive: true }, { id: 'ver2', number: 2, label: 'Review Round 2', isActive: false }, { id: 'ver1', number: 1, label: 'First Draft', isActive: false }, ], currentVersion: { id: 'ver3', number: 3, label: 'Final Cut', providerId: 'youtube', videoId: 'dQw4w9WgXcQ', // Sample video duration: 342, // 5:42 }, }; const mockComments = [ { id: 'c1', content: 'The transition here feels a bit abrupt. Can we add a fade?', timestamp: 45.5, author: { name: 'Sarah Chen', image: null }, createdAt: '2 hours ago', isResolved: false, replies: [ { id: 'c1r1', content: 'Good catch! I\'ll smooth that out in the next version.', author: { name: 'Mike Johnson', image: null }, createdAt: '1 hour ago', }, ], }, { id: 'c2', content: 'Love this section! The pacing is perfect.', timestamp: 120, author: { name: 'Alex Rivera', image: null }, createdAt: '5 hours ago', isResolved: true, replies: [], }, { id: 'c3', content: 'Can we add some background music here?', timestamp: 200, voiceUrl: '/mock-voice.mp3', // Mock voice comment voiceDuration: 8.5, author: { name: 'Jordan Lee', image: null }, createdAt: '1 day ago', isResolved: false, replies: [], }, ]; function formatTime(seconds: number): string { const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins}:${secs.toString().padStart(2, '0')}`; } export default function VideoPage() { const params = useParams(); const videoId = params.videoId as string; // In real app, fetch projectId from video data const projectId = mockVideo.projectId; const iframeRef = useRef(null); const playerRef = useRef(null); const timelineRef = useRef(null); const [isReady, setIsReady] = useState(false); const [isPlaying, setIsPlaying] = useState(false); const [currentTime, setCurrentTime] = useState(0); const [duration, setDuration] = useState(mockVideo.currentVersion.duration); const [isMuted, setIsMuted] = useState(false); const [isDragging, setIsDragging] = useState(false); const [commentText, setCommentText] = useState(''); const [isRecording, setIsRecording] = useState(false); const [selectedTimestamp, setSelectedTimestamp] = useState(null); const [comments, setComments] = useState(mockComments); const [showResolved, setShowResolved] = useState(false); // Load YouTube iframe API useEffect(() => { // Load YouTube iframe API script if (!window.YT) { const tag = document.createElement('script'); tag.src = 'https://www.youtube.com/iframe_api'; const firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode?.insertBefore(tag, firstScriptTag); } // Initialize player when API is ready const onYouTubeIframeAPIReady = () => { playerRef.current = new window.YT.Player(iframeRef.current, { events: { onReady: () => { setIsReady(true); setDuration(playerRef.current.getDuration() || mockVideo.currentVersion.duration); }, onStateChange: (event: any) => { setIsPlaying(event.data === window.YT.PlayerState.PLAYING); }, }, }); }; if (window.YT && window.YT.Player) { onYouTubeIframeAPIReady(); } else { window.onYouTubeIframeAPIReady = onYouTubeIframeAPIReady; } return () => { window.onYouTubeIframeAPIReady = undefined; }; }, []); // Update current time periodically useEffect(() => { if (!isReady || !playerRef.current) return; const interval = setInterval(() => { if (playerRef.current?.getCurrentTime && !isDragging) { setCurrentTime(playerRef.current.getCurrentTime()); } }, 100); return () => clearInterval(interval); }, [isReady, isDragging]); const filteredComments = comments.filter(c => showResolved || !c.isResolved); const handlePlayPause = useCallback(() => { if (!playerRef.current) return; if (isPlaying) { playerRef.current.pauseVideo(); } else { playerRef.current.playVideo(); } }, [isPlaying]); const handleSeekToTimestamp = useCallback((timestamp: number) => { setCurrentTime(timestamp); if (playerRef.current?.seekTo) { playerRef.current.seekTo(timestamp, true); } }, []); const handleTimelineClick = useCallback((e: React.MouseEvent) => { if (!timelineRef.current) return; const rect = timelineRef.current.getBoundingClientRect(); const x = e.clientX - rect.left; const percentage = Math.max(0, Math.min(1, x / rect.width)); const newTime = percentage * duration; handleSeekToTimestamp(newTime); }, [duration, handleSeekToTimestamp]); const handleTimelineMouseDown = useCallback((e: React.MouseEvent) => { setIsDragging(true); handleTimelineClick(e); }, [handleTimelineClick]); const handleTimelineMouseMove = useCallback((e: React.MouseEvent) => { if (!isDragging || !timelineRef.current) return; const rect = timelineRef.current.getBoundingClientRect(); const x = e.clientX - rect.left; const percentage = Math.max(0, Math.min(1, x / rect.width)); const newTime = percentage * duration; setCurrentTime(newTime); }, [isDragging, duration]); const handleTimelineMouseUp = useCallback(() => { if (isDragging) { handleSeekToTimestamp(currentTime); setIsDragging(false); } }, [isDragging, currentTime, handleSeekToTimestamp]); const handleMuteToggle = useCallback(() => { if (!playerRef.current) return; if (isMuted) { playerRef.current.unMute(); } else { playerRef.current.mute(); } setIsMuted(!isMuted); }, [isMuted]); const handleSkip = useCallback((seconds: number) => { const newTime = Math.max(0, Math.min(duration, currentTime + seconds)); handleSeekToTimestamp(newTime); }, [currentTime, duration, handleSeekToTimestamp]); const handleAddComment = useCallback(() => { if (!commentText.trim() && !isRecording) return; const newComment = { id: `c${Date.now()}`, content: commentText, timestamp: selectedTimestamp ?? currentTime, author: { name: 'You', image: null }, createdAt: 'Just now', isResolved: false, replies: [], }; setComments(prev => [...prev, newComment]); setCommentText(''); setSelectedTimestamp(null); }, [commentText, currentTime, selectedTimestamp, isRecording]); const handleResolveComment = useCallback((commentId: string) => { setComments(prev => prev.map(c => c.id === commentId ? { ...c, isResolved: !c.isResolved } : c )); }, []); // Hide YouTube controls, enable JS API const embedUrl = `https://www.youtube.com/embed/${mockVideo.currentVersion.videoId}?enablejsapi=1&rel=0&modestbranding=1&controls=0&showinfo=0&iv_load_policy=3&disablekb=1`; return (
isDragging && handleTimelineMouseUp()} > {/* Main Content - Full Width Layout */}
{/* Video Area */}
{/* Compact Header Bar */}
Back
{mockVideo.title} • {mockVideo.projectName}
{/* Version Selector */} {mockVideo.versions.map((version) => ( v{version.number} {version.label} ))}
{/* Video Player - Maximized */}