feat: streamline course editor workflow
This commit is contained in:
@@ -24,7 +24,7 @@ import {
|
||||
Unlock,
|
||||
Globe,
|
||||
Download
|
||||
,Copy, ChevronUp, ChevronDown
|
||||
,Copy, ChevronUp, ChevronDown, ChevronLeft, ChevronRight
|
||||
} from 'lucide-react';
|
||||
import { Course, Comment, Attachment, Lesson, AttachmentType, LessonMedia } from '../types';
|
||||
import { useToast } from '../context/ToastContext';
|
||||
@@ -49,6 +49,7 @@ const CourseModal: React.FC<{
|
||||
const [coverConfigured, setCoverConfigured] = useState<boolean | null>(null);
|
||||
const [coverUploadMessage, setCoverUploadMessage] = useState('');
|
||||
const [isCoverUploading, setIsCoverUploading] = useState(false);
|
||||
const [editorStep, setEditorStep] = useState<1 | 2 | 3>(1);
|
||||
|
||||
// Lesson Management
|
||||
const [lessons, setLessons] = useState<Lesson[]>([]);
|
||||
@@ -80,6 +81,7 @@ const CourseModal: React.FC<{
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setEditorStep(1);
|
||||
if (initialData) {
|
||||
setTitle(initialData.title);
|
||||
setDescription(initialData.description);
|
||||
@@ -320,8 +322,19 @@ const CourseModal: React.FC<{
|
||||
setAttachments(attachments.filter(a => a.id !== id));
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
const goToEditorStep = (nextStep: 1 | 2 | 3) => {
|
||||
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) {
|
||||
alert("Adicione pelo menos uma aula à trilha.");
|
||||
return;
|
||||
@@ -360,14 +373,30 @@ const CourseModal: React.FC<{
|
||||
return (
|
||||
<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="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">
|
||||
|
||||
{/* Left Column: Course Details, Tips & Downloads */}
|
||||
<div className="w-full lg:w-5/12 space-y-6">
|
||||
<div className="flex justify-between items-center lg:hidden mb-2">
|
||||
<h2 className="text-xl font-bold text-white">{initialData ? 'Editar Curso' : 'Criar Curso'}</h2>
|
||||
<button onClick={onClose}><X className="text-white" /></button>
|
||||
<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">
|
||||
<div>
|
||||
<p className="text-xs font-bold uppercase tracking-wider text-orange-400">{initialData ? 'Editar curso' : 'Novo curso'}</p>
|
||||
<h2 className="mt-1 text-2xl font-extrabold text-white">{editorStep === 1 ? 'Informações do curso' : editorStep === 2 ? 'Estruture as aulas' : 'Materiais complementares'}</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>
|
||||
</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 */}
|
||||
<div>
|
||||
@@ -454,8 +483,13 @@ const CourseModal: React.FC<{
|
||||
</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 */}
|
||||
<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">
|
||||
<h3 className="text-xs font-bold text-orange-400 uppercase tracking-wider flex items-center gap-1.5">
|
||||
<Download className="w-4 h-4" />
|
||||
@@ -530,21 +564,16 @@ const CourseModal: React.FC<{
|
||||
</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>
|
||||
|
||||
{/* 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 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="p-4 border-b border-white/5 bg-zinc-900/50 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -699,14 +728,14 @@ const CourseModal: React.FC<{
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
<div className="pt-4 border-t border-white/5">
|
||||
<div className="flex flex-col-reverse gap-3 border-t border-white/5 pt-5 sm:flex-row sm:justify-between">
|
||||
<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
|
||||
onClick={handleSubmit}
|
||||
disabled={isSubmitting}
|
||||
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"
|
||||
type="button"
|
||||
onClick={() => goToEditorStep(3)}
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user