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:
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,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%;
|
||||
|
||||
Reference in New Issue
Block a user