"use client"; import { useEffect, useState } from "react"; import { cmsApi, Course, Module, Lesson, Organization } from "@/lib/api"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { useAuth } from "@/context/AuthContext"; import { Plus, Pencil, ChevronUp, ChevronDown, PlayCircle, FileText, Calendar, Save, X, GripVertical, Trash2, ArrowLeft, Send, } from "lucide-react"; import CourseEditorLayout from "@/components/CourseEditorLayout"; import OrganizationSelector from "@/components/OrganizationSelector"; interface FullModule extends Module { lessons: Lesson[]; } export default function CourseEditor({ params }: { params: { id: string } }) { const router = useRouter(); const [course, setCourse] = useState(null); const [modules, setModules] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [editingId, setEditingId] = useState(null); const [editValue, setEditValue] = useState(""); const [organizations, setOrganizations] = useState([]); const [isOrgModalOpen, setIsOrgModalOpen] = useState(false); const [saving, setSaving] = useState(false); // Added saving state const { user } = useAuth(); const startEditing = (id: string, currentTitle: string) => { setEditingId(id); setEditValue(currentTitle); }; useEffect(() => { const loadData = async () => { try { setLoading(true); const data = await cmsApi.getCourseWithFullOutline(params.id); setCourse(data); setModules(data.modules as FullModule[]); } catch (err) { console.error("Failed to load course data:", err); setError("Failed to load course details. Is the backend running?"); } finally { setLoading(false); } }; loadData(); }, [params.id]); useEffect(() => { const loadOrgs = async () => { if (user?.role === 'admin' && user?.organization_id === '00000000-0000-0000-0000-000000000001') { try { const orgs = await cmsApi.getOrganizations(); setOrganizations(orgs); } catch (err) { console.error("Failed to load organizations", err); } } }; loadOrgs(); }, [user]); const handleAddModule = async () => { const title = ""; try { const newMod = await cmsApi.createModule(params.id, title, modules.length + 1); const fullMod = { ...newMod, lessons: [] }; setModules([...modules, fullMod]); setEditingId(newMod.id); setEditValue(title); } catch { alert("Failed to create module"); } }; const handleAddLesson = async (moduleId: string) => { const mod = modules.find((m: FullModule) => m.id === moduleId); if (!mod) return; const title = "New Lesson"; try { const newLesson = await cmsApi.createLesson(moduleId, title, "video", mod.lessons.length + 1); setModules(modules.map((m: FullModule) => m.id === moduleId ? { ...m, lessons: [...m.lessons, newLesson] } : m )); setEditingId(newLesson.id); setEditValue(title); } catch { alert("Failed to create lesson"); } }; const handleSaveTitle = async (id: string, type: 'module' | 'lesson') => { if (!editValue) { setEditingId(null); return; } try { if (type === 'module') { await cmsApi.updateModule(id, { title: editValue }); setModules(modules.map((m: FullModule) => m.id === id ? { ...m, title: editValue } : m)); } else { await cmsApi.updateLesson(id, { title: editValue }); setModules(modules.map((mod: FullModule) => ({ ...mod, lessons: mod.lessons.map((l: Lesson) => l.id === id ? { ...l, title: editValue } : l) }))); } setEditingId(null); } catch { alert("Failed to update title"); } }; const handleDeleteModule = async (id: string) => { if (!confirm("Are you sure you want to delete this module and all its lessons?")) return; try { await cmsApi.deleteModule(id); setModules(modules.filter((m: FullModule) => m.id !== id)); } catch { alert("Failed to delete module"); } }; const handleDeleteLesson = async (moduleId: string, lessonId: string) => { if (!confirm("Are you sure you want to delete this lesson?")) return; try { await cmsApi.deleteLesson(lessonId); setModules(modules.map((m: FullModule) => m.id === moduleId ? { ...m, lessons: m.lessons.filter((l: Lesson) => l.id !== lessonId) } : m )); } catch { alert("Failed to delete lesson"); } }; const handleReorderModule = async (index: number, direction: 'up' | 'down') => { const newModules = [...modules]; const targetIndex = direction === 'up' ? index - 1 : index + 1; if (targetIndex < 0 || targetIndex >= newModules.length) return; [newModules[index], newModules[targetIndex]] = [newModules[targetIndex], newModules[index]]; const items = newModules.map((m: FullModule, i: number) => ({ id: m.id, position: i + 1 })); setModules(newModules.map((m: FullModule, i: number) => ({ ...m, position: i + 1 }))); try { await cmsApi.reorderModules({ items }); } catch { alert("Failed to save module order"); } }; const handleReorderLesson = async (moduleId: string, lessonIndex: number, direction: 'up' | 'down') => { const mod = modules.find((m: FullModule) => m.id === moduleId); if (!mod) return; const newLessons = [...mod.lessons]; const targetIndex = direction === 'up' ? lessonIndex - 1 : lessonIndex + 1; if (targetIndex < 0 || targetIndex >= newLessons.length) return; [newLessons[lessonIndex], newLessons[targetIndex]] = [newLessons[targetIndex], newLessons[lessonIndex]]; const items = newLessons.map((l: Lesson, i: number) => ({ id: l.id, position: i + 1 })); setModules(modules.map((m: FullModule) => m.id === moduleId ? { ...m, lessons: newLessons.map((l: Lesson, i: number) => ({ ...l, position: i + 1 })) } : m)); try { await cmsApi.reorderLessons({ items }); } catch { alert("Failed to save lesson order"); } }; const handlePublish = async () => { if (!course) return; const isSuperAdmin = user?.role === 'admin' && user?.organization_id === '00000000-0000-0000-0000-000000000001'; if (isSuperAdmin && organizations.length > 0) { setIsOrgModalOpen(true); } else { publishCourse(); } }; const publishCourse = async (targetOrgId?: string) => { try { setSaving(true); await cmsApi.publishCourse(params.id as string, targetOrgId); alert("Course published successfully!"); } catch (err) { console.error("Failed to publish course", err); alert("Failed to publish course."); } finally { setSaving(false); setIsOrgModalOpen(false); // Close modal after publishing attempt } }; if (loading) return
Loading editor...
; if (error) return
{error}
; return ( <> } >
{modules.map((module: FullModule, mIndex: number) => (
{editingId === module.id ? (
setEditValue(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleSaveTitle(module.id, 'module')} className="bg-white dark:bg-black/40 border border-blue-500/50 rounded-2xl px-5 py-3 flex-1 text-slate-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500/30 text-lg font-black uppercase tracking-tight shadow-inner" />
) : (
Module {mIndex + 1} { setEditingId(module.id); setEditValue(module.title); }} className="font-black text-2xl text-slate-900 dark:text-white cursor-pointer hover:text-blue-600 dark:hover:text-blue-400 transition-colors uppercase tracking-tight" > {module.title || `Untitled Section`}
)}
{module.lessons.map((lesson: Lesson, lIndex: number) => (
{editingId === lesson.id ? (
setEditValue(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleSaveTitle(lesson.id, 'lesson')} className="bg-transparent border-none flex-1 text-slate-900 dark:text-white focus:outline-none font-black uppercase tracking-tight" />
) : (
{lesson.content_type === 'video' ? : }
{ e.preventDefault(); e.stopPropagation(); startEditing(lesson.id, lesson.title); }} className="font-black text-slate-800 dark:text-white hover:text-blue-600 dark:hover:text-blue-400 transition-colors uppercase tracking-tight text-sm" > {lesson.title}
{lesson.content_type} {lesson.due_date && (
{new Date(lesson.due_date).toLocaleDateString()}
)}
)}
))}
))}
{/* Organization Selector Modal */} setIsOrgModalOpen(false)} organizations={organizations} title="Publish to Organization" actionLabel="Publish Course" onConfirm={(orgId) => publishCourse(orgId)} /> ); }