Add people + Move fetching to client side

This commit is contained in:
rotimi-best
2021-08-24 10:45:43 +03:00
parent b3e68119a7
commit 6ef33cbfd0
27 changed files with 7073 additions and 320 deletions
+6566 -139
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -38,6 +38,7 @@
"@rollup/plugin-commonjs": "^14.0.0",
"@rollup/plugin-node-resolve": "^8.0.0",
"@rollup/plugin-replace": "^2.4.2",
"@rollup/plugin-typescript": "^8.2.5",
"@tailwindcss/typography": "^0.3.1",
"carbon-icons-svelte": "^10.21.0",
"dotenv": "^10.0.0",
@@ -56,4 +57,4 @@
"tailwindcss": "^2.1.2",
"typescript": "^4.1.3"
}
}
}
+18 -4
View File
@@ -6,6 +6,7 @@ import babel from '@rollup/plugin-babel';
import { terser } from 'rollup-plugin-terser';
import config from 'sapper/config/rollup.js';
import sveltePreprocess from 'svelte-preprocess';
import typescript from '@rollup/plugin-typescript';
import json from '@rollup/plugin-json';
import sapperEnv from 'sapper-environment';
@@ -13,6 +14,7 @@ import pkg from './package.json';
const mode = process.env.NODE_ENV;
const dev = mode === 'development';
const sourcemap = dev ? 'inline' : false;
const legacy = !!process.env.SAPPER_LEGACY_BUILD;
const onwarn = (warning, onwarn) => {
@@ -26,7 +28,7 @@ const onwarn = (warning, onwarn) => {
);
};
const preprocess = sveltePreprocess({ postcss: true });
const preprocess = sveltePreprocess({ postcss: true, sourceMap: !!sourcemap });
export default {
client: {
@@ -39,6 +41,10 @@ export default {
'process.env.NODE_ENV': JSON.stringify(mode),
preventAssignment: true,
}),
typescript({
noEmitOnError: !dev,
sourceMap: !!sourcemap,
}),
json(),
svelte({
preprocess,
@@ -50,7 +56,9 @@ export default {
browser: true,
dedupe: ['svelte'],
}),
commonjs(),
commonjs({
sourceMap: !!sourcemap,
}),
legacy &&
babel({
@@ -88,13 +96,17 @@ export default {
server: {
input: config.server.input(),
output: config.server.output(),
output: { ...config.server.output(), sourcemap },
plugins: [
replace({
'process.browser': false,
'process.env.NODE_ENV': JSON.stringify(mode),
preventAssignment: true,
}),
typescript({
noEmitOnError: !dev,
sourceMap: !!sourcemap,
}),
svelte({
preprocess,
generate: 'ssr',
@@ -104,7 +116,9 @@ export default {
resolve({
dedupe: ['svelte'],
}),
commonjs(),
commonjs({
sourceMap: !!sourcemap,
}),
],
external: Object.keys(pkg.dependencies).concat(
require('module').builtinModules
@@ -47,6 +47,7 @@
}
async function getExercises() {
if (!lessonId) return;
const exercisesData = await supabase
.from('exercise')
.select(`id, title, created_at`)
@@ -125,7 +126,7 @@
</div>
</Modal>
<PageBody width="w-11/12 m-auto">
<PageBody>
<slot:fragment slot="header">
<PrimaryButton
className="mr-2"
@@ -1,10 +1,11 @@
<script>
import { onMount } from 'svelte';
import CheckmarkFilled20 from 'carbon-icons-svelte/lib/CheckmarkFilled20';
import { stores, goto } from '@sapper/app';
import hotkeys from 'hotkeys-js';
import CheckmarkFilled20 from 'carbon-icons-svelte/lib/CheckmarkFilled20';
import Settings20 from 'carbon-icons-svelte/lib/Settings20';
import ChevronLeft32 from 'carbon-icons-svelte/lib/ChevronLeft32';
import ChevronRight32 from 'carbon-icons-svelte/lib/ChevronRight32';
import { stores, goto } from '@sapper/app';
import Expandable from '../../../Expandable/index.svelte';
import PageNav from '../../../PageNav/index.svelte';
import IconButton from '../../../IconButton/index.svelte';
@@ -14,6 +15,8 @@
getLectureNo,
} from '../../function';
import Settings from '../Settings/index.svelte';
import { settingsDialog } from '../Settings/store';
import { lessons } from '../Lesson/store/lessons';
import { course } from '../../store';
import { updateCourse } from '../../../../utils/services/courses';
@@ -78,32 +81,42 @@
to: getNavItemRoute($course.id, 'submissions'),
hideSortIcon: true,
},
{
label: 'Scoreboard',
to: getNavItemRoute($course.id, 'scoreboard'),
hideSortIcon: true,
},
// {
// label: 'Scoreboard',
// to: getNavItemRoute($course.id, 'scoreboard'),
// hideSortIcon: true,
// },
{
label: 'People',
to: getNavItemRoute($course.id, 'people'),
hideSortIcon: true,
},
{
label: 'Timetable',
to: getNavItemRoute($course.id, 'timetable'),
hideSortIcon: true,
},
// {
// label: 'Timetable',
// to: getNavItemRoute($course.id, 'timetable'),
// hideSortIcon: true,
// },
];
}
</script>
<Settings />
<div class="root z-10 {!show && 'hide'}">
{#if show}
<PageNav
bind:title={$course.title}
onEditComplete={handleSaveTitle}
isTitleEditable={true}
/>
paddingClass="pl-2"
>
<slot:fragment slot="widget">
<IconButton
onClick={() => ($settingsDialog.open = !$settingsDialog.open)}
>
<Settings20 class="carbon-icon" />
</IconButton>
</slot:fragment>
</PageNav>
<div>
{#each navItems as navItem}
<Expandable
@@ -0,0 +1,96 @@
<script>
import Modal from '../../../Modal/index.svelte';
import PrimaryButton from '../../../PrimaryButton/index.svelte';
import TextArea from '../../../Form/TextArea.svelte';
import { ROLE } from '../../../../utils/constants/roles';
import {
addGroupMember,
fetchGroup,
} from '../../../../utils/services/courses';
import { invitationModal } from './store';
import { validateForm } from '../../../Courses/functions';
import { course, setCourseData } from '../../../Course/store';
let errors = {
emails: '',
};
function addPerson() {
const { hasError, fieldErrors, students, tutors } = validateForm({
...$invitationModal,
title: 'sfdsd',
description: 'dsfdsf',
});
errors = fieldErrors;
if (hasError) return;
let membersStack = [];
console.log(`$course`, $course);
// Create groupmemebers from group_id and add teachers and students
for (const student of students) {
membersStack.push({
email: student,
group_id: $course.group.id,
role_id: ROLE.STUDENT,
});
}
for (const tutor of tutors) {
membersStack.push({
email: tutor,
group_id: $course.group.id,
role_id: ROLE.TUTOR,
});
}
addGroupMember(membersStack).then(async (membersAdded) => {
console.log(`membersAdded`, membersAdded);
const group = await fetchGroup($course.group.id);
setCourseData(
{
...$course,
group: group.data,
},
false
);
$invitationModal.open = false;
});
}
</script>
<Modal
onClose={() => ($invitationModal.open = false)}
bind:open={$invitationModal.open}
width="w-2/5"
modalHeading="Create a Course"
>
<form on:submit|preventDefault={addPerson}>
<TextArea
label="Invite Tutors"
bind:value={$invitationModal.tutors}
rows="2"
maxRows="3"
placeholder="tutor@company.com, john@gmail.com"
helperMessage="To invite people, add their emails separated by a comma"
errorMessage={errors.tutors}
className="mb-4"
/>
<TextArea
label="Invite Students"
bind:value={$invitationModal.students}
rows="2"
maxRows="3"
placeholder="student@gmail.com, john@gmail.com"
helperMessage="To invite people, add their emails separated by a comma"
errorMessage={errors.students}
className="mb-4"
/>
<div class="mt-5 flex items-center">
<PrimaryButton className="px-6 py-2" label="Finish" type="submit" />
</div>
</form>
</Modal>
@@ -0,0 +1,7 @@
import { writable } from 'svelte/store';
export const invitationModal = writable({
open: false,
tutors: '',
students: '',
});
@@ -0,0 +1,13 @@
export interface Profile {
id: string;
fullname: string;
username: string;
email: string;
}
export interface Person {
id: string;
email?: string;
role_id: number;
profile?: Profile;
}
@@ -0,0 +1,81 @@
<script>
import Modal from '../../../Modal/index.svelte';
import TextArea from '../../../Form/TextArea.svelte';
import PrimaryButton from '../../../PrimaryButton/index.svelte';
import { VARIANTS } from '../../../PrimaryButton/constants';
import { settingsDialog } from './store';
import { course } from '../../store';
import {
updateCourse,
deleteCourse,
} from '../../../../utils/services/courses';
let shouldDelete = false;
async function updateDescription() {
console.log('update');
$settingsDialog.open = false;
await updateCourse($course.id, {
description: $course.description,
});
}
async function handleDelete() {
console.log('delete');
$settingsDialog.open = false;
await deleteCourse($course.id);
}
</script>
<Modal
onClose={() => ($settingsDialog.open = false)}
bind:open={$settingsDialog.open}
width="w-96"
modalHeading="Update Course"
>
{#if shouldDelete}
<form on:submit|preventDefault={() => (shouldDelete = false)}>
<h1 class="text-2xl">Are you sure?</h1>
<div class="mt-5 flex items-center justify-between">
<PrimaryButton
className="px-6 py-2"
variant={VARIANTS.OUTLINED}
label="No, cancel"
type="submit"
/>
<PrimaryButton
className="px-6 py-2"
variant={VARIANTS.OUTLINED}
label="Yes, delete"
onClick={handleDelete}
/>
</div>
</form>
{:else}
<form on:submit|preventDefault={updateDescription}>
<TextArea
label="Short description"
bind:value={$course.description}
rows="4"
maxRows="4"
placeholder="A little description about this course"
className="mb-4"
isRequired={true}
/>
<div class="mt-5 flex items-center justify-between">
<PrimaryButton
className="px-6 py-2"
variant={VARIANTS.CONTAINED_DANGER}
label="Delete"
onClick={() => (shouldDelete = true)}
/>
<PrimaryButton className="px-6 py-2" label="Update" type="submit" />
</div>
</form>
{/if}
</Modal>
@@ -0,0 +1,5 @@
import { writable } from 'svelte/store';
export const settingsDialog = writable({
open: false,
});
+3 -2
View File
@@ -3,6 +3,7 @@
import PageNav from '../PageNav/index.svelte';
import MODES from '../../utils/constants/mode';
import PrimaryButton from '../PrimaryButton/index.svelte';
import PageBody from '../PageBody/index.svelte';
import EditContent from '../EditContent/index.svelte';
import { course } from '../Course/store';
import { updateCourse } from '../../utils/services/courses';
@@ -37,7 +38,7 @@
</svelte:fragment>
</PageNav>
<div class="px-3">
<PageBody>
{#if mode === MODES.edit}
<EditContent
writeLabel="Note"
@@ -51,4 +52,4 @@
{:else}
{@html marked($course.overview || '', { breaks: true, gfm: true })}
{/if}
</div>
</PageBody>
+11 -9
View File
@@ -26,9 +26,9 @@ export const group = writable({
students: [],
});
export async function setCourseData(data) {
export async function setCourseData(data, setLesson = true) {
if (!data || !(Object.values(data) && Object.values(data).length)) return;
console.log(`data`, data);
console.log(`setCourseData`, data);
if (data.group) {
const groupData = Object.assign(data.group, { tutors: [], students: [] });
@@ -37,7 +37,8 @@ export async function setCourseData(data) {
for (const member of groupData.members) {
if (member.role_id === ROLE.STUDENT) {
groupData.students.push(member);
} else if (member.profile) {
// } else if (member.profile) {
} else {
groupData.tutors.push({
...member,
...member.profile,
@@ -51,12 +52,13 @@ export async function setCourseData(data) {
group.set(groupData);
}
const orderedLessons = (data.lessons || []).sort(
(a, b) => new Date(a.created_at) - new Date(b.created_at)
);
// .map();
lessons.set(orderedLessons);
if (setLesson) {
const orderedLessons = (data.lessons || []).sort(
(a, b) => new Date(a.created_at) - new Date(b.created_at)
);
// .map();
lessons.set(orderedLessons);
}
delete data.lessons;
course.set(data);
+1 -1
View File
@@ -8,7 +8,7 @@
import { course } from '../Course/store';
import Confetti from '../Confetti/index.svelte';
export let path;
export let path = '';
export let isExercisePage = false;
function setStoreWithData() {
+3 -2
View File
@@ -1,10 +1,11 @@
<script>
// import
// border border-gray-300 rounded-md p-3 bg-blue-200 bg-opacity-25
export let width = 'max-w-3xl';
export let width = 'max-w-3xl w-11/12';
export let className = '';
</script>
<div class="root mt-4 mx-auto {width} relative pb-5 pr-5">
<div class="root mt-4 mx-auto {width} {className} relative pb-5">
{#if $$slots.header}
<div
class="head flex bg-white items-center justify-between sticky right-0 w-full py-3"
+4 -4
View File
@@ -4,11 +4,12 @@
export let title = '';
export let overidableStyle = '';
export let navItems = [];
export let addButtonHref;
export let addButtonLabel;
export let addButtonHref = '';
export let addButtonLabel = '';
export let disableSticky = false;
export let isTitleEditable = false;
export let onEditComplete = () => {};
export let paddingClass = 'px-2';
let dynamicRootClass = '';
let enterEditTitleMode = false;
@@ -19,7 +20,7 @@
<div
class="header flex items-center justify-between {!disableSticky &&
'sticky'} {dynamicRootClass}"
'sticky'} {paddingClass} {dynamicRootClass}"
style={overidableStyle}
>
{#if !!title}
@@ -66,7 +67,6 @@
<style>
.header {
border-bottom: 1px solid var(--border-color);
padding: 0 15px;
min-height: 61px;
background-color: #fff;
z-index: 1;
+7 -4
View File
@@ -1,10 +1,13 @@
export const VARIANTS = {
CONTAINED: "CONTAINED",
OUTLINED: "OUTLINED",
CONTAINED: 'CONTAINED',
CONTAINED_DANGER: 'CONTAINED_DANGER',
OUTLINED: 'OUTLINED',
};
export const VARIANTS_CLASS = {
[VARIANTS.CONTAINED]: "bg-blue-700 hover:bg-blue-900 text-white font-bold",
[VARIANTS.CONTAINED]: 'bg-blue-700 hover:bg-blue-900 text-white font-bold',
[VARIANTS.CONTAINED_DANGER]:
'bg-red-700 hover:bg-red-900 text-white font-bold',
[VARIANTS.OUTLINED]:
"border border-gray-700 hover:border-black-300 hover:bg-gray-200 text-black",
'border border-gray-700 hover:border-black-300 hover:bg-gray-200 text-black',
};
+5
View File
@@ -16,6 +16,7 @@
import { handleAuthChange } from '../utils/functions/api';
import { user, profile } from '../utils/store/user';
import { getSupabase } from '../utils/functions/supabase';
import { setProfileIdOfGroupMember } from '../utils/services/courses';
export let segment;
export let config;
@@ -52,6 +53,7 @@
id: authUser.id,
username: regexUsernameMatch[1],
fullname: regexUsernameMatch[1],
email: authUser.email,
});
// Profile created, go to profile page
@@ -61,6 +63,7 @@
$user.currentSession = authUser;
profile.set(data[0]);
await setProfileIdOfGroupMember(authUser.email, data[0].id);
// If user coming to login page, then
if (path.includes('login') || path.includes('signup')) {
@@ -75,6 +78,8 @@
console.log(`profileData`, profileData);
profile.set(profileData);
setProfileIdOfGroupMember(authUser.email, profileData.id);
// If user coming to login page, then
if (path.includes('login') || path.includes('signup')) {
goto('/dashboard');
+4 -8
View File
@@ -1,5 +1,5 @@
<script context="module">
import { profile, user } from '../utils/store/user';
import { profile } from '../utils/store/user';
import { fetchCourses } from '../components/Courses/api';
export async function preload(page, params) {
@@ -29,6 +29,7 @@
import { courses, createCourseModal } from '../components/Courses/store';
import { validateForm } from '../components/Courses/functions';
import { ROLE } from '../utils/constants/roles';
import { addGroupMember } from '../utils/services/courses';
// const { page } = stores();
@@ -69,8 +70,9 @@
// Get profile of author and add as member
const admin = await addGroupMember({
profile_id: $profile.id,
email: student,
group_id,
role_id: ROLE.ADMIN,
role_id: ROLE.TUTOR,
});
let membersStack = [];
@@ -97,12 +99,6 @@
});
}
function addGroupMember(member) {
return supabase.from('groupmember').insert(member, {
returning: 'minimal',
});
}
async function getCourses(userId) {
console.log(`getCourses process.browser`, process.browser);
if (process.browser) {
+9 -11
View File
@@ -1,13 +1,6 @@
<script context="module">
import { fetchCourse } from '../../../utils/services/courses';
export async function preload({ params }, session) {
const {
data,
// error
} = await fetchCourse(params.id, session);
return { courseData: data };
return { courseId: params.id };
}
</script>
@@ -15,12 +8,17 @@
import { onMount } from 'svelte';
import Overview from '../../../components/Course/index.svelte';
import CourseContainer from '../../../components/CourseContainer/index.svelte';
import { setCourseData } from '../../../components/Course/store';
import { setCourseData, course } from '../../../components/Course/store';
import { fetchCourse } from '../../../utils/services/courses';
export let courseData;
export let courseId;
onMount(async () => {
setCourseData(courseData);
console.log(`overview courseId`, courseId);
if ($course.id) return;
const { data } = await fetchCourse(courseId);
setCourseData(data);
});
</script>
@@ -3,35 +3,10 @@
fetchCourse,
fetchLesson,
} from '../../../../../utils/services/courses';
let prevLessonId;
export async function preload({ params }, session) {
const { id: courseId, lessonParams = [] } = params;
const [lessonId, exerciseRouteName, exerciseId] = lessonParams;
let courseData;
let lessonData;
if (!process.browser) {
const [course, lesson] = await Promise.all([
fetchCourse(courseId, session),
fetchLesson(lessonId),
]);
courseData = course.data;
lessonData = lesson.data;
} else if (prevLessonId && prevLessonId !== lessonId) {
const lesson = await fetchLesson(lessonId);
lessonData = lesson.data;
}
prevLessonId = lessonId;
return {
courseData,
lessonData,
isMaterialsTabActive: !exerciseRouteName,
lessonId,
exerciseId,
params,
};
}
</script>
@@ -47,21 +22,54 @@
import Materials from '../../../../../components/Course/components/Lesson/Materials/index.svelte';
import Exercises from '../../../../../components/Course/components/Lesson/Exercises/index.svelte';
import MODES from '../../../../../utils/constants/mode.js';
import { setCourseData } from '../../../../../components/Course/store';
import {
setCourseData,
course,
} from '../../../../../components/Course/store';
import { lesson } from '../../../../../components/Course/components/Lesson/store/lessons';
export let courseData;
export let lessonData;
export let params;
export let lessonId;
export let exerciseId;
export let isMaterialsTabActive;
let path;
let mode = MODES.view;
let prevLessonId;
const { page } = stores();
function setLesson() {
async function fetchReqData() {
const { id: courseId, lessonParams = [] } = params;
const [_lessonId, exerciseRouteName, _exerciseId] = lessonParams;
let courseData;
let lessonData;
if (!$course.id) {
const [_course, lesson] = await Promise.all([
fetchCourse(courseId),
fetchLesson(_lessonId),
]);
courseData = _course.data;
lessonData = lesson.data;
} else if (prevLessonId && prevLessonId !== _lessonId) {
const lesson = await fetchLesson(_lessonId);
lessonData = lesson.data;
}
prevLessonId = _lessonId;
lessonId = _lessonId;
exerciseId = _exerciseId;
isMaterialsTabActive = !exerciseRouteName;
setCourseData(courseData);
setLesson(lessonData);
}
function setLesson(lessonData) {
if (!lessonData) return;
lesson.update((l) => ({
@@ -71,19 +79,13 @@
}));
}
onMount(() => {
setCourseData(courseData);
setLesson();
});
afterUpdate(setLesson);
$: path = $page.path.replace(/\/exercises[\/ 0-9 a-z -]*/, '');
$: {
if (lessonId) {
console.log(`params`, params);
if (params) {
mode = MODES.view;
fetchReqData();
}
}
</script>
@@ -119,8 +121,8 @@
{#if !isMaterialsTabActive}
<Exercises {lessonId} {exerciseId} path={`${path}/exercises`} />
{:else}
<PageBody width="w-11/12 m-auto">
{:else if !!lessonId}
<PageBody>
<Materials {lessonId} {mode} />
</PageBody>
{/if}
+11 -19
View File
@@ -1,17 +1,6 @@
<script context="module">
import { fetchCourse } from '../../../../utils/services/courses';
export async function preload({ params }, session) {
if (process.browser) {
return {};
}
const {
data,
// error
} = await fetchCourse(params.id, session);
return { courseData: data };
export async function preload({ params }) {
return { courseId: params.id };
}
</script>
@@ -30,6 +19,7 @@
import TextField from '../../../../components/Form/TextField.svelte';
import IconButton from '../../../../components/IconButton/index.svelte';
import Select from '../../../../components/Form/Select.svelte';
import { fetchCourse } from '../../../../utils/services/courses';
import CourseContainer from '../../../../components/CourseContainer/index.svelte';
import {
lessons,
@@ -43,7 +33,7 @@
group,
} from '../../../../components/Course/store';
export let courseData;
export let courseId;
let lessonEditing;
let ref;
@@ -88,8 +78,10 @@
}, 100);
}
onMount(() => {
setCourseData(courseData);
onMount(async () => {
if ($course.id) return;
const { data } = await fetchCourse(courseId);
setCourseData(data);
});
$: console.log(`$lessons`, $lessons);
@@ -106,12 +98,12 @@
</div>
</PageNav>
<PageBody width="w-11/12 m-auto">
<div class="w-4/5 mt-4 m-auto">
<PageBody>
<div class="w-11/12 mt-4 m-auto">
{#each $lessons as lesson, index}
<div
bind:this={ref}
class="group relative m-auto rounded-md border-2 border-gray-100 py-3 px-5 mr-4 mb-4 flex items-center hover:shadow-2xl shadow-md transition delay-150 duration-300 ease-in-out"
class="group relative m-auto rounded-md border-2 border-gray-100 py-3 px-5 mb-4 flex items-center hover:shadow-2xl shadow-md transition delay-150 duration-300 ease-in-out"
>
<!-- Complete or Not complete icon -->
<div class="absolute -left-6 -top-6 success">
+67 -51
View File
@@ -1,71 +1,87 @@
<script context="module">
import { fetchCourse } from '../../../utils/services/courses';
export async function preload({ params }, session) {
if (process.browser) {
return {};
}
const {
data,
// error
} = await fetchCourse(params.id, session);
return { courseData: data };
export async function preload({ params }) {
return { courseId: params.id };
}
</script>
<script>
<script lang="ts">
import { onMount } from 'svelte';
import { stores } from '@sapper/app';
import CourseContainer from '../../../components/CourseContainer/index.svelte';
import PageNav from '../../../components/PageNav/index.svelte';
import UserCard from '../../../components/UserCard/index.svelte';
import { setCourseData } from '../../../components/Course/store';
import PrimaryButton from '../../../components/PrimaryButton/index.svelte';
import PageBody from '../../../components/PageBody/index.svelte';
import { setCourseData, course } from '../../../components/Course/store';
import InviationModal from '../../../components/Course/components/People/InviationModal.svelte';
import { invitationModal } from '../../../components/Course/components/People/store';
import type { Person } from '../../../components/Course/components/People/types';
import { ROLE_LABEL } from '../../../utils/constants/roles';
export let courseData = {};
export let courseId: string = '';
const { page } = stores();
let people: Array<Person> = [];
let borderBottomGrey = 'border-r-0 border-b border-l-0 border-gray-300';
const teachers = [];
const students = [...teachers, ...teachers, ...teachers];
function setPeople(group) {
if (!group) return;
const { students, tutors } = group;
people = [...students, ...tutors].sort((a, b) => a.role_id - b.role_id);
console.log(`group changed`, group);
}
onMount(() => {
setCourseData(courseData);
onMount(async () => {
if ($course.id) return;
const { data } = await fetchCourse(courseId);
setCourseData(data);
});
$: setPeople($course.group);
</script>
<CourseContainer>
<PageNav title="People" disableSticky={true} />
<div class="m-3">
<h3>
<a id="teachers" name="teachers" href="{$page.path}#teachers">Teachers</a>
</h3>
<div class="flex flex-wrap">
{#each teachers as teacher}
<UserCard
name={teacher.name}
avatarUrl={teacher.avatar}
title={teacher.title}
telegramLink={teacher.telegramLink}
mailLink={teacher.mailLink}
/>
{/each}
</div>
</div>
<InviationModal />
<div class="m-3">
<h3>
<a id="students" name="students" href="{$page.path}#students">Students</a>
</h3>
<div class="flex flex-wrap">
{#each students as student}
<UserCard
name={student.name}
avatarUrl={student.avatar}
title={student.title}
/>
<CourseContainer>
<PageNav title="People" disableSticky={true}>
<slot:fragment slot="widget">
<PrimaryButton
className="mr-2"
label="Add"
onClick={() => ($invitationModal.open = true)}
/>
</slot:fragment>
</PageNav>
<PageBody className="">
<div class="table rounded-md border border-gray-300 w-11/12 m-auto">
<div
class="flex items-center justify-evenly font-bold border-t-0 {borderBottomGrey} p-3"
>
<span class="mr-2">No</span>
<!-- <span class="flex-grow" /> -->
<p class="text-lg w-1/4">Name</p>
<p class="text-lg w-1/4">Email</p>
<p class="text-lg w-1/4 ">Role</p>
</div>
{#each people as person, index}
<div
class="flex items-center justify-evenly p-3 cursor-pointer {borderBottomGrey} hover:bg-gray-100"
>
<span class="mr-2">{index + 1}</span>
<!-- <span class="flex-grow" /> -->
{#if person.profile}
<p class="text-lg w-1/4 break-all">{person.profile.fullname}</p>
{:else}
<p
class="bg-yellow-500 w-1/4 text-sm text-center rounded-xl text-white"
>
Pending confirmation
</p>
{/if}
<p class="text-lg w-1/4 break-all">{person.email}</p>
<p class="text-lg w-1/4 ">{ROLE_LABEL[person.role_id]}</p>
</div>
{/each}
</div>
</div>
</PageBody>
</CourseContainer>
@@ -19,6 +19,7 @@
import { onMount } from 'svelte';
import CourseContainer from '../../../components/CourseContainer/index.svelte';
import PageNav from '../../../components/PageNav/index.svelte';
import PageBody from '../../../components/PageBody/index.svelte';
import { setCourseData } from '../../../components/Course/store';
export let courseData = {};
@@ -30,4 +31,5 @@
<CourseContainer>
<PageNav title="Scoreboard" />
<PageBody />
</CourseContainer>
+2
View File
@@ -19,6 +19,7 @@
import { onMount } from 'svelte';
import CourseContainer from '../../../components/CourseContainer/index.svelte';
import PageNav from '../../../components/PageNav/index.svelte';
import PageBody from '../../../components/PageBody/index.svelte';
import { setCourseData } from '../../../components/Course/store';
export let courseData = {};
@@ -30,4 +31,5 @@
<CourseContainer>
<PageNav title="Timetable" />
<PageBody />
</CourseContainer>
+6
View File
@@ -3,3 +3,9 @@ export const ROLE = {
TUTOR: 2,
STUDENT: 3,
};
export const ROLE_LABEL = {
[ROLE.ADMIN]: 'Admin',
[ROLE.TUTOR]: 'Tutor',
[ROLE.STUDENT]: 'Student',
};
+62 -10
View File
@@ -5,11 +5,17 @@ import { QUESTION_TYPE } from '../../../components/Question/constants';
export async function fetchCourse(courseId, session) {
let jsonUser;
try {
jsonUser = JSON.parse(session.user);
} catch (error) {
console.log('Token is expired probably cause user not found');
return this.redirect(301, '/login');
if (!process.browser) {
try {
jsonUser = JSON.parse(session.user);
} catch (error) {
console.log('Token is expired probably cause user not found');
return {
data: [],
cantFetch: true,
};
// return this.redirect(301, '/login');
}
}
const { data, error } = await supabase
@@ -18,6 +24,7 @@ export async function fetchCourse(courseId, session) {
`
id,
title,
description,
overview,
group(*,
members:groupmember(*,
@@ -45,6 +52,34 @@ export async function fetchCourse(courseId, session) {
};
}
export async function fetchGroup(groupId) {
const { data, error } = await supabase
.from('group')
.select(`*,members:groupmember(*,profile(*))`)
.match({ id: groupId })
.single();
return {
data,
error,
};
}
export async function setProfileIdOfGroupMember(email, profileId) {
const { data, error } = await supabase
.from('groupmember')
.update({
email: null,
profile_id: profileId,
})
.match({ email: email });
return {
data,
error,
};
}
export async function updateCourse(courseId, course) {
return await supabase
.from('course')
@@ -52,6 +87,16 @@ export async function updateCourse(courseId, course) {
.match({ id: courseId });
}
export async function deleteCourse(courseId, course) {
return await supabase.from('course').delete(course).match({ id: courseId });
}
export function addGroupMember(member) {
return supabase.from('groupmember').insert(member, {
returning: 'minimal',
});
}
export function fetchLesson(lessonId) {
return supabase
.from('lesson')
@@ -117,14 +162,21 @@ export async function upsertExercise(questionnaire, exerciseId) {
is_dirty,
} = question;
// DELETE /delete/:questionId
// DELETE /delete/:questionId - Don't delete if answer already given
if (deleted_at) {
// Delete from server only if this question exists in the database
if (!isNew(id)) {
Promise.all([
supabase.from('option').delete().match({ question_id: id }),
supabase.from('question').delete().match({ id }),
]);
const { error } = await supabase
.from('question')
.delete()
.match({ id });
if (error) {
console.error('Cannot delete this question', error);
continue;
}
await supabase.from('option').delete().match({ question_id: id });
}
// Skip the remaining operation so we filter out this question from the new questions array
+17
View File
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"lib": ["DOM", "ES2017", "WebWorker"],
"target": "es2017",
"allowSyntheticDefaultImports": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importsNotUsedAsValues": "error",
"isolatedModules": true,
"moduleResolution": "node",
"sourceMap": true,
"strict": true,
"types": ["svelte"]
},
"include": ["src/**/*", "src/node_modules/**/*"],
"exclude": ["node_modules/*", "__sapper__/*", "static/*"]
}