feat: streamline course editor workflow

This commit is contained in:
Cauê Faleiros
2026-09-04 13:53:35 -03:00
parent e15a44e733
commit b1bbf35751

View File

@@ -24,7 +24,7 @@ import {
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 +49,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[]>([]);
@@ -80,6 +81,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);
@@ -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>
@@ -454,8 +483,13 @@ const CourseModal: React.FC<{
</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" />
@@ -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">
@@ -699,14 +728,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>