CLASSROOMIO-T-21: Course landing page + editor

This commit is contained in:
Rotimi Best
2022-12-30 13:08:54 +01:00
parent 38eae9dafb
commit 963d899adf
80 changed files with 2543 additions and 653 deletions
@@ -25,7 +25,7 @@
<div class="mt-5 flex items-center justify-between">
<PrimaryButton
className="px-6 py-2"
className="px-6 py-3"
variant={VARIANTS.OUTLINED}
label="No, please don't"
onClick={() => {
@@ -34,7 +34,7 @@
}}
/>
<PrimaryButton
className="px-6 py-2"
className="px-6 py-3"
variant={VARIANTS.CONTAINED_DANGER}
label="Yes, delete"
onClick={() => {
@@ -132,13 +132,13 @@
<div class="mt-5 flex items-center justify-between">
<PrimaryButton
className="px-6 py-2"
className="px-6 py-3"
variant={VARIANTS.OUTLINED}
label="No, cancel"
type="submit"
/>
<PrimaryButton
className="px-6 py-2"
className="px-6 py-3"
variant={VARIANTS.CONTAINED_DANGER}
label={isDeleting ? 'Deleting...' : 'Yes, delete'}
isDisabled={isDeleting}
@@ -174,7 +174,7 @@
</QuestionContainer>
<div class="float-right">
<PrimaryButton
className="px-6 py-2"
className="px-6 py-3"
variant={VARIANTS.CONTAINED_DANGER}
label="Delete this exercise"
onClick={() => (shouldDelete = true)}
@@ -111,7 +111,7 @@
}}
label="Save"
variant={VARIANTS.CONTAINED_SUCCESS}
className="py-2 px-8"
className="py-3 px-8"
/>
</div>
<div class="border border-gray-300 rounded-md mt-2">
@@ -228,14 +228,14 @@
<h2 class="text-xl mb-2 mt-0">{$questionnaire.title}</h2>
{#if STATUS.GRADED === $questionnaireMetaData.status}
<span
class="status-text bg-green-700 text-white rounded-full py-2 px-6 text-center"
class="status-text bg-green-700 text-white rounded-full py-3 px-6 text-center"
title="Status: Pending Review"
>
Graded
</span>
{:else}
<span
class="status-text bg-yellow-600 text-white rounded-full py-2 px-6 text-center"
class="status-text bg-yellow-600 text-white rounded-full py-3 px-6 text-center"
title="Status: Pending Review"
>
Pending
@@ -3,7 +3,7 @@
import { onMount } from 'svelte';
import AudioConsole32 from 'carbon-icons-svelte/lib/AudioConsole32';
import Box from '../../../../Box/index.svelte';
import { Firework } from 'svelte-loading-spinners';
import { Moon } from 'svelte-loading-spinners';
import Backdrop from '../../../../Backdrop/index.svelte';
import PrimaryButton from '../../../../PrimaryButton/index.svelte';
import RoleBasedSecurity from '../../../../RoleBasedSecurity/index.svelte';
@@ -122,7 +122,7 @@
{#if isFetching}
<Backdrop>
<Firework size="60" color="#1d4ed8" unit="px" duration="1s" />
<Moon size="60" color="#1d4ed8" unit="px" duration="1s" />
</Backdrop>
{/if}
@@ -147,7 +147,7 @@
<div class="mt-5 flex items-center">
<PrimaryButton
className="px-6 py-2"
className="px-6 py-3"
label="Submit"
onClick={handleAddExercise}
/>
@@ -1,5 +1,6 @@
<script>
import MODES from '../../../../../utils/constants/mode.js';
import { formatYoutubeVideo } from '../../../../../utils/functions/formatYoutubeVideo';
import Tabs from '../../../../Tabs/index.svelte';
import TabContent from '../../../../TabContent/index.svelte';
@@ -24,34 +25,6 @@
const onChange = (tab) => () => (currentTab = tab);
function formatYoutubeVideo(url) {
console.log(`url`, url);
const prefix = 'https://www.youtube.com/embed';
// https://www.youtube.com/embed/qajK1J1neAM
if (url.includes('embed')) {
return url;
}
// https://youtu.be/qajK1J1neAM
if (url.includes('.be/')) {
const splittedUrlWithBe = url.split('.be/');
return `${prefix}/${splittedUrlWithBe[1]}`;
}
// https://www.youtube.com/watch?v=qajK1J1neAM
const splitedUrl = url.split('watch');
if (splitedUrl.length !== 2) {
errors.video = 'Wrong url format';
return;
}
const query = new URLSearchParams(splitedUrl[1]);
return `${prefix}/${query.get('v')}`;
}
function handleSave(prevMode) {
if (prevMode === MODES.edit) {
handleUpdateLessonMaterials($lesson, lessonId);
@@ -146,7 +119,7 @@
width="100%"
height="569"
class="iframe"
src={formatYoutubeVideo(url)}
src={formatYoutubeVideo(url, errors)}
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
@@ -1,11 +1,13 @@
import { writable } from 'svelte/store';
import { writable, Writable, Updater } from 'svelte/store';
import {
createLesson,
updateLesson,
deleteLesson,
} from '../../../../../utils/services/courses';
export const lessons = writable([]);
import type { Lesson, Course } from '../../../../../utils/types';
export const lessons: Writable<Lesson[]> = writable([]);
export const lesson = writable({
id: null,
totalExercises: 0,
@@ -19,7 +21,7 @@ export const lesson = writable({
export const isLessonDirty = writable(false);
export function handleAddLesson() {
lessons.update((_lessons) => {
lessons.update(((_lessons) => {
return [
..._lessons,
{
@@ -31,10 +33,10 @@ export function handleAddLesson() {
is_complete: false,
},
];
});
}) as Updater<any>);
}
export function handleDelete(lessonId) {
export function handleDelete(lessonId: Lesson['id']) {
return async () => {
// Need to implement soft delete
if (lessonId) {
@@ -52,18 +54,21 @@ export function handleDelete(lessonId) {
};
}
export async function handleSaveLesson(lesson, course_id) {
export async function handleSaveLesson(
lesson: Lesson,
course_id: Course['id']
) {
console.log(`handleSaveLesson lesson`, lesson);
const newLesson = {
title: lesson.title,
lesson_at: lesson.lesson_at,
call_url: lesson.call_url,
teacher_id: lesson.profile ? lesson.profile.id : null,
teacher_id: lesson.profile ? lesson.profile.id : undefined,
course_id,
is_complete: lesson.is_complete,
};
let updatedLesson;
let updatedLesson: any[] | null = null;
if (typeof lesson.id === 'string') {
// No need to get the result of update cause we have all in local state
@@ -85,7 +90,10 @@ export async function handleSaveLesson(lesson, course_id) {
);
}
export async function handleUpdateLessonMaterials(lesson, lessonId) {
export async function handleUpdateLessonMaterials(
lesson: any,
lessonId: Lesson['id']
) {
const materials = {
...lesson.materials,
};
@@ -171,7 +171,7 @@
<a
class="item flex items-center {(
path || $page.path
).includes(item.id) && 'active'} pl-7 py-2 {isStudent &&
).includes(item.id) && 'active'} pl-7 py-3 {isStudent &&
!item.is_complete
? 'cursor-not-allowed'
: ''}"
@@ -27,13 +27,13 @@
<div class="mt-5 flex items-center justify-between">
<PrimaryButton
className="px-6 py-2"
className="px-6 py-3"
variant={VARIANTS.OUTLINED}
label="No"
onClick={() => ($deleteMemberModal.open = false)}
/>
<PrimaryButton
className="px-6 py-2"
className="px-6 py-3"
variant={VARIANTS.OUTLINED}
label="Yes"
onClick={handleDelete}
@@ -9,7 +9,7 @@
} from '../../../../utils/services/courses';
import { invitationModal, resetForm } from './store';
import { validateForm } from '../../../Courses/functions';
import { course, setCourseData } from '../../../Course/store';
import { course, setCourse } from '../../store';
let errors = {
emails: '',
@@ -47,7 +47,7 @@
addGroupMember(membersStack).then(async (membersAdded) => {
const group = await fetchGroup($course.group.id);
setCourseData(
setCourse(
{
...$course,
group: group.data,
@@ -90,7 +90,7 @@
/>
<div class="mt-5 flex items-center">
<PrimaryButton className="px-6 py-2" label="Finish" type="submit" />
<PrimaryButton className="px-6 py-3" label="Finish" type="submit" />
</div>
</form>
</Modal>
@@ -29,8 +29,7 @@
}
$settingsDialog.open = false;
const logo = await updateCourse($course.id, {
avatar,
const logo = await updateCourse($course.id, avatar, {
title: $course.title,
description: $course.description,
});
@@ -67,13 +66,13 @@
<div class="mt-5 flex items-center justify-between">
<PrimaryButton
className="px-6 py-2"
className="px-6 py-3"
variant={VARIANTS.OUTLINED}
label="No, cancel"
type="submit"
/>
<PrimaryButton
className="px-6 py-2"
className="px-6 py-3"
variant={VARIANTS.OUTLINED}
label="Yes, delete"
onClick={handleDelete}
@@ -103,12 +102,12 @@
<div class="mt-5 flex items-center justify-between">
<PrimaryButton
className="px-6 py-2"
className="px-6 py-3"
variant={VARIANTS.CONTAINED_DANGER}
label="Delete"
onClick={() => (shouldDelete = true)}
/>
<PrimaryButton className="px-6 py-2" label="Update" type="submit" />
<PrimaryButton className="px-6 py-3" label="Update" type="submit" />
</div>
</form>
{/if}
+3 -3
View File
@@ -26,12 +26,12 @@ export function getLessonsRoute(courseId, lessonId) {
return `${path}/${lessonId}`;
}
export function getLectureNo(index) {
export function getLectureNo(index, initNo = '00') {
if (index <= 9) {
return `00${index}`;
return `${initNo}${index}`;
}
return `0${index}`;
return `${initNo.replace('0', '')}${index}`;
}
export function formatAnswers(data) {
+1 -1
View File
@@ -6,7 +6,7 @@
import PrimaryButton from '../PrimaryButton/index.svelte';
import PageBody from '../PageBody/index.svelte';
import EditContent from '../EditContent/index.svelte';
import { course } from '../Course/store';
import { course } from './store';
import { updateCourse } from '../../utils/services/courses';
let mode = MODES.view;
@@ -1,8 +1,35 @@
import { writable } from 'svelte/store';
import { writable, Writable } from 'svelte/store';
import { lessons } from './components/Lesson/store/lessons';
//@ts-ignore
import { ROLE } from '../../utils/constants/roles';
import type { Course } from '../../utils/types';
export const course = writable({ id: '' });
export const course: Writable<Course> = writable({
id: '',
title: '',
description: '',
cost: 0,
currency: '',
metadata: {
requirements: '',
description: '',
goals: '',
videoUrl: '',
showDiscount: false,
discount: 0,
reward: {
show: false,
description: '',
},
instructor: {
name: '',
role: '',
coursesNo: 0,
description: '',
imgUrl: '',
},
},
});
export const mockGroupMember = {
id: '434534534535',
@@ -27,7 +54,7 @@ export const group = writable({
people: [],
});
export async function setCourseData(data, setLesson = true) {
export async function setCourse(data: Course, setLesson = true) {
if (!data || !(Object.values(data) && Object.values(data).length)) return;
// const tutorsById = {};
@@ -38,27 +65,37 @@ export async function setCourseData(data, setLesson = true) {
people: [],
});
// @ts-ignore
if (Array.isArray(groupData.members)) {
// @ts-ignore
for (const member of groupData.members) {
if (member.role_id === ROLE.STUDENT) {
// @ts-ignore
groupData.students.push(member);
} else if (member.profile) {
// @ts-ignore
groupData.tutors.push(member.profile);
// tutorsById[member.profile.id] = member.profile;
}
}
// @ts-ignore
groupData.people = groupData.members;
}
// @ts-ignore
delete groupData.members;
// @ts-ignore
group.set(groupData);
}
if (setLesson) {
// @ts-ignore
const orderedLessons = (data.lessons || [])
// @ts-ignore
.sort((a, b) => new Date(a.created_at) - new Date(b.created_at))
// @ts-ignore
.sort((a, b) => a.order - b.order);
// .map((lesson) => ({
// ...lesson,
@@ -67,6 +104,29 @@ export async function setCourseData(data, setLesson = true) {
lessons.set(orderedLessons);
}
//@ts-ignore
delete data.lessons;
if (data.metadata && !Object.values(data.metadata)) {
data.metadata = {
requirements: '',
description: '',
goals: '',
videoUrl: '',
showDiscount: false,
discount: 0,
reward: {
show: false,
description: '',
},
instructor: {
name: '',
role: '',
coursesNo: 0,
description: '',
imgUrl: '',
},
};
}
course.set(data);
}