feat: add sequential progression and video watch lesson completion

Gate student content by completion order, track per-video watch progress
with seek enforcement, and auto-complete lessons at the configured
threshold. Show ring progress in the lesson header, keep watch progress
on rewatch, and sync progression locks client-side so the next item
unlocks after completion.
This commit is contained in:
Rotimi Best
2026-06-11 14:48:34 +01:00
parent c8154409f5
commit 52d9bf18c3
94 changed files with 3499 additions and 564 deletions
+21 -2
View File
@@ -32,6 +32,7 @@ import { exerciseBelongsToCourse } from '@cio/db/queries/course/certification-ex
import { updateExercisesSectionId } from '@cio/db/queries/exercise/exercise';
import { getProfileById } from '@cio/db/queries/auth';
import { insertOrganizationMembersOnConflictDoNothing } from '@cio/db/queries/organization';
import { annotateCourseContentWithProgression } from './progression';
import { buildCourseContent, calcPercentageWithRounding, formatLastSeen, type CourseContent } from './utils';
import { guardCourseTypeTransition } from './public-course-guard';
@@ -136,13 +137,31 @@ export async function getCourse(courseId?: string, slug?: string, profileId?: st
}
const isContentGroupingEnabled = course.metadata?.isContentGroupingEnabled ?? DEFAULT_CONTENT_GROUPING;
const content = buildCourseContent(course.contentItems, isContentGroupingEnabled);
const progressionMode = course.metadata?.progressionMode ?? 'free';
const roleId = profileId
? (course.group?.members?.find((member) => member.profileId === profileId)?.roleId ?? null)
: null;
const content = profileId
? await annotateCourseContentWithProgression({
courseId: course.id,
profileId,
roleId,
progressionMode,
contentRows: course.contentItems,
isContentGroupingEnabled
})
: buildCourseContent(course.contentItems, isContentGroupingEnabled);
const { contentItems, org: courseOrg, ...rest } = course;
const base = {
...rest,
content
content,
metadata: {
...course.metadata,
progressionMode
}
};
if (courseOrg) {