feat: Update course landing page with sections (#456)

* feat: add sections ui to the landing page

* quick fixes

* quick fixes

* feat: add translations

* run translation script

* fix: sections not showing on the landing page

---------

Co-authored-by: Rotimi Best <[email protected]>
This commit is contained in:
Adeyemi Adetunji
2024-09-07 10:18:21 +01:00
committed by GitHub
co-authored by Rotimi Best
parent f7640b8a67
commit 982acb0711
12 changed files with 193 additions and 58 deletions
@@ -0,0 +1,49 @@
<script lang="ts">
import { CheckmarkOutline, ChevronDown, ChevronUp } from 'carbon-icons-svelte';
import { t } from '$lib/utils/functions/translations';
import type { Lesson } from '$lib/utils/types';
import IconButton from '$lib/components/IconButton/index.svelte';
export let index: number;
export let title: string;
export let lessonCount: number;
export let exerciseCount: number;
export let lessons: Lesson;
export let showLessons: boolean[] = Array(lessons.length).fill(true);
function toggleShowLessons(index) {
showLessons[index] = !showLessons[index];
}
</script>
<div class="w-full rounded-md my-3">
<!-- lesson header -->
<div class="py-1 pl-3 pr-4 bg-[#F7F7F7] text-sm font-medium flex items-center justify-between">
<span class="flex items-center gap-2">
<IconButton onClick={() => toggleShowLessons(index)}>
{#if showLessons[index]}
<ChevronDown />
{:else}
<ChevronUp />
{/if}
</IconButton>
{title}</span
>
<span class="text-xs font-normal"
>{lessonCount}
{$t('course.navItem.landing_page.lessons')}, {exerciseCount}
{$t('course.navItem.landing_page.exercises')}</span
>
</div>
<!-- lessons -->
{#if showLessons[index]}
{#each lessons as lesson}
<div class="py-3 pl-6 text-[13px] text-[#656565] flex items-center gap-5 lesson-section">
<CheckmarkOutline size={16} class="scale-[0.8]" />
{lesson.title}
</div>
{/each}
{/if}
</div>
@@ -1,32 +1,34 @@
<script lang="ts">
import get from 'lodash/get';
import { onMount, onDestroy } from 'svelte';
import PoweredBy from '$lib/components/Upgrade/PoweredBy.svelte';
import pluralize from 'pluralize';
import { page } from '$app/stores';
import { onMount, onDestroy } from 'svelte';
import Notebook from 'carbon-icons-svelte/lib/Notebook.svelte'; //note
import PresentationFile from 'carbon-icons-svelte/lib/PresentationFile.svelte'; // exercise
import Video from 'carbon-icons-svelte/lib/Video.svelte'; //video
import PageNumber from 'carbon-icons-svelte/lib/PageNumber.svelte';
import PlayFilled from 'carbon-icons-svelte/lib/PlayFilled.svelte';
import PricingSection from './components/PricingSection.svelte';
import { observeIntersection } from './components/IntersectionObserver';
import { getLectureNo } from '../Course/function';
import { NAV_ITEMS } from './constants';
import Modal from '../Modal/index.svelte';
import { handleOpenWidget, reviewsModalStore } from './store';
import Chip from '../Chip/index.svelte';
import Avatar from '$lib/components/Avatar/index.svelte';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { getEmbedId } from '$lib/utils/functions/formatYoutubeVideo';
import type { Course, Lesson, Review } from '$lib/utils/types';
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import HtmlRender from '$lib/components/HTMLRender/HTMLRender.svelte';
import { calDateDiff } from '$lib/utils/functions/date';
import { getLectureNo } from '../Course/function';
import { currentOrg } from '$lib/utils/store/org';
import UploadWidget from '$lib/components/UploadWidget/index.svelte';
import { course } from '$lib/components/Course/store';
import { t } from '$lib/utils/functions/translations';
import { calDateDiff } from '$lib/utils/functions/date';
import { handleOpenWidget, reviewsModalStore } from './store';
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import { COURSE_VERSION, type Course, type Lesson, type Review } from '$lib/utils/types';
import Chip from '../Chip/index.svelte';
import Modal from '../Modal/index.svelte';
import Avatar from '$lib/components/Avatar/index.svelte';
import PricingSection from './components/PricingSection.svelte';
import PoweredBy from '$lib/components/Upgrade/PoweredBy.svelte';
import UploadWidget from '$lib/components/UploadWidget/index.svelte';
import { getEmbedId } from '$lib/utils/functions/formatYoutubeVideo';
import HtmlRender from '$lib/components/HTMLRender/HTMLRender.svelte';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { observeIntersection } from './components/IntersectionObserver';
import SectionsDisplay from './components/SectionsDisplay.svelte';
export let editMode = false;
export let courseData: Course;
@@ -49,11 +51,20 @@
// initialize the expandDescription array with 'false' values for each review.
let expandDescription = Array(reviews.length).fill(false);
// a variable that stores the sections and the lessons in each sections
const lesson_sections = createLessonSections(courseData);
let activeNav = NAV_ITEMS[0].key;
let instructor = {};
let video: string | undefined;
// get total lessons
const totalLessons = Array.isArray(lesson_sections)
? lesson_sections.reduce((total, section) => {
return total + section.lessons.length;
}, 0)
: 0;
function playVideo() {
console.log('open modal with video');
}
@@ -75,6 +86,18 @@
expandDescription[id] = !expandDescription[id];
}
// this function creates a new lesson_section variable using the lessons section_id and groups them into appropriate sections,
// this way it solves the issue of the landing page being a standalone site
function createLessonSections(data) {
const lessonSections = data.lesson_section.map((section) => {
return {
...section,
lessons: data.lessons.filter((lesson) => lesson.section_id === section.id)
};
});
return lessonSections;
}
onMount(() => {
window.onhashchange = locationHashChanged;
const targetNode = document.querySelector('.target-component');
@@ -256,30 +279,30 @@
</section>
<!-- Sections - Lessons -->
<section id="lessons" class="border-b border-gray-300 mt-8 pb-10">
<div class="flex items-center justify-between w-full mb-3">
<h3 class="text-2xl font-bold mt-0 mb-3">
{$t('course.navItem.landing_page.content')}
</h3>
<p class="dark:text-white text-sm font-light">
<!-- {lessons.length} lessons -->
{pluralize('lesson', lessons.length, true)}
</p>
</div>
{#if $course.version === COURSE_VERSION.V1}
<section id="lessons" class="border-b border-gray-300 mt-8 pb-10">
<div class="flex items-center justify-between w-full mb-3">
<h3 class="text-2xl font-bold mt-0 mb-3">
{$t('course.navItem.landing_page.content')}
</h3>
<p class="dark:text-white text-sm font-light">
<!-- {lessons.length} lessons -->
{pluralize('lesson', lessons.length, true)}
</p>
</div>
<div class="flex flex-wrap">
{#each lessons as lesson, index}
<div class="px-2 py-1 m-2 border rounded">
<Chip
value={getLectureNo(index + 1, '0')}
className="bg-primary-100 text-primary-700 inline "
/>
<p class="ml-2 text-xs font-light dark:text-white inline">
{lesson.title}
</p>
<div class="flex flex-wrap">
{#each lessons as lesson, index}
<div class="px-2 py-1 m-2 border rounded">
<Chip
value={getLectureNo(index + 1, '0')}
className="bg-primary-100 text-primary-700 inline "
/>
<p class="ml-2 text-xs font-light dark:text-white inline">
{lesson.title}
</p>
<!-- <div class="flex items-center">
<!-- <div class="flex items-center">
{#if lesson.slide_url}
<span class="text-sm font-light flex w-2/4"
><PresentationFile size={16} class="mr-1" />{$t(
@@ -296,8 +319,7 @@
{/if}
</div> -->
<!-- <div class="flex items-center">
<!-- <div class="flex items-center">
{#if lesson.videos}
<span class="text-sm font-light flex w-2/4"
><Video size={16} class="mr-1" />{lesson.videos.length}
@@ -314,10 +336,36 @@
>
{/if}
</div> -->
</div>
{/each}
</div>
{/each}
</div>
</section>
</section>
{:else if $course.version === COURSE_VERSION.V2}
<section>
<!-- header -->
<div class="flex items-center justify-between">
<h1>{$t('course.navItem.landing_page.course_content')}</h1>
<span class="text-xs font-normal"
>{lesson_sections?.length}
{$t('course.navItem.landing_page.modules')}, {totalLessons}
{$t('course.navItem.landing_page.lessons')}</span
>
</div>
{#each lesson_sections as sections, index}
<SectionsDisplay
{index}
title={sections.title}
lessonCount={sections.lessons?.length}
exerciseCount={sections.lessons.reduce(
(total, lesson) => total + (lesson.totalExercises?.[0]?.count || 0),
0
)}
lessons={sections.lessons}
/>
{/each}
</section>
{/if}
<!-- Sections - Reviews -->
{#if reviews && reviews.length > 0}
@@ -563,6 +611,11 @@
color: blue;
text-decoration: underline;
}
.lesson-section:not(:last-child) {
border-bottom: 1px solid #f7f7f7;
}
@media screen and (max-width: 1023px) {
.course-content {
min-width: 80%;
@@ -78,7 +78,7 @@ const SLUG_QUERY = `
certificate_theme,
lesson_section(id, title, order),
lessons:lesson(
id, title, order
id, title, order, section_id
)
`;
@@ -393,7 +393,11 @@
"instructor": "Lehrer",
"pricing": "Preisgestaltung"
}
}
},
"course_content": "Kursinhalte",
"modules": "Module",
"lessons": "Unterricht",
"exercises": "Übungen"
},
"certificates": {
"title": "Zertifikate",
@@ -1001,6 +1001,10 @@
"bird": "Early bird offer. Buy ASAP"
},
"course_content": "Course content",
"modules": "Modules",
"lessons": "lessons",
"exercises": "exercises",
"start_course": "Start Course",
"requirement": "Requirement",
"description": "Course Description",
@@ -393,7 +393,11 @@
"instructor": "Instructor",
"pricing": "Precios"
}
}
},
"course_content": "Contenido del curso",
"modules": "Módulos",
"lessons": "lecciones",
"exercises": "ceremonias"
},
"certificates": {
"title": "Certificados",
@@ -393,7 +393,11 @@
"instructor": "Instructeur",
"pricing": "Tarifs"
}
}
},
"course_content": "Contenu du cours",
"modules": "Modules",
"lessons": "leçons",
"exercises": "exercices"
},
"certificates": {
"title": "Certificats",
@@ -393,7 +393,11 @@
"instructor": "प्रशिक्षक",
"pricing": "मूल्य निर्धारण"
}
}
},
"course_content": "पाठ्यक्रम सामग्री",
"modules": "मॉड्यूल",
"lessons": "पाठ",
"exercises": "अभ्यास"
},
"certificates": {
"title": "प्रमाण पत्र",
@@ -393,7 +393,11 @@
"instructor": "Instrutor",
"pricing": "Preços"
}
}
},
"course_content": "Conteúdo do curso",
"modules": "Módulos",
"lessons": "lições",
"exercises": "exercícios"
},
"certificates": {
"title": "Certificados",
@@ -393,7 +393,11 @@
"instructor": "Инструктор",
"pricing": "Цены"
}
}
},
"course_content": "Содержание курса",
"modules": "Модули",
"lessons": "уроки",
"exercises": "упражнения"
},
"certificates": {
"title": "Сертификаты",
@@ -393,7 +393,11 @@
"instructor": "Người hướng dẫn",
"pricing": "Định giá"
}
}
},
"course_content": "Nội dung khóa học",
"modules": "Mô-đun",
"lessons": "bài học",
"exercises": "bài tập"
},
"certificates": {
"title": "Chứng chỉ",
@@ -1,11 +1,12 @@
<script lang="ts">
import { dev } from '$app/environment';
import { fly } from 'svelte/transition';
import { course } from '$lib/components/Course/store';
import type { Course, Lesson, LessonSection } from '$lib/utils/types';
import { lessons, lessonSections } from '$lib/components/Course/components/Lesson/store/lessons';
import CourseLandingPage from '$lib/components/CourseLandingPage/index.svelte';
import Editor from '$lib/components/CourseLandingPage/components/Editor/index.svelte';
import { course } from '$lib/components/Course/store';
import { lessons } from '$lib/components/Course/components/Lesson/store/lessons';
import type { Course, Lesson } from '$lib/utils/types';
export let data;
@@ -13,15 +14,15 @@
let courseData: Course = $course;
function setCourseData(course: Course, lessons: Lesson[]) {
courseData = { ...course, lessons };
function setCourseData(course: Course, lessons: Lesson[], lesson_section: LessonSection[]) {
courseData = { ...course, lessons, lesson_section };
}
function syncCourseStore(_courseData: Course) {
$course = _courseData;
}
$: setCourseData($course, $lessons);
$: setCourseData($course, $lessons, $lessonSections);
$: dev && console.log('courseData changed', courseData);
</script>