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 <best@classroomio.com>
This commit is contained in:
co-authored by
Rotimi Best
parent
f7640b8a67
commit
982acb0711
@@ -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,6 +279,7 @@
|
||||
</section>
|
||||
|
||||
<!-- Sections - Lessons -->
|
||||
{#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">
|
||||
@@ -278,7 +302,6 @@
|
||||
{lesson.title}
|
||||
</p>
|
||||
|
||||
|
||||
<!-- <div class="flex items-center">
|
||||
{#if lesson.slide_url}
|
||||
<span class="text-sm font-light flex w-2/4"
|
||||
@@ -296,7 +319,6 @@
|
||||
{/if}
|
||||
</div> -->
|
||||
|
||||
|
||||
<!-- <div class="flex items-center">
|
||||
{#if lesson.videos}
|
||||
<span class="text-sm font-light flex w-2/4"
|
||||
@@ -318,6 +340,32 @@
|
||||
{/each}
|
||||
</div>
|
||||
</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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user