/** * @license * SPDX-License-Identifier: Apache-2.0 */ import React, { useState, useEffect, useRef } from 'react'; import { X, Plus, Trash2, HelpCircle, Film, Layers, Link, HardDrive, Upload, Loader2, Check, FileVideo, Folder, Cloud } from 'lucide-react'; import { ContentItem, Season, Episode } from '../types'; interface CreatorModalProps { editItem?: ContentItem | null; // If passed, we are in edit mode onClose: () => void; onSave: (item: ContentItem) => void; } export default function CreatorModal({ editItem, onClose, onSave }: CreatorModalProps) { const [type, setType] = useState<'movie' | 'series'>('movie'); const [title, setTitle] = useState(''); const [poster, setPoster] = useState(''); const [synopsis, setSynopsis] = useState(''); const [category, setCategory] = useState(''); const [year, setYear] = useState('2026'); const [duration, setDuration] = useState(''); const [message, setMessage] = useState(''); const [file, setFile] = useState(''); const [seasons, setSeasons] = useState([]); // Video source controls for Movies const [movieSourceType, setMovieSourceType] = useState<'url' | 'drive' | 'pc'>('url'); const [movieUploadProgress, setMovieUploadProgress] = useState(null); const [movieFileName, setMovieFileName] = useState(''); const movieFileInputRef = useRef(null); // Simulated Google Drive states const [showDrivePicker, setShowDrivePicker] = useState(false); const [activeDrivePickerTarget, setActiveDrivePickerTarget] = useState<{ type: 'movie' | 'episode'; seasonId?: string; episodeId?: string; } | null>(null); const [driveConnecting, setDriveConnecting] = useState(false); // Prefill values if in edit mode useEffect(() => { if (editItem) { setType(editItem.type); setTitle(editItem.title || ''); setPoster(editItem.poster || ''); setSynopsis(editItem.synopsis || ''); setCategory(editItem.category || ''); setYear(editItem.year || '2026'); setDuration(editItem.duration || ''); setMessage(editItem.message || ''); setFile(editItem.file || ''); setSeasons(editItem.seasons ? JSON.parse(JSON.stringify(editItem.seasons)) : []); if (editItem.file) { if (editItem.file.startsWith('blob:')) { setMovieSourceType('pc'); setMovieFileName('Video local cargado de la PC'); } else if (editItem.file.includes('drive.google.com') || editItem.file.includes('googleusercontent')) { setMovieSourceType('drive'); setMovieFileName('Archivo de Google Drive seleccionado'); } else { setMovieSourceType('url'); } } } else { // Defaults for new item setType('movie'); setTitle(''); setPoster(''); setSynopsis(''); setCategory('Acción / Sci-Fi'); setYear('2026'); setDuration(''); setMessage(''); setFile('https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4'); setSeasons([ { id: `season-${Date.now()}`, name: 'Temporada 1', episodes: [ { id: `ep-${Date.now()}-1`, name: 'Episodio 1: Piloto', file: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4', duration: '10 min', synopsis: 'Introducción a la historia y personajes principales.' } ] } ]); } }, [editItem]); // Handle local video upload from PC (Movies) const handleMovieLocalFileChange = (e: React.ChangeEvent) => { const localFile = e.target.files?.[0]; if (!localFile) return; setMovieFileName(localFile.name); setMovieUploadProgress(0); // Simulate high quality upload transition progress let progress = 0; const interval = setInterval(() => { progress += 12; if (progress >= 100) { progress = 100; clearInterval(interval); setTimeout(() => { setMovieUploadProgress(null); // Create real reproducible blob URL in client browser! const blobUrl = URL.createObjectURL(localFile); setFile(blobUrl); }, 400); } setMovieUploadProgress(progress); }, 120); }; // Simulated Google Drive sample video picker selection const handleSelectDriveFile = (driveFileUrl: string, driveFileName: string) => { if (!activeDrivePickerTarget) return; setDriveConnecting(true); setTimeout(() => { setDriveConnecting(false); setShowDrivePicker(false); if (activeDrivePickerTarget.type === 'movie') { setFile(driveFileUrl); setMovieFileName(driveFileName); setMovieSourceType('drive'); } else if (activeDrivePickerTarget.type === 'episode' && activeDrivePickerTarget.seasonId && activeDrivePickerTarget.episodeId) { const sId = activeDrivePickerTarget.seasonId; const eId = activeDrivePickerTarget.episodeId; setSeasons(prev => prev.map(s => { if (s.id === sId) { return { ...s, episodes: s.episodes.map(e => e.id === eId ? { ...e, file: driveFileUrl, driveFileName: driveFileName } : e) }; } return s; }) ); } setActiveDrivePickerTarget(null); }, 800); // 800ms loading simulator }; // Handle local episode upload from PC (Series) const handleEpisodeLocalFileChange = (seasonId: string, episodeId: string, e: React.ChangeEvent) => { const localFile = e.target.files?.[0]; if (!localFile) return; // Trigger local simulation of progress directly const elementId = `ep-progress-${episodeId}`; const progressTextId = `ep-text-${episodeId}`; // Quick update via manual DOM state since state is inside a nested array const progressEl = document.getElementById(elementId); const progressTextEl = document.getElementById(progressTextId); if (progressEl) progressEl.style.width = '0%'; if (progressTextEl) progressTextEl.innerText = 'Subiendo: 0%'; let progress = 0; const interval = setInterval(() => { progress += 20; if (progressEl) progressEl.style.width = `${progress}%`; if (progressTextEl) progressTextEl.innerText = `Subiendo: ${progress}%`; if (progress >= 100) { clearInterval(interval); setTimeout(() => { if (progressTextEl) progressTextEl.innerText = `Listo: ${localFile.name}`; const blobUrl = URL.createObjectURL(localFile); setSeasons(prev => prev.map(s => { if (s.id === seasonId) { return { ...s, episodes: s.episodes.map(e => e.id === episodeId ? { ...e, file: blobUrl, pcFileName: localFile.name } : e) }; } return s; }) ); }, 300); } }, 100); }; const handleAddSeason = () => { const seasonId = `season-${Date.now()}-${Math.random().toString(36).substr(2, 4)}`; const newSeason: Season = { id: seasonId, name: `Temporada ${seasons.length + 1}`, episodes: [ { id: `ep-${Date.now()}-${Math.random().toString(36).substr(2, 4)}`, name: 'Episodio 1: Inicio', file: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4', duration: '10 min', synopsis: 'Sinopsis del nuevo episodio.' } ] }; setSeasons([...seasons, newSeason]); }; const handleRemoveSeason = (seasonId: string) => { setSeasons(seasons.filter((s) => s.id !== seasonId)); }; const handleSeasonNameChange = (seasonId: string, value: string) => { setSeasons( seasons.map((s) => (s.id === seasonId ? { ...s, name: value } : s)) ); }; const handleAddEpisode = (seasonId: string) => { setSeasons( seasons.map((s) => { if (s.id === seasonId) { const epId = `ep-${Date.now()}-${Math.random().toString(36).substr(2, 4)}`; const newEp: Episode = { id: epId, name: `Episodio ${s.episodes.length + 1}`, file: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4', duration: '12 min', synopsis: '' }; return { ...s, episodes: [...s.episodes, newEp] }; } return s; }) ); }; const handleRemoveEpisode = (seasonId: string, episodeId: string) => { setSeasons( seasons.map((s) => { if (s.id === seasonId) { return { ...s, episodes: s.episodes.filter((e) => e.id !== episodeId) }; } return s; }) ); }; const handleEpisodeChange = ( seasonId: string, episodeId: string, field: keyof Episode, value: string ) => { setSeasons( seasons.map((s) => { if (s.id === seasonId) { const updatedEpisodes = s.episodes.map((e) => { if (e.id === episodeId) { return { ...e, [field]: value }; } return e; }); return { ...s, episodes: updatedEpisodes }; } return s; }) ); }; const handleSave = (e: React.FormEvent) => { e.preventDefault(); if (!title.trim()) { alert('Por favor introduce un título válido.'); return; } // Default poster if empty const posterUrl = poster.trim() || 'https://images.unsplash.com/photo-1594909122845-11baa439b7bf?q=80&w=800&auto=format&fit=crop'; const savedItem: ContentItem = { id: editItem ? editItem.id : `custom-${Date.now()}`, type, title, poster: posterUrl, synopsis, category, year, duration: type === 'movie' ? duration || '1h 30m' : `${seasons.length} ${seasons.length === 1 ? 'Temporada' : 'Temporadas'}`, likes: editItem ? editItem.likes : 0, dislikes: editItem ? editItem.dislikes : 0, message: message.trim() || undefined, file: type === 'movie' ? file : undefined, seasons: type === 'series' ? seasons : undefined }; onSave(savedItem); }; // Helper to prefill beautiful Unsplash images on click const setPredefinedPoster = (url: string) => { setPoster(url); }; return (
{/* Modal Header */}

{editItem ? 'Editar Contenido' : 'Publicar Nuevo Contenido'}

{/* Modal Form */}
{/* Format Type Selector */}
{/* Title & Metadata */}
setTitle(e.target.value)} placeholder="Ej: Sombras de Neon" className="w-full bg-white/[0.04] border border-white/10 focus:border-red-500 rounded-xl px-4 py-2.5 text-white outline-none transition-colors" />
setYear(e.target.value)} placeholder="2026" className="w-full bg-white/[0.04] border border-white/10 focus:border-red-500 rounded-xl px-4 py-2.5 text-white outline-none transition-colors" />
setCategory(e.target.value)} placeholder="Ej: Cyberpunk / Thriller" className="w-full bg-white/[0.04] border border-white/10 focus:border-red-500 rounded-xl px-4 py-2.5 text-white outline-none transition-colors" />
setDuration(e.target.value)} placeholder={type === 'movie' ? "Ej: 1h 45m" : "Ej: 1 Temporada"} className="w-full bg-white/[0.04] disabled:opacity-50 border border-white/10 focus:border-red-500 rounded-xl px-4 py-2.5 text-white outline-none transition-colors" />
{/* Synopsis */}