87 lines
2.5 KiB
TypeScript
Executable File
87 lines
2.5 KiB
TypeScript
Executable File
export type CourseProgress = {
|
|
lastLessonId: string | null;
|
|
completedLessonIds: string[];
|
|
};
|
|
|
|
const STORAGE_KEY = "acve.local-progress.v1";
|
|
const PROGRESS_UPDATED_EVENT = "acve:progress-updated";
|
|
|
|
type ProgressStore = Record<string, Record<string, CourseProgress>>;
|
|
|
|
const defaultProgress: CourseProgress = {
|
|
lastLessonId: null,
|
|
completedLessonIds: [],
|
|
};
|
|
|
|
const isBrowser = () => typeof window !== "undefined";
|
|
|
|
const readStore = (): ProgressStore => {
|
|
if (!isBrowser()) return {};
|
|
|
|
const raw = window.localStorage.getItem(STORAGE_KEY);
|
|
if (!raw) return {};
|
|
|
|
try {
|
|
return JSON.parse(raw) as ProgressStore;
|
|
} catch {
|
|
return {};
|
|
}
|
|
};
|
|
|
|
const writeStore = (store: ProgressStore) => {
|
|
if (!isBrowser()) return;
|
|
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(store));
|
|
window.dispatchEvent(new Event(PROGRESS_UPDATED_EVENT));
|
|
};
|
|
|
|
const getOrCreateProgress = (store: ProgressStore, userId: string, courseSlug: string) => {
|
|
if (!store[userId]) {
|
|
store[userId] = {};
|
|
}
|
|
if (!store[userId][courseSlug]) {
|
|
store[userId][courseSlug] = { ...defaultProgress };
|
|
}
|
|
return store[userId][courseSlug];
|
|
};
|
|
|
|
export const getCourseProgress = (userId: string, courseSlug: string): CourseProgress => {
|
|
const store = readStore();
|
|
return store[userId]?.[courseSlug] ?? { ...defaultProgress };
|
|
};
|
|
|
|
export const getCourseProgressPercent = (
|
|
userId: string,
|
|
courseSlug: string,
|
|
totalLessons: number,
|
|
): number => {
|
|
if (totalLessons <= 0) return 0;
|
|
const progress = getCourseProgress(userId, courseSlug);
|
|
return Math.round((progress.completedLessonIds.length / totalLessons) * 100);
|
|
};
|
|
|
|
export const setLastLesson = (userId: string, courseSlug: string, lessonId: string) => {
|
|
const store = readStore();
|
|
const progress = getOrCreateProgress(store, userId, courseSlug);
|
|
progress.lastLessonId = lessonId;
|
|
writeStore(store);
|
|
};
|
|
|
|
export const markLessonComplete = (userId: string, courseSlug: string, lessonId: string) => {
|
|
const store = readStore();
|
|
const progress = getOrCreateProgress(store, userId, courseSlug);
|
|
if (!progress.completedLessonIds.includes(lessonId)) {
|
|
progress.completedLessonIds = [...progress.completedLessonIds, lessonId];
|
|
}
|
|
progress.lastLessonId = lessonId;
|
|
writeStore(store);
|
|
};
|
|
|
|
export const clearCourseProgress = (userId: string, courseSlug: string) => {
|
|
const store = readStore();
|
|
if (!store[userId]) return;
|
|
delete store[userId][courseSlug];
|
|
writeStore(store);
|
|
};
|
|
|
|
export const progressUpdatedEventName = PROGRESS_UPDATED_EVENT;
|