"use client"; import React, { useState, useCallback } from 'react'; import { Upload, X, Video as VideoIcon } from 'lucide-react'; import { AIGeneratedBadge } from '../ui/ai-generated-badge'; import { Button } from '../ui/button'; import { Input } from '../ui/input'; import { Label } from '../ui/label'; import { Badge } from '../ui/badge'; import { YouTubeIcon } from '../icons/youtube-icon'; import { Video } from './video'; export type VideoSourceType = 'youtube' | 'uploaded'; export interface VideoSourceSelectorProps { /** Current video source type selection */ videoSourceType: VideoSourceType; /** Callback when video source type changes */ onVideoSourceTypeChange: (type: VideoSourceType) => void; /** YouTube URL value */ youtubeUrl: string; /** Callback when YouTube URL changes */ onYoutubeUrlChange: (url: string) => void; /** Uploaded video URL value */ mainVideoUrl: string; /** Callback when uploaded video URL changes */ /** * Fired with the uploaded/typed URL. May return a promise — the upload * spinner stays up until it settles, so post-upload work (persisting the * URL, capturing + persisting the poster) finishes BEFORE the loading * state clears. Without this, the modal can be closed mid-capture and the * thumbnail is silently lost. */ onMainVideoUrlChange: (url: string) => void | Promise; /** Callback to handle video upload - receives file and returns URL or throws error */ onUploadVideo: (file: File, onProgress?: (progress: number) => void) => Promise; /** Optional: Show AI generated badge on uploaded video */ showAIBadge?: boolean; /** Optional: Whether the video was AI generated */ isAIGenerated?: boolean; /** Optional: Custom label for YouTube section */ youtubeLabel?: string; /** Optional: Custom label for upload section */ uploadLabel?: string; /** Optional: Custom placeholder for YouTube URL input */ youtubePlaceholder?: string; /** Optional: Custom helper text for YouTube section */ youtubeHelperText?: string; /** Optional: Custom empty state text for upload section */ uploadEmptyText?: string; /** Optional: Disable the component */ disabled?: boolean; /** Optional: Custom video preview component */ VideoPreviewComponent?: React.ComponentType<{ videoUrl: string; onDelete: () => void; isAIGenerated?: boolean; }>; /** Optional: Upload progress bar component */ UploadProgressComponent?: React.ComponentType<{ progress: number; message: string; className?: string; }>; /** Optional: Section title */ title?: string; /** Optional: Show section title */ showTitle?: boolean; /** Optional: Additional class name */ className?: string; } /** * VideoSourceSelector - Unified component for selecting between YouTube URL and uploaded video * * This component provides a toggle between YouTube URL input and video upload, * without clearing data when switching between options. */ export function VideoSourceSelector({ videoSourceType, onVideoSourceTypeChange, youtubeUrl, onYoutubeUrlChange, mainVideoUrl, onMainVideoUrlChange, onUploadVideo, showAIBadge = true, isAIGenerated = false, youtubeLabel = 'YouTube Video URL', uploadLabel = 'Uploaded Video', youtubePlaceholder = 'https://youtube.com/watch?v=...', youtubeHelperText = 'YouTube videos will be embedded. AI processing is not available for YouTube links.', uploadEmptyText = 'No video uploaded yet. Click "Upload Video" to add one.', disabled = false, VideoPreviewComponent, UploadProgressComponent, title = 'Video', showTitle = true, className = '', }: VideoSourceSelectorProps) { const [isUploading, setIsUploading] = useState(false); const [uploadProgress, setUploadProgress] = useState(0); const [uploadMessage, setUploadMessage] = useState(''); const [uploadError, setUploadError] = useState(null); const handleUploadClick = useCallback(() => { const input = document.createElement('input'); input.type = 'file'; input.accept = 'video/*'; input.onchange = async (e: Event) => { const target = e.target as HTMLInputElement; const file = target.files?.[0]; if (!file) return; setIsUploading(true); setUploadProgress(0); setUploadMessage('Uploading video...'); setUploadError(null); try { const url = await onUploadVideo(file, (progress) => { setUploadProgress(progress); }); setUploadProgress(100); setUploadMessage('Upload complete — selecting thumbnail...'); // Await the host's attach chain (persist URL -> capture poster -> // persist poster). isUploading holds until it settles, so closing // the modal can no longer race the 15-60s thumbnail capture. await onMainVideoUrlChange(url); setUploadMessage('Video attached!'); } catch (err) { setUploadError(err instanceof Error ? err.message : 'Failed to upload video'); } finally { setIsUploading(false); setTimeout(() => { setUploadProgress(0); setUploadMessage(''); }, 1000); } }; input.click(); }, [onUploadVideo, onMainVideoUrlChange]); const handleDeleteVideo = useCallback(() => { onMainVideoUrlChange(''); }, [onMainVideoUrlChange]); return (
{/* Section Title */} {showTitle && (

{title}

)} {/* Video Source Type Toggle */}
{/* YouTube URL Input */} {videoSourceType === 'youtube' && (
onYoutubeUrlChange(e.target.value)} placeholder={youtubePlaceholder} className="bg-ods-bg" disabled={disabled} /> {youtubeHelperText && (

{youtubeHelperText}

)}
)} {/* Uploaded Video Section */} {videoSourceType === 'uploaded' && (
{showAIBadge && isAIGenerated && ( )}
{/* Upload Progress */} {isUploading && UploadProgressComponent && ( )} {/* Simple progress bar fallback if no custom component */} {isUploading && !UploadProgressComponent && (
{uploadProgress}%

{uploadMessage}

)} {/* Upload Error */} {uploadError && (

{uploadError}

)} {/* Video Preview */} {mainVideoUrl ? ( VideoPreviewComponent ? ( ) : ( ) ) : (

{uploadEmptyText}

)}
)}
); } interface DefaultVideoPreviewProps { /** Uploaded video URL — MP4 or Mux HLS manifest, both playable via