Files
ACVE/lib/progress/localProgress.ts
2026-02-17 00:07:00 +00:00

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;