CLASSROOMIO-T-21: Course landing page + editor
This commit is contained in:
@@ -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"
|
||||
|
||||
+17
-9
@@ -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}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user