Compare commits

..

2 Commits

Author SHA1 Message Date
Cauê Faleiros
1d5eafbbd0 feat: simplify instructor video uploads
All checks were successful
CI / Validate frontend and API (push) Successful in 46s
CI / Build and publish Docker images (push) Successful in 20s
2026-09-04 14:16:02 -03:00
Cauê Faleiros
b1bbf35751 feat: streamline course editor workflow 2026-09-04 13:53:35 -03:00

View File

@@ -23,8 +23,12 @@ import {
Lock, Lock,
Unlock, Unlock,
Globe, Globe,
Download Download,
,Copy, ChevronUp, ChevronDown Copy,
ChevronUp,
ChevronDown,
ChevronLeft,
ChevronRight,
} from 'lucide-react'; } from 'lucide-react';
import { Course, Comment, Attachment, Lesson, AttachmentType, LessonMedia } from '../types'; import { Course, Comment, Attachment, Lesson, AttachmentType, LessonMedia } from '../types';
import { useToast } from '../context/ToastContext'; import { useToast } from '../context/ToastContext';
@@ -49,6 +53,7 @@ const CourseModal: React.FC<{
const [coverConfigured, setCoverConfigured] = useState<boolean | null>(null); const [coverConfigured, setCoverConfigured] = useState<boolean | null>(null);
const [coverUploadMessage, setCoverUploadMessage] = useState(''); const [coverUploadMessage, setCoverUploadMessage] = useState('');
const [isCoverUploading, setIsCoverUploading] = useState(false); const [isCoverUploading, setIsCoverUploading] = useState(false);
const [editorStep, setEditorStep] = useState<1 | 2 | 3>(1);
// Lesson Management // Lesson Management
const [lessons, setLessons] = useState<Lesson[]>([]); const [lessons, setLessons] = useState<Lesson[]>([]);
@@ -56,8 +61,6 @@ const CourseModal: React.FC<{
// New Lesson State // New Lesson State
const [newLessonTitle, setNewLessonTitle] = useState(''); const [newLessonTitle, setNewLessonTitle] = useState('');
const [newLessonDuration, setNewLessonDuration] = useState(''); const [newLessonDuration, setNewLessonDuration] = useState('');
const [newLessonVideoUrl, setNewLessonVideoUrl] = useState('');
const [newLessonProvider, setNewLessonProvider] = useState('external');
const [newLessonMedia, setNewLessonMedia] = useState<LessonMedia | null>(null); const [newLessonMedia, setNewLessonMedia] = useState<LessonMedia | null>(null);
const [newLessonIsFree, setNewLessonIsFree] = useState(false); const [newLessonIsFree, setNewLessonIsFree] = useState(false);
const [bunnyConfigured, setBunnyConfigured] = useState<boolean | null>(null); const [bunnyConfigured, setBunnyConfigured] = useState<boolean | null>(null);
@@ -80,6 +83,7 @@ const CourseModal: React.FC<{
useEffect(() => { useEffect(() => {
if (isOpen) { if (isOpen) {
setEditorStep(1);
if (initialData) { if (initialData) {
setTitle(initialData.title); setTitle(initialData.title);
setDescription(initialData.description); setDescription(initialData.description);
@@ -134,12 +138,12 @@ const CourseModal: React.FC<{
return; return;
} }
if (!bunnyConfigured) { if (!bunnyConfigured) {
alert('A integração com Bunny Stream ainda não foi configurada pelo administrador.'); alert('O envio de vídeos ainda não está disponível. Tente novamente mais tarde ou entre em contato com o administrador.');
return; return;
} }
setBunnyUploadState('uploading'); setBunnyUploadState('uploading');
setBunnyUploadMessage(`Enviando ${file.name} para Bunny Stream...`); setBunnyUploadMessage(`Enviando ${file.name}...`);
try { try {
const created = await instructorApi.createBunnyVideo(newLessonTitle.trim()); const created = await instructorApi.createBunnyVideo(newLessonTitle.trim());
const uploaded = await instructorApi.uploadBunnyVideo(created.data.id, file); const uploaded = await instructorApi.uploadBunnyVideo(created.data.id, file);
@@ -152,10 +156,10 @@ const CourseModal: React.FC<{
setBunnyUploadState('uploaded'); setBunnyUploadState('uploaded');
setBunnyUploadMessage(uploaded.data.status === 'ready' setBunnyUploadMessage(uploaded.data.status === 'ready'
? 'Vídeo pronto para ser adicionado à aula.' ? 'Vídeo pronto para ser adicionado à aula.'
: 'Upload concluído. Bunny Stream está processando o vídeo; você já pode salvar a aula.'); : 'Upload concluído. O vídeo está sendo processado; você já pode salvar a aula.');
} catch (error) { } catch (error) {
setBunnyUploadState('error'); setBunnyUploadState('error');
setBunnyUploadMessage(error instanceof Error ? error.message : 'Não foi possível enviar o vídeo para Bunny Stream.'); setBunnyUploadMessage('Não foi possível enviar o vídeo. Tente novamente.');
} }
}; };
@@ -236,8 +240,8 @@ const CourseModal: React.FC<{
alert("Digite um título para a aula."); alert("Digite um título para a aula.");
return; return;
} }
if (!newLessonMedia && !newLessonVideoUrl.trim()) { if (!newLessonMedia) {
alert('Envie um vídeo para Bunny Stream ou informe uma URL de vídeo hospedado.'); alert('Envie o vídeo da aula antes de adicioná-la.');
return; return;
} }
@@ -245,9 +249,9 @@ const CourseModal: React.FC<{
id: `lesson-${Date.now()}`, id: `lesson-${Date.now()}`,
title: newLessonTitle, title: newLessonTitle,
duration: newLessonDuration || '15m', duration: newLessonDuration || '15m',
videoUrl: newLessonMedia?.playbackUrl || newLessonVideoUrl, videoUrl: newLessonMedia.playbackUrl || '',
mediaProvider: newLessonMedia?.provider || newLessonProvider || 'external', mediaProvider: newLessonMedia.provider,
media: newLessonMedia ? [newLessonMedia] : undefined, media: [newLessonMedia],
isFree: newLessonIsFree, isFree: newLessonIsFree,
description: '' description: ''
}; };
@@ -256,8 +260,6 @@ const CourseModal: React.FC<{
setNewLessonTitle(''); setNewLessonTitle('');
setNewLessonDuration(''); setNewLessonDuration('');
setNewLessonVideoUrl('');
setNewLessonProvider('external');
setNewLessonMedia(null); setNewLessonMedia(null);
setNewLessonIsFree(false); setNewLessonIsFree(false);
setBunnyUploadState('idle'); setBunnyUploadState('idle');
@@ -320,8 +322,19 @@ const CourseModal: React.FC<{
setAttachments(attachments.filter(a => a.id !== id)); setAttachments(attachments.filter(a => a.id !== id));
}; };
const handleSubmit = async (e: React.FormEvent) => { const goToEditorStep = (nextStep: 1 | 2 | 3) => {
e.preventDefault(); if (nextStep > 1 && (!title.trim() || !category)) {
alert('Informe o título e a categoria do curso antes de continuar.');
return;
}
if (nextStep > 2 && lessons.length === 0) {
alert('Adicione pelo menos uma aula antes de continuar.');
return;
}
setEditorStep(nextStep);
};
const handleSubmit = async () => {
if (lessons.length === 0) { if (lessons.length === 0) {
alert("Adicione pelo menos uma aula à trilha."); alert("Adicione pelo menos uma aula à trilha.");
return; return;
@@ -360,14 +373,30 @@ const CourseModal: React.FC<{
return ( return (
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4"> <div className="fixed inset-0 z-[100] flex items-center justify-center p-4">
<div className="absolute inset-0 bg-black/80 backdrop-blur-xl" onClick={onClose} /> <div className="absolute inset-0 bg-black/80 backdrop-blur-xl" onClick={onClose} />
<div className="relative w-full max-w-6xl bg-[#141416] rounded-3xl border border-white/10 p-6 md:p-8 shadow-2xl animate-fade-in-up max-h-[92vh] overflow-y-auto flex flex-col lg:flex-row gap-8"> <div className="relative w-full max-w-5xl bg-[#141416] rounded-3xl border border-white/10 p-5 md:p-8 shadow-2xl animate-fade-in-up max-h-[92vh] overflow-y-auto">
<div className="flex items-start justify-between gap-4 mb-7">
{/* Left Column: Course Details, Tips & Downloads */} <div>
<div className="w-full lg:w-5/12 space-y-6"> <p className="text-xs font-bold uppercase tracking-wider text-orange-400">{initialData ? 'Editar curso' : 'Novo curso'}</p>
<div className="flex justify-between items-center lg:hidden mb-2"> <h2 className="mt-1 text-2xl font-extrabold text-white">{editorStep === 1 ? 'Informações do curso' : editorStep === 2 ? 'Estruture as aulas' : 'Materiais complementares'}</h2>
<h2 className="text-xl font-bold text-white">{initialData ? 'Editar Curso' : 'Criar Curso'}</h2> <p className="mt-1 text-sm text-gray-500">{editorStep === 1 ? 'Defina como o curso será apresentado.' : editorStep === 2 ? 'Adicione e organize a trilha de aprendizado.' : 'Inclua os arquivos e links de apoio.'}</p>
<button onClick={onClose}><X className="text-white" /></button>
</div> </div>
<button type="button" onClick={onClose} className="shrink-0 rounded-full bg-white/5 p-2.5 text-gray-300 hover:bg-white/10 hover:text-white" aria-label="Fechar editor"><X className="w-5 h-5" /></button>
</div>
<div className="mb-8 grid grid-cols-3 gap-2" aria-label="Etapas de criação do curso">
{[
{ step: 1, label: 'Curso' },
{ step: 2, label: 'Aulas' },
{ step: 3, label: 'Materiais' },
].map(({ step, label }) => (
<button key={step} type="button" onClick={() => goToEditorStep(step as 1 | 2 | 3)} className={`flex items-center gap-2 rounded-xl border px-3 py-3 text-left transition-colors ${editorStep === step ? 'border-orange-500/50 bg-orange-500/10 text-white' : editorStep > step ? 'border-emerald-500/20 bg-emerald-500/[0.04] text-emerald-300' : 'border-white/10 bg-black/20 text-gray-500 hover:text-gray-300'}`}>
<span className={`flex h-6 w-6 items-center justify-center rounded-full text-xs font-bold ${editorStep === step ? 'bg-orange-500 text-white' : editorStep > step ? 'bg-emerald-500/20 text-emerald-300' : 'bg-white/5'}`}>{editorStep > step ? <Check className="w-3.5 h-3.5" /> : step}</span>
<span className="text-xs font-semibold uppercase tracking-wide">{label}</span>
</button>
))}
</div>
<div className={`w-full ${editorStep === 1 || editorStep === 3 ? 'block' : 'hidden'}`}>
<div className={editorStep === 1 ? 'space-y-6' : 'hidden'}>
{/* Thumbnail */} {/* Thumbnail */}
<div> <div>
@@ -402,7 +431,7 @@ const CourseModal: React.FC<{
disabled={!coverConfigured || isCoverUploading} disabled={!coverConfigured || isCoverUploading}
onChange={handleCoverUpload} onChange={handleCoverUpload}
/> />
<p className={`mt-2 text-[11px] ${coverUploadMessage.includes('sucesso') ? 'text-emerald-300' : coverUploadMessage ? 'text-amber-300' : 'text-gray-500'}`}>{coverUploadMessage || (coverConfigured ? 'JPG, PNG ou WebP. Tamanho máximo: 10 MB.' : 'O upload de capas será habilitado após configurar Bunny Storage no servidor.')}</p> <p className={`mt-2 text-[11px] ${coverUploadMessage.includes('sucesso') ? 'text-emerald-300' : coverUploadMessage ? 'text-amber-300' : 'text-gray-500'}`}>{coverUploadMessage || (coverConfigured ? 'JPG, PNG ou WebP. Tamanho máximo: 10 MB.' : 'O envio de imagens estará disponível assim que o armazenamento de mídia for configurado.')}</p>
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
@@ -412,7 +441,7 @@ const CourseModal: React.FC<{
type="text" type="text"
value={title} value={title}
onChange={(e) => setTitle(e.target.value)} onChange={(e) => setTitle(e.target.value)}
placeholder="Ex: Masterclass: Escala de Tráfego Pago" placeholder="Ex.: Estratégias de tráfego pago para e-commerce"
className="w-full bg-black/50 text-white text-base font-bold border border-white/10 rounded-xl px-4 py-3 focus:outline-none focus:border-orange-500/50" className="w-full bg-black/50 text-white text-base font-bold border border-white/10 rounded-xl px-4 py-3 focus:outline-none focus:border-orange-500/50"
required required
/> />
@@ -448,14 +477,19 @@ const CourseModal: React.FC<{
value={description} value={description}
onChange={(e) => setDescription(e.target.value)} onChange={(e) => setDescription(e.target.value)}
rows={3} rows={3}
placeholder="O que os alunos vão aprender e dominar neste curso?" placeholder="Descreva os tópicos, as competências e os resultados esperados para os alunos."
className="w-full bg-black/50 text-white border border-white/10 rounded-xl px-4 py-2.5 text-xs focus:outline-none focus:border-orange-500/50 resize-none leading-relaxed" className="w-full bg-black/50 text-white border border-white/10 rounded-xl px-4 py-2.5 text-xs focus:outline-none focus:border-orange-500/50 resize-none leading-relaxed"
/> />
</div> </div>
</div> </div>
<div className="flex justify-end border-t border-white/5 pt-5">
<button type="button" onClick={() => goToEditorStep(2)} disabled={!title.trim() || !category} className="inline-flex items-center gap-2 rounded-xl bg-orange-500 px-5 py-3 text-sm font-bold text-white transition-colors hover:bg-orange-600 disabled:cursor-not-allowed disabled:opacity-40">Continuar para aulas <ChevronRight className="w-4 h-4" /></button>
</div>
</div>
{/* Downloadable Materials Section */} {/* Downloadable Materials Section */}
<div className="bg-black/40 p-4 rounded-2xl border border-white/10 space-y-3"> <div className={editorStep === 3 ? 'bg-black/40 p-5 rounded-2xl border border-white/10 space-y-3' : 'hidden'}>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h3 className="text-xs font-bold text-orange-400 uppercase tracking-wider flex items-center gap-1.5"> <h3 className="text-xs font-bold text-orange-400 uppercase tracking-wider flex items-center gap-1.5">
<Download className="w-4 h-4" /> <Download className="w-4 h-4" />
@@ -470,7 +504,7 @@ const CourseModal: React.FC<{
type="text" type="text"
value={newAttName} value={newAttName}
onChange={(e) => setNewAttName(e.target.value)} onChange={(e) => setNewAttName(e.target.value)}
placeholder="Nome do arquivo (ex: Planilha_ROAS.xlsx)" placeholder="Ex.: Planilha de acompanhamento de campanhas"
className="col-span-2 bg-white/5 border border-white/10 rounded-lg px-2.5 py-1.5 text-xs text-white focus:outline-none focus:border-orange-500/50" className="col-span-2 bg-white/5 border border-white/10 rounded-lg px-2.5 py-1.5 text-xs text-white focus:outline-none focus:border-orange-500/50"
/> />
<select <select
@@ -491,14 +525,14 @@ const CourseModal: React.FC<{
type="text" type="text"
value={newAttSize} value={newAttSize}
onChange={(e) => setNewAttSize(e.target.value)} onChange={(e) => setNewAttSize(e.target.value)}
placeholder="Tamanho (ex: 2.4 MB)" placeholder="Ex.: 2,4 MB"
className="w-28 bg-white/5 border border-white/10 rounded-lg px-2.5 py-1.5 text-xs text-white focus:outline-none" className="w-28 bg-white/5 border border-white/10 rounded-lg px-2.5 py-1.5 text-xs text-white focus:outline-none"
/> />
<input <input
type="text" type="text"
value={newAttDesc} value={newAttDesc}
onChange={(e) => setNewAttDesc(e.target.value)} onChange={(e) => setNewAttDesc(e.target.value)}
placeholder="Breve descrição do material..." placeholder="Explique como este material apoia o aprendizado."
className="flex-1 bg-white/5 border border-white/10 rounded-lg px-2.5 py-1.5 text-xs text-white focus:outline-none" className="flex-1 bg-white/5 border border-white/10 rounded-lg px-2.5 py-1.5 text-xs text-white focus:outline-none"
/> />
<button <button
@@ -530,21 +564,16 @@ const CourseModal: React.FC<{
</div> </div>
))} ))}
</div> </div>
<div className="mt-5 flex flex-col-reverse gap-3 border-t border-white/5 pt-5 sm:flex-row sm:justify-between">
<button type="button" onClick={() => setEditorStep(2)} className="inline-flex items-center justify-center gap-2 rounded-xl px-4 py-3 text-sm font-semibold text-gray-300 hover:bg-white/5 hover:text-white"><ChevronLeft className="w-4 h-4" /> Voltar para aulas</button>
<button type="button" onClick={() => void handleSubmit()} disabled={isSubmitting} className="inline-flex items-center justify-center gap-2 rounded-xl bg-orange-500 px-6 py-3 text-sm font-extrabold text-white shadow-lg shadow-orange-500/20 transition-colors hover:bg-orange-600 disabled:opacity-60">{isSubmitting ? 'Salvando...' : status === 'draft' ? 'Salvar como rascunho' : 'Salvar e publicar'}</button>
</div>
</div> </div>
</div> </div>
{/* Right Column: Lessons Builder */} {/* Right Column: Lessons Builder */}
<div className="flex-1 flex flex-col justify-between"> <div className={editorStep === 2 ? 'w-full flex flex-col justify-between' : 'hidden'}>
<div> <div>
<div className="hidden lg:flex justify-between items-center mb-6">
<h2 className="text-2xl font-extrabold text-white">
{initialData ? 'Gerenciar Trilha & Aulas' : 'Estruturar Grade de Aulas'}
</h2>
<button onClick={onClose} className="p-2 bg-white/5 rounded-full hover:bg-white/20 transition-colors">
<X className="w-5 h-5 text-white" />
</button>
</div>
<div className="bg-black/50 rounded-2xl border border-white/10 overflow-hidden mb-6"> <div className="bg-black/50 rounded-2xl border border-white/10 overflow-hidden mb-6">
<div className="p-4 border-b border-white/5 bg-zinc-900/50 flex items-center justify-between"> <div className="p-4 border-b border-white/5 bg-zinc-900/50 flex items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@@ -576,7 +605,7 @@ const CourseModal: React.FC<{
<span className="flex items-center gap-1"><Video className="w-3 h-3 text-orange-400/70"/> {lesson.duration}</span> <span className="flex items-center gap-1"><Video className="w-3 h-3 text-orange-400/70"/> {lesson.duration}</span>
{lesson.media?.[0]?.provider === 'bunny' && ( {lesson.media?.[0]?.provider === 'bunny' && (
<span className={`font-bold px-1.5 py-0.2 rounded text-[9px] uppercase ${lesson.media[0].status === 'ready' ? 'text-emerald-400 bg-emerald-500/10' : lesson.media[0].status === 'failed' ? 'text-red-400 bg-red-500/10' : 'text-amber-400 bg-amber-500/10'}`}> <span className={`font-bold px-1.5 py-0.2 rounded text-[9px] uppercase ${lesson.media[0].status === 'ready' ? 'text-emerald-400 bg-emerald-500/10' : lesson.media[0].status === 'failed' ? 'text-red-400 bg-red-500/10' : 'text-amber-400 bg-amber-500/10'}`}>
Bunny: {lesson.media[0].status === 'ready' ? 'pronto' : lesson.media[0].status === 'failed' ? 'falhou' : 'processando'} Vídeo {lesson.media[0].status === 'ready' ? 'pronto' : lesson.media[0].status === 'failed' ? 'com erro' : 'processando'}
</span> </span>
)} )}
{lesson.isFree ? ( {lesson.isFree ? (
@@ -624,7 +653,7 @@ const CourseModal: React.FC<{
<div className="p-4 bg-zinc-950 border-t border-white/5 space-y-3"> <div className="p-4 bg-zinc-950 border-t border-white/5 space-y-3">
<h4 className="text-xs font-bold text-gray-400 uppercase tracking-wider flex items-center gap-1.5"> <h4 className="text-xs font-bold text-gray-400 uppercase tracking-wider flex items-center gap-1.5">
<Plus className="w-3.5 h-3.5 text-orange-400" /> <Plus className="w-3.5 h-3.5 text-orange-400" />
Cadastrar Nova Aula Adicionar aula
</h4> </h4>
<div className="flex gap-2"> <div className="flex gap-2">
@@ -632,14 +661,14 @@ const CourseModal: React.FC<{
type="text" type="text"
value={newLessonTitle} value={newLessonTitle}
onChange={(e) => setNewLessonTitle(e.target.value)} onChange={(e) => setNewLessonTitle(e.target.value)}
placeholder="Título da Aula (ex: 01. Configuração do Pixel)" placeholder="Ex.: Aula 01 — Configuração inicial do Pixel"
className="flex-1 bg-white/5 border border-white/10 rounded-xl px-3 py-2 text-xs text-white focus:outline-none focus:border-orange-500/50" className="flex-1 bg-white/5 border border-white/10 rounded-xl px-3 py-2 text-xs text-white focus:outline-none focus:border-orange-500/50"
/> />
<input <input
type="text" type="text"
value={newLessonDuration} value={newLessonDuration}
onChange={(e) => setNewLessonDuration(e.target.value)} onChange={(e) => setNewLessonDuration(e.target.value)}
placeholder="Duração (ex: 25m)" placeholder="Ex.: 25 min"
className="w-28 bg-white/5 border border-white/10 rounded-xl px-3 py-2 text-xs text-white focus:outline-none focus:border-orange-500/50" className="w-28 bg-white/5 border border-white/10 rounded-xl px-3 py-2 text-xs text-white focus:outline-none focus:border-orange-500/50"
/> />
</div> </div>
@@ -647,11 +676,11 @@ const CourseModal: React.FC<{
<div className="rounded-xl border border-orange-500/20 bg-orange-500/[0.04] p-3 space-y-2"> <div className="rounded-xl border border-orange-500/20 bg-orange-500/[0.04] p-3 space-y-2">
<div className="flex items-center justify-between gap-3"> <div className="flex items-center justify-between gap-3">
<div> <div>
<p className="text-xs font-bold text-orange-200">Vídeo Bunny Stream</p> <p className="text-xs font-bold text-orange-200">Vídeo da aula</p>
<p className="text-[11px] text-gray-500">O vídeo será protegido e reproduzido no player seguro da Bunny.</p> <p className="text-[11px] text-gray-500">Envie o arquivo para adicioná-lo à aula. O processamento ocorre automaticamente.</p>
</div> </div>
<label className={`cursor-pointer rounded-lg px-3 py-2 text-xs font-bold transition-colors ${bunnyConfigured && bunnyUploadState !== 'uploading' ? 'bg-orange-500 hover:bg-orange-600 text-white' : 'bg-white/10 text-gray-500 cursor-not-allowed'}`}> <label className={`cursor-pointer rounded-lg px-3 py-2 text-xs font-bold transition-colors ${bunnyConfigured && bunnyUploadState !== 'uploading' ? 'bg-orange-500 hover:bg-orange-600 text-white' : 'bg-white/10 text-gray-500 cursor-not-allowed'}`}>
{bunnyUploadState === 'uploading' ? 'Enviando...' : 'Selecionar vídeo'} {bunnyUploadState === 'uploading' ? 'Enviando...' : 'Enviar vídeo'}
<input <input
type="file" type="file"
accept="video/*" accept="video/*"
@@ -661,32 +690,17 @@ const CourseModal: React.FC<{
/> />
</label> </label>
</div> </div>
{bunnyConfigured === false && <p className="text-[11px] text-amber-300">Bunny Stream não está configurado no servidor. Peça ao administrador para adicionar as variáveis no Portainer.</p>} {bunnyConfigured === false && <p className="text-[11px] text-amber-300">O envio de vídeos está indisponível no momento. Entre em contato com o administrador.</p>}
{bunnyUploadMessage && <p className={`text-[11px] ${bunnyUploadState === 'error' ? 'text-red-300' : bunnyUploadState === 'uploaded' ? 'text-emerald-300' : 'text-gray-400'}`}>{bunnyUploadMessage}</p>} {bunnyUploadMessage && <p className={`text-[11px] ${bunnyUploadState === 'error' ? 'text-red-300' : bunnyUploadState === 'uploaded' ? 'text-emerald-300' : 'text-gray-400'}`}>{bunnyUploadMessage}</p>}
{newLessonMedia && ( {newLessonMedia && (
<div className="flex items-center justify-between rounded-lg bg-black/30 px-3 py-2 text-[11px] text-gray-300"> <div className="flex items-center justify-between rounded-lg bg-black/30 px-3 py-2 text-[11px] text-gray-300">
<span>Vídeo Bunny vinculado ({newLessonMedia.status === 'ready' ? 'pronto' : 'processando'})</span> <span>Vídeo enviado ({newLessonMedia.status === 'ready' ? 'pronto' : 'processando'})</span>
<button type="button" onClick={() => setNewLessonMedia(null)} className="text-gray-400 hover:text-red-300">Remover</button> <button type="button" onClick={() => setNewLessonMedia(null)} className="text-gray-400 hover:text-red-300">Remover</button>
</div> </div>
)} )}
</div> </div>
<div className="flex gap-2 items-center pt-1"> <div className="flex justify-end pt-1">
<input
type="text"
value={newLessonProvider}
onChange={(event) => setNewLessonProvider(event.target.value)}
placeholder="Provider externo"
className="w-40 bg-white/5 border border-white/10 rounded-xl px-3 py-2 text-xs text-white focus:outline-none"
/>
<input
type="url"
value={newLessonVideoUrl}
onChange={(event) => setNewLessonVideoUrl(event.target.value)}
placeholder="Ou cole uma URL externa de vídeo"
className="flex-1 bg-white/5 border border-white/10 rounded-xl px-3 py-2 text-xs text-white focus:outline-none"
/>
<button <button
type="button" type="button"
onClick={handleAddLesson} onClick={handleAddLesson}
@@ -699,14 +713,14 @@ const CourseModal: React.FC<{
</div> </div>
</div> </div>
{/* Submit Button */} <div className="flex flex-col-reverse gap-3 border-t border-white/5 pt-5 sm:flex-row sm:justify-between">
<div className="pt-4 border-t border-white/5"> <button type="button" onClick={() => setEditorStep(1)} className="inline-flex items-center justify-center gap-2 rounded-xl px-4 py-3 text-sm font-semibold text-gray-300 hover:bg-white/5 hover:text-white"><ChevronLeft className="w-4 h-4" /> Voltar para curso</button>
<button <button
onClick={handleSubmit} type="button"
disabled={isSubmitting} onClick={() => goToEditorStep(3)}
className="w-full bg-orange-500 hover:bg-orange-600 text-white font-extrabold text-base py-4 rounded-2xl active:scale-95 transition-all shadow-lg shadow-orange-500/25" className="inline-flex items-center justify-center gap-2 rounded-xl bg-orange-500 px-6 py-3 text-sm font-extrabold text-white shadow-lg shadow-orange-500/20 transition-colors hover:bg-orange-600"
> >
{isSubmitting ? 'Salvando...' : status === 'draft' ? 'Salvar como Rascunho' : (initialData ? 'Salvar e Publicar Curso' : 'Publicar Curso & Materiais')} Continuar para materiais <ChevronRight className="w-4 h-4" />
</button> </button>
</div> </div>
</div> </div>
@@ -1006,7 +1020,7 @@ export const ManageCourses: React.FC = () => {
<textarea <textarea
value={replyText} value={replyText}
onChange={(e) => setReplyText(e.target.value)} onChange={(e) => setReplyText(e.target.value)}
placeholder="Escreva a resposta para o aluno..." placeholder="Escreva uma resposta clara para o aluno..."
className="w-full bg-black/60 border border-white/10 rounded-[8px] p-3 text-body-sm text-white focus:outline-none focus:border-orange-500/50 resize-none" className="w-full bg-black/60 border border-white/10 rounded-[8px] p-3 text-body-sm text-white focus:outline-none focus:border-orange-500/50 resize-none"
rows={3} rows={3}
/> />