feat: students should move to next lesson on click go to lesson (#230)
* completion * completion * PR Fixes * PR Fixes * fix: progress bug on home page * chore: fix lesson completion issue --------- Co-authored-by: Rotimi Best <[email protected]>
This commit is contained in:
co-authored by
Rotimi Best
parent
db38756cbf
commit
1277bcbfc6
@@ -9,6 +9,7 @@
|
|||||||
import { profile } from '$lib/utils/store/user';
|
import { profile } from '$lib/utils/store/user';
|
||||||
import { fetchCourse } from '$lib/utils/services/courses';
|
import { fetchCourse } from '$lib/utils/services/courses';
|
||||||
import { globalStore } from '$lib/utils/store/app';
|
import { globalStore } from '$lib/utils/store/app';
|
||||||
|
import { lessons } from '../Course/components/Lesson/store/lessons';
|
||||||
|
|
||||||
export let courseId = '';
|
export let courseId = '';
|
||||||
export let path = '';
|
export let path = '';
|
||||||
@@ -23,6 +24,7 @@
|
|||||||
|
|
||||||
isFetching = true;
|
isFetching = true;
|
||||||
course.set(defaultCourse);
|
course.set(defaultCourse);
|
||||||
|
lessons.set([]);
|
||||||
|
|
||||||
const { data: _data } = await fetchCourse(courseId);
|
const { data: _data } = await fetchCourse(courseId);
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
<a
|
<a
|
||||||
rel="prefetch"
|
rel="prefetch"
|
||||||
bind:this={target}
|
bind:this={target}
|
||||||
href={isOnLandingPage ? `/course/${slug}` : `/courses/${id}`}
|
href={isOnLandingPage ? `/course/${slug}` : `/courses/${id}${isLMS ? '/lessons?next=true' : ''}`}
|
||||||
class="text-black border border-gray dark:border-neutral-600 rounded w-full max-w-[320px] relative hover:scale-95 transition-all ease-in-out"
|
class="text-black border border-gray dark:border-neutral-600 rounded w-full max-w-[320px] relative hover:scale-95 transition-all ease-in-out"
|
||||||
>
|
>
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
@@ -122,7 +122,7 @@
|
|||||||
|
|
||||||
{#if isLMS}
|
{#if isLMS}
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
label={'Continue Course'}
|
label="Continue Course"
|
||||||
variant={VARIANTS.OUTLINED}
|
variant={VARIANTS.OUTLINED}
|
||||||
className="rounded-none text-primary-600"
|
className="rounded-none text-primary-600"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
const gotoCourse = (id: string | undefined) => {
|
const gotoCourse = (id: string | undefined) => {
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
goto(`/courses/${id}`);
|
goto(`/courses/${id}/lessons?next=true`);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -41,7 +41,9 @@
|
|||||||
</span>
|
</span>
|
||||||
<div class="relative bg-[#EAEAEA] w-full h-1">
|
<div class="relative bg-[#EAEAEA] w-full h-1">
|
||||||
<div
|
<div
|
||||||
style="width:{course.progress_rate}%"
|
style="width: {Math.round(
|
||||||
|
((course?.progress_rate ?? 0) / (course?.total_lessons ?? 0)) * 100
|
||||||
|
) || 0}%"
|
||||||
class={`absolute top-0 left-0 bg-primary-700 h-full`}
|
class={`absolute top-0 left-0 bg-primary-700 h-full`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -175,8 +175,17 @@ export function updateLesson(lesson: any, lessonId: Lesson['id']) {
|
|||||||
.match({ id: lessonId });
|
.match({ id: lessonId });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateLessonCompletion(completion: LessonCompletion) {
|
export function updateLessonCompletion(completion: LessonCompletion, shouldUpdate: boolean) {
|
||||||
return supabase.from('lesson_completion').upsert(completion);
|
if (shouldUpdate) {
|
||||||
|
return supabase
|
||||||
|
.from('lesson_completion')
|
||||||
|
.update({
|
||||||
|
is_complete: completion.is_complete
|
||||||
|
})
|
||||||
|
.eq('id', completion.id);
|
||||||
|
} else {
|
||||||
|
return supabase.from('lesson_completion').insert(completion);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteLesson(lessonId: Lesson['id']) {
|
export function deleteLesson(lessonId: Lesson['id']) {
|
||||||
|
|||||||
@@ -26,13 +26,17 @@
|
|||||||
import { course, group } from '$lib/components/Course/store';
|
import { course, group } from '$lib/components/Course/store';
|
||||||
import DateField from '$lib/components/Form/Date.svelte';
|
import DateField from '$lib/components/Form/Date.svelte';
|
||||||
import type { Lesson } from '$lib/utils/types';
|
import type { Lesson } from '$lib/utils/types';
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
|
|
||||||
|
const query = new URLSearchParams($page.url.search);
|
||||||
|
|
||||||
let lessonEditing: string | undefined;
|
let lessonEditing: string | undefined;
|
||||||
let lessonToDelete: Lesson | undefined;
|
let lessonToDelete: Lesson | undefined;
|
||||||
let isStudent = true;
|
let isStudent: boolean = true;
|
||||||
let openDeleteModal = false;
|
let openDeleteModal: boolean = false;
|
||||||
|
let isFetching: boolean = false;
|
||||||
|
|
||||||
const flipDurationMs = 300;
|
const flipDurationMs = 300;
|
||||||
|
|
||||||
@@ -81,6 +85,30 @@
|
|||||||
return index + 1;
|
return index + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findFirstIncompleteLesson() {
|
||||||
|
return $lessons.find(
|
||||||
|
(lesson) =>
|
||||||
|
lesson.lesson_completion &&
|
||||||
|
lesson.lesson_completion.length === 0 &&
|
||||||
|
lesson.is_unlocked === true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onNextQuery(lessons) {
|
||||||
|
if (!isFetching && lessons.length > 0) {
|
||||||
|
const incompleteLesson = findFirstIncompleteLesson();
|
||||||
|
|
||||||
|
if (incompleteLesson) {
|
||||||
|
goto(`/courses/${data.courseId}/lessons/${incompleteLesson.id}`);
|
||||||
|
} else {
|
||||||
|
goto(`/courses/${data.courseId}/lessons`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: shouldGoToNextLesson = query.get('next') === 'true';
|
||||||
|
$: shouldGoToNextLesson && onNextQuery($lessons);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $handleAddLessonWidget}
|
{#if $handleAddLessonWidget}
|
||||||
@@ -94,7 +122,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- TODO: Refactor usage of two way binding isStudent, rather use $globalStore.isStudent -->
|
<!-- TODO: Refactor usage of two way binding isStudent, rather use $globalStore.isStudent -->
|
||||||
<CourseContainer bind:isStudent bind:courseId={data.courseId}>
|
<CourseContainer bind:isFetching bind:isStudent bind:courseId={data.courseId}>
|
||||||
<PageNav title="Lessons">
|
<PageNav title="Lessons">
|
||||||
<div slot="widget">
|
<div slot="widget">
|
||||||
<RoleBasedSecurity allowedRoles={[1, 2]}>
|
<RoleBasedSecurity allowedRoles={[1, 2]}>
|
||||||
@@ -104,7 +132,18 @@
|
|||||||
</PageNav>
|
</PageNav>
|
||||||
|
|
||||||
<PageBody width="max-w-6xl" padding="p-0">
|
<PageBody width="max-w-6xl" padding="p-0">
|
||||||
{#if $lessons.length}
|
{#if shouldGoToNextLesson}
|
||||||
|
<Box className="w-full lg:w-11/12 lg:px-4 m-auto">
|
||||||
|
<div class="flex flex-col items-center justify-between">
|
||||||
|
<img src="/images/empty-lesson-icon.svg" alt="Lesson" class="mx-auto my-2.5" />
|
||||||
|
<h2 class="my-1.5 text-xl font-normal">No lessons yet</h2>
|
||||||
|
<p class="text-center text-sm text-slate-500">
|
||||||
|
Share your knowledge with the world by creating engaging lessons. Start by clicking on
|
||||||
|
the Add button.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
{:else if $lessons.length}
|
||||||
<section
|
<section
|
||||||
class="m-auto w-full p-3 lg:w-11/12 lg:px-4"
|
class="m-auto w-full p-3 lg:w-11/12 lg:px-4"
|
||||||
use:dndzone={{
|
use:dndzone={{
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
import type { LessonCompletion } from '$lib/utils/types';
|
import type { LessonCompletion } from '$lib/utils/types';
|
||||||
import { profile } from '$lib/utils/store/user';
|
import { profile } from '$lib/utils/store/user';
|
||||||
import { getIsLessonComplete } from '$lib/components/Course/components/Lesson/functions';
|
import { getIsLessonComplete } from '$lib/components/Course/components/Lesson/functions';
|
||||||
|
import { captureMessage } from '@sentry/browser';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
|
|
||||||
@@ -75,29 +76,38 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function markLessonComplete(lessonId: string) {
|
async function markLessonComplete(lessonId: string) {
|
||||||
let found = false;
|
|
||||||
isMarkingComplete = true;
|
isMarkingComplete = true;
|
||||||
|
|
||||||
let completion: LessonCompletion = {
|
let newCompletion: LessonCompletion = {
|
||||||
is_complete: true,
|
is_complete: true,
|
||||||
profile_id: $profile.id || '',
|
profile_id: $profile.id || '',
|
||||||
lesson_id: lessonId,
|
lesson_id: lessonId,
|
||||||
created_at: new Date().toDateString(),
|
created_at: new Date().toDateString(),
|
||||||
updated_at: new Date().toDateString()
|
updated_at: new Date().toDateString()
|
||||||
};
|
};
|
||||||
const completions: LessonCompletion[] = $lesson.lesson_completion.map((_completion) => {
|
let updatedCompletion: LessonCompletion;
|
||||||
if (_completion.profile_id === $profile.id) {
|
|
||||||
found = true;
|
const completions: LessonCompletion[] = $lesson.lesson_completion.map((completion) => {
|
||||||
|
if (completion.profile_id === $profile.id) {
|
||||||
|
console.log('shouldUpdate');
|
||||||
|
|
||||||
completion = {
|
completion = {
|
||||||
...completion,
|
...completion,
|
||||||
is_complete: _completion.is_complete
|
is_complete: !completion.is_complete
|
||||||
};
|
};
|
||||||
|
|
||||||
|
updatedCompletion = completion;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _completion;
|
return completion;
|
||||||
});
|
});
|
||||||
if (!found) {
|
|
||||||
completions.push(completion);
|
// @ts-ignore
|
||||||
|
if (updatedCompletion) {
|
||||||
|
await updateLessonCompletion(updatedCompletion, true);
|
||||||
|
} else {
|
||||||
|
completions.push(newCompletion);
|
||||||
|
await updateLessonCompletion(newCompletion, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
$lesson.lesson_completion = completions;
|
$lesson.lesson_completion = completions;
|
||||||
@@ -108,8 +118,6 @@
|
|||||||
|
|
||||||
return l;
|
return l;
|
||||||
});
|
});
|
||||||
|
|
||||||
await updateLessonCompletion(completion);
|
|
||||||
snackbar.success('Marked as complete');
|
snackbar.success('Marked as complete');
|
||||||
isMarkingComplete = false;
|
isMarkingComplete = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -326,12 +326,6 @@ INSERT INTO "public"."lesson_comment" ("id", "created_at", "updated_at", "lesson
|
|||||||
-- Data for Name: lesson_completion; Type: TABLE DATA; Schema: public; Owner: postgres
|
-- Data for Name: lesson_completion; Type: TABLE DATA; Schema: public; Owner: postgres
|
||||||
--
|
--
|
||||||
|
|
||||||
INSERT INTO "public"."lesson_completion" ("id", "created_at", "lesson_id", "profile_id", "is_complete", "updated_at") VALUES
|
|
||||||
(1, '2023-12-18 00:00:00+00', '5e5c8221-4c11-4c40-8664-11743bb79579', '0c256e75-aa40-4f62-8d30-0217ca1c60d9', true, '2023-12-18 00:00:00+00'),
|
|
||||||
(2, '2023-12-18 00:00:00+00', '829da386-8ccd-4c81-b2fb-b9891102c83c', '0c256e75-aa40-4f62-8d30-0217ca1c60d9', true, '2023-12-18 00:00:00+00'),
|
|
||||||
(3, '2023-12-19 00:00:00+00', '829da386-8ccd-4c81-b2fb-b9891102c83c', '0c256e75-aa40-4f62-8d30-0217ca1c60d9', true, '2023-12-19 00:00:00+00'),
|
|
||||||
(4, '2023-12-19 00:00:00+00', '829da386-8ccd-4c81-b2fb-b9891102c83c', '0c256e75-aa40-4f62-8d30-0217ca1c60d9', true, '2023-12-19 00:00:00+00');
|
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Data for Name: lesson_progress; Type: TABLE DATA; Schema: public; Owner: postgres
|
-- Data for Name: lesson_progress; Type: TABLE DATA; Schema: public; Owner: postgres
|
||||||
|
|||||||
Reference in New Issue
Block a user