Initial commit: Clean workspace without heavy binaries
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { cmsApi, Course, Module, Lesson } from "@/lib/api";
|
||||
import Link from "next/link";
|
||||
|
||||
interface FullModule extends Module {
|
||||
lessons: Lesson[];
|
||||
}
|
||||
|
||||
export default function CourseEditor({ params }: { params: { id: string } }) {
|
||||
const [course, setCourse] = useState<Course | null>(null);
|
||||
const [modules, setModules] = useState<FullModule[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
// 1. Fetch course details
|
||||
const courseData = await fetch(`http://localhost:3001/courses/${params.id}`).then(res => res.json());
|
||||
setCourse(courseData);
|
||||
|
||||
// 2. Fetch modules
|
||||
const modulesData: Module[] = await fetch(`http://localhost:3001/modules?course_id=${params.id}`).then(res => res.json());
|
||||
|
||||
// 3. Fetch lessons for each module
|
||||
const fullModules = await Promise.all(modulesData.map(async (mod) => {
|
||||
const lessonsData: Lesson[] = await fetch(`http://localhost:3001/lessons?module_id=${mod.id}`).then(res => res.json());
|
||||
return { ...mod, lessons: lessonsData };
|
||||
}));
|
||||
|
||||
setModules(fullModules);
|
||||
} 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]);
|
||||
|
||||
const handleAddModule = async () => {
|
||||
const title = prompt("Module Title:");
|
||||
if (!title) return;
|
||||
|
||||
try {
|
||||
const newMod = await cmsApi.createModule(params.id, title, modules.length + 1);
|
||||
setModules([...modules, { ...newMod, lessons: [] }]);
|
||||
} catch {
|
||||
alert("Failed to create module");
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddLesson = async (moduleId: string) => {
|
||||
const title = prompt("Lesson Title:");
|
||||
if (!title) return;
|
||||
|
||||
try {
|
||||
// Default to 'video' for now as a content type
|
||||
const newLesson = await cmsApi.createLesson(moduleId, title, "video", 1);
|
||||
setModules(modules.map(mod =>
|
||||
mod.id === moduleId
|
||||
? { ...mod, lessons: [...mod.lessons, newLesson] }
|
||||
: mod
|
||||
));
|
||||
} catch {
|
||||
alert("Failed to create lesson");
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) return <div className="py-20 text-center">Loading editor...</div>;
|
||||
if (error) return <div className="py-20 text-center text-red-400">{error}</div>;
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div className="flex items-center gap-4 text-sm text-gray-400">
|
||||
<Link href="/" className="hover:text-white cursor-pointer underline">Courses</Link>
|
||||
<span>/</span>
|
||||
<span className="text-white">{course?.title}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-end">
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold">{course?.title}</h2>
|
||||
<p className="text-gray-400">Editor - Outline (ID: {params.id})</p>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<button className="px-4 py-2 glass hover:bg-white/10 transition-colors text-sm font-medium">Preview</button>
|
||||
<button className="btn-premium">Publish</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="glass p-1">
|
||||
<div className="flex border-b border-white/10">
|
||||
<button className="px-6 py-3 text-sm font-medium border-b-2 border-blue-500 bg-white/5">Outline</button>
|
||||
<button className="px-6 py-3 text-sm font-medium text-gray-500 hover:text-white transition-colors">Settings</button>
|
||||
<button className="px-6 py-3 text-sm font-medium text-gray-500 hover:text-white transition-colors">Files</button>
|
||||
</div>
|
||||
|
||||
<div className="p-6 space-y-4">
|
||||
{modules.map((module) => (
|
||||
<div key={module.id} className="glass overflow-hidden">
|
||||
<div className="bg-white/5 px-4 py-3 flex justify-between items-center border-b border-white/5">
|
||||
<span className="font-medium text-blue-400">Module {module.position}: {module.title}</span>
|
||||
<button className="text-xs text-gray-400 hover:text-white">Options</button>
|
||||
</div>
|
||||
<div className="p-4 space-y-2">
|
||||
{module.lessons.map(lesson => (
|
||||
<Link href={`/courses/${params.id}/lessons/${lesson.id}`} key={lesson.id}>
|
||||
<div className="glass border-white/5 p-3 flex items-center justify-between text-sm hover:bg-white/10 hover:border-blue-500/30 transition-all cursor-pointer group/lesson">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-blue-400 text-lg group-hover/lesson:scale-110 transition-transform">
|
||||
{lesson.content_type === 'video' ? '🎬' : '📄'}
|
||||
</span>
|
||||
<span>{lesson.title}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
{lesson.transcription && <span className="text-[10px] bg-blue-500/20 text-blue-400 px-1.5 py-0.5 rounded">CC</span>}
|
||||
<span className="text-xs text-gray-500 capitalize">{lesson.content_type}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
|
||||
<button
|
||||
onClick={() => handleAddLesson(module.id)}
|
||||
className="w-full py-2 border border-dashed border-white/10 rounded-lg text-xs text-gray-500 hover:text-white hover:border-white/20 transition-all mt-2"
|
||||
>
|
||||
+ New Lesson
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<button
|
||||
onClick={handleAddModule}
|
||||
className="w-full py-4 border-2 border-dashed border-white/10 rounded-xl font-medium text-gray-500 hover:text-white hover:border-white/20 transition-all"
|
||||
>
|
||||
+ Add Module
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user