Files
ACVE/lib/data/teacherCourses.ts
2026-02-17 00:07:00 +00:00

135 lines
3.5 KiB
TypeScript
Executable File

import type { Course, CourseLevel, Lesson } from "@/types/course";
const STORAGE_KEY = "acve.teacher-courses.v1";
const TEACHER_UPDATED_EVENT = "acve:teacher-courses-updated";
export type TeacherCourseInput = {
title: string;
level: CourseLevel;
summary: string;
instructor: string;
weeks: number;
};
export type TeacherCoursePatch = Partial<Omit<Course, "slug" | "lessons">> & {
summary?: string;
title?: string;
level?: CourseLevel;
instructor?: string;
weeks?: number;
};
const isBrowser = () => typeof window !== "undefined";
const readStore = (): Course[] => {
if (!isBrowser()) return [];
const raw = window.localStorage.getItem(STORAGE_KEY);
if (!raw) return [];
try {
return JSON.parse(raw) as Course[];
} catch {
return [];
}
};
const writeStore = (courses: Course[]) => {
if (!isBrowser()) return;
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(courses));
window.dispatchEvent(new Event(TEACHER_UPDATED_EVENT));
};
const slugify = (value: string) =>
value
.toLowerCase()
.trim()
.replace(/[^a-z0-9\s-]/g, "")
.replace(/\s+/g, "-")
.replace(/-+/g, "-");
const lessonId = (index: number) => `lesson-${index}`;
const normalizeCourse = (course: Course): Course => ({
...course,
lessonsCount: course.lessons.length,
});
export const teacherCoursesUpdatedEventName = TEACHER_UPDATED_EVENT;
export const getTeacherCourses = (): Course[] => readStore().map(normalizeCourse);
export const getTeacherCourseBySlug = (slug: string): Course | undefined =>
getTeacherCourses().find((course) => course.slug === slug);
export const ensureUniqueTeacherCourseSlug = (title: string, reservedSlugs: string[]) => {
const baseSlug = slugify(title) || "untitled-course";
let slug = baseSlug;
let index = 2;
const pool = new Set(reservedSlugs.map((item) => item.toLowerCase()));
while (pool.has(slug.toLowerCase())) {
slug = `${baseSlug}-${index}`;
index += 1;
}
return slug;
};
export const createTeacherCourse = (input: TeacherCourseInput, reservedSlugs: string[]) => {
const courses = getTeacherCourses();
const slug = ensureUniqueTeacherCourseSlug(input.title, reservedSlugs);
const starterLesson: Lesson = {
id: lessonId(1),
title: "Course introduction",
type: "video",
minutes: 8,
isPreview: true,
videoUrl: "https://example.com/video-placeholder",
};
const next: Course = {
slug,
title: input.title,
level: input.level,
status: "Draft",
summary: input.summary,
rating: 5,
weeks: Math.max(1, input.weeks),
lessonsCount: 1,
students: 0,
instructor: input.instructor,
lessons: [starterLesson],
};
writeStore([...courses, next]);
return next;
};
export const updateTeacherCourse = (slug: string, patch: TeacherCoursePatch) => {
const courses = getTeacherCourses();
const nextCourses = courses.map((course) =>
course.slug === slug ? normalizeCourse({ ...course, ...patch }) : course,
);
writeStore(nextCourses);
return nextCourses.find((course) => course.slug === slug);
};
export const addLessonToTeacherCourse = (slug: string, lesson: Omit<Lesson, "id">) => {
const courses = getTeacherCourses();
const nextCourses = courses.map((course) => {
if (course.slug !== slug) return course;
const nextLesson: Lesson = {
...lesson,
id: lessonId(course.lessons.length + 1),
};
return normalizeCourse({
...course,
lessons: [...course.lessons, nextLesson],
});
});
writeStore(nextCourses);
return nextCourses.find((course) => course.slug === slug);
};