CLASSROOMIO-T-97: Course UI improvement 😁😁😁

This commit is contained in:
Rotimi Best
2023-08-05 04:44:32 +01:00
parent 131bb878ee
commit d542a4f7e3
27 changed files with 258 additions and 319 deletions
+2
View File
@@ -63,6 +63,8 @@ h6 {
.dark .prose h4, .dark .prose h4,
.dark .prose h5, .dark .prose h5,
.dark .prose h6, .dark .prose h6,
.dark .prose li,
.dark .prose li::marker,
.dark .prose p { .dark .prose p {
color: #fff; color: #fff;
} }
+1 -1
View File
@@ -10,7 +10,7 @@
'bg-' 'bg-'
) )
? '' ? ''
: 'bg-gray-200'} {shape} text-center" : 'bg-gray-200 dark:text-black'} {shape} text-center"
> >
{value} {value}
</p> </p>
@@ -1,18 +1,26 @@
<script> <script>
import CaretSortDownIcon from 'carbon-icons-svelte/lib/CaretSortDown.svelte'; import CaretSortDownIcon from 'carbon-icons-svelte/lib/CaretSortDown.svelte';
import ChevronDownIcon from 'carbon-icons-svelte/lib/ChevronDown.svelte';
import ChevronUpIcon from 'carbon-icons-svelte/lib/ChevronUp.svelte';
import IconButton from '$lib/components/IconButton/index.svelte';
import NavIcons from './NavIcons.svelte';
export let handleClick = () => {}; export let handleClick = () => {};
export let label = ''; export let label = '';
export let isGroupActive = false; export let isGroupActive = false;
export let isExpanded = true;
export let total = 0;
// export let subMenuItems = []; // export let subMenuItems = [];
let isExpanded = true; let activeClass = 'bg-gray-200 dark:bg-gray-500';
let activeClass = 'bg-gray-200 dark:bg-gray-700';
function onClick() { function onClick() {
// isExpanded = !isExpanded;
handleClick(); handleClick();
} }
function toggleIsExpanded() {
isExpanded = !isExpanded;
}
</script> </script>
<div class=""> <div class="">
@@ -22,7 +30,22 @@
tabindex="0" tabindex="0"
on:click={onClick} on:click={onClick}
> >
<span class="dark:text-white font-bold">{label}</span> <NavIcons name={label} />
<span class="dark:text-white font-bold text-md">{label}</span>
{#if total}
<span class="ml-1">({total})</span>
{/if}
<span class="grow" />
{#if label === 'Lessons'}
<IconButton size="small" stopPropagation={true} onClick={toggleIsExpanded}>
{#if isExpanded}
<ChevronUpIcon class="carbon-icon dark:text-white" />
{:else}
<ChevronDownIcon class="carbon-icon dark:text-white" />
{/if}
</IconButton>
{/if}
</button> </button>
{#if isExpanded} {#if isExpanded}
<div class="flex flex-col"> <div class="flex flex-col">
@@ -0,0 +1,30 @@
<script>
import TableOfContents from 'carbon-icons-svelte/lib/TableOfContents.svelte'; // overview
import Result from 'carbon-icons-svelte/lib/Result.svelte';
import Workspace from 'carbon-icons-svelte/lib/Workspace.svelte';
import ListChecked from 'carbon-icons-svelte/lib/ListChecked.svelte';
import Group from 'carbon-icons-svelte/lib/Group.svelte';
import ApplicationWeb from 'carbon-icons-svelte/lib/ApplicationWeb.svelte';
import Badge from 'carbon-icons-svelte/lib/Badge.svelte';
import IbmWatsonKnowledgeCatalog from 'carbon-icons-svelte/lib/IbmWatsonKnowledgeCatalog.svelte';
export let name = '';
</script>
{#if name === 'Overview'}
<TableOfContents size={20} class="carbon-icon dark:text-white mr-2" />
{:else if name === 'Lessons'}
<IbmWatsonKnowledgeCatalog size={20} class="carbon-icon dark:text-white mr-2" />
{:else if name === 'Attendance'}
<ListChecked size={20} class="carbon-icon dark:text-white mr-2" />
{:else if name === 'Submissions'}
<Workspace size={20} class="carbon-icon dark:text-white mr-2" />
{:else if name === 'Marks'}
<Result size={20} class="carbon-icon dark:text-white mr-2" />
{:else if name === 'People'}
<Group size={20} class="carbon-icon dark:text-white mr-2" />
{:else if name === 'Landing Page'}
<ApplicationWeb size={20} class="carbon-icon dark:text-white mr-2" />
{:else if name === 'Certificates'}
<Badge size={20} class="carbon-icon dark:text-white mr-2" />
{/if}
@@ -5,21 +5,21 @@
import hotkeys from 'hotkeys-js'; import hotkeys from 'hotkeys-js';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
import LockedIcon from 'carbon-icons-svelte/lib/Locked.svelte'; import LockedIcon from 'carbon-icons-svelte/lib/Locked.svelte';
import HomeIcon from 'carbon-icons-svelte/lib/Home.svelte'; // import ArrowLeftIcon from 'carbon-icons-svelte/lib/ArrowLeft.svelte';
import CheckmarkFilled from 'carbon-icons-svelte/lib/CheckmarkFilled.svelte'; import CheckmarkFilled from 'carbon-icons-svelte/lib/CheckmarkFilled.svelte';
import SettingsIcon from 'carbon-icons-svelte/lib/Settings.svelte'; // import SettingsIcon from 'carbon-icons-svelte/lib/Settings.svelte';
import ChevronLeftIcon from 'carbon-icons-svelte/lib/ChevronLeft.svelte'; import ChevronLeftIcon from 'carbon-icons-svelte/lib/ChevronLeft.svelte';
import ChevronRightIcon from 'carbon-icons-svelte/lib/ChevronRight.svelte'; import ChevronRightIcon from 'carbon-icons-svelte/lib/ChevronRight.svelte';
import NavExpandable from './NavExpandable.svelte'; import NavExpandable from './NavExpandable.svelte';
import PageNav from '$lib/components/PageNav/index.svelte'; import PageNav from '$lib/components/PageNav/index.svelte';
import IconButton from '$lib/components/IconButton/index.svelte'; import IconButton from '$lib/components/IconButton/index.svelte';
import Avatar from '$lib/components/Avatar/index.svelte'; // import Avatar from '$lib/components/Avatar/index.svelte';
import { getNavItemRoute, getLessonsRoute, getLectureNo } from '$lib/components/Course/function'; import { getNavItemRoute, getLessonsRoute, getLectureNo } from '$lib/components/Course/function';
import TextChip from '$lib/components/Chip/Text.svelte'; import TextChip from '$lib/components/Chip/Text.svelte';
import RoleBasedSecurity from '$lib/components/RoleBasedSecurity/index.svelte'; // import RoleBasedSecurity from '$lib/components/RoleBasedSecurity/index.svelte';
import Settings from '../Settings/index.svelte'; import Settings from '../Settings/index.svelte';
import { settingsDialog } from '../Settings/store'; // import { settingsDialog } from '../Settings/store';
import { lessons } from '../Lesson/store/lessons'; import { lessons } from '../Lesson/store/lessons';
import { course } from '$lib/components/Course/store'; import { course } from '$lib/components/Course/store';
import { updateCourse } from '$lib/utils/services/courses'; import { updateCourse } from '$lib/utils/services/courses';
@@ -30,8 +30,10 @@
// export let lessonId; // export let lessonId;
export let path; export let path;
export let isStudent = null; export let isStudent = null;
let show = null; let show = null;
let activeClass = 'bg-gray-200 dark:bg-gray-700'; let activeClass = 'bg-gray-200 dark:bg-gray-500';
let isLessonActive = false;
function handleMainGroupClick(href) { function handleMainGroupClick(href) {
return () => { return () => {
@@ -85,6 +87,8 @@
} }
} }
$: isLessonActive = $page.url.pathname.includes('/lessons');
$: { $: {
navItems = [ navItems = [
{ {
@@ -148,9 +152,17 @@
<div class="root z-10 {!show && 'hide'} {$isMobile ? 'fixed shadow-xl' : 'sticky'}"> <div class="root z-10 {!show && 'hide'} {$isMobile ? 'fixed shadow-xl' : 'sticky'}">
{#if show} {#if show}
<div class="relative h-full dark:bg-gray-700"> <div class="relative h-full dark:bg-gray-700">
<PageNav bind:title={$course.title} paddingClass="pl-2"> <!-- <PageNav title="Back to courses" paddingClass="pl-2" titleSize="text-md">
<slot:fragment slot="image"> <slot:fragment slot="image">
<Avatar src={$course.logo} /> <Avatar src={$course.logo} />
<IconButton
value="toggle"
onClick={() => goto(`${$currentOrgPath}/courses`)}
size="small"
contained={true}
>
<ArrowLeftIcon size={20} class="carbon-icon dark:text-white" />
</IconButton>
</slot:fragment> </slot:fragment>
<slot:fragment slot="widget"> <slot:fragment slot="widget">
<RoleBasedSecurity allowedRoles={[1, 2]}> <RoleBasedSecurity allowedRoles={[1, 2]}>
@@ -159,19 +171,22 @@
</IconButton> </IconButton>
</RoleBasedSecurity> </RoleBasedSecurity>
</slot:fragment> </slot:fragment>
</PageNav> </PageNav> -->
<div class="h-[82%] overflow-y-auto"> <div class="h-[90%] mt-5 overflow-y-auto">
{#each navItems as navItem} {#each navItems as navItem}
{#if !navItem.show || (typeof navItem.show === 'function' && navItem.show())} {#if !navItem.show || (typeof navItem.show === 'function' && navItem.show())}
<NavExpandable <NavExpandable
label={navItem.label} label={navItem.label}
handleClick={handleMainGroupClick(navItem.to)} handleClick={handleMainGroupClick(navItem.to)}
isGroupActive={(path || $page.url.pathname) === navItem.to} isGroupActive={(path || $page.url.pathname) === navItem.to}
isExpanded={isLessonActive}
total={navItem.isLecture ? ($lessons || []).length : 0}
> >
{#if navItem.isLecture} {#if navItem.isLecture}
{#each $lessons as item, index} {#each $lessons as item, index}
<a <a
class="pl-3 font-bold w-[95%] text-sm mb-1 {isStudent && !item.is_unlocked class="pl-7 w-[95%] text-[0.80rem] mb-1 text-black dark:text-white {isStudent &&
!item.is_unlocked
? 'cursor-not-allowed' ? 'cursor-not-allowed'
: ''}" : ''}"
href={isStudent && !item.is_unlocked href={isStudent && !item.is_unlocked
@@ -191,17 +206,17 @@
> >
<TextChip <TextChip
value={getLectureNo(index + 1)} value={getLectureNo(index + 1)}
className="bg-blue-200 text-xs mr-2" className="bg-blue-200 dark:text-black text-xs mr-2"
size="sm" size="sm"
shape="rounded-full" shape="rounded-full"
/> />
<span>{item.title}</span> <span>{item.title}</span>
{#if !item.is_unlocked} {#if !item.is_unlocked}
<span class="text-md ml-2"> <span class="text-md ml-2" title="This lesson is locked.">
<LockedIcon class="carbon-icon dark:text-white" /> <LockedIcon class="carbon-icon dark:text-white" />
</span> </span>
{:else if item.is_complete} {:else if item.is_complete}
<span class="ml-2"> <span class="ml-2" title="You have completed this lesson">
<CheckmarkFilled class="carbon-icon dark:text-white" /> <CheckmarkFilled class="carbon-icon dark:text-white" />
</span> </span>
{/if} {/if}
@@ -213,19 +228,6 @@
{/if} {/if}
{/each} {/each}
</div> </div>
<div class="w-full h-[50px] p-3 absolute bottom-2 flex items-center justify-between">
<IconButton
value="toggle"
onClick={() => goto(`${$currentOrgPath}/courses`)}
buttonClassName="mx-3"
>
<HomeIcon size={32} class="carbon-icon dark:text-white" />
</IconButton>
<a class="block mx-3" href={`/profile/${$profile.id}`}>
<Avatar src={$profile.avatar_url} name={$profile.username} />
</a>
</div>
</div> </div>
{/if} {/if}
<div class="toggler rounded-full shadow-lg absolute bottom-0"> <div class="toggler rounded-full shadow-lg absolute bottom-0">
@@ -253,7 +255,7 @@
<style lang="scss"> <style lang="scss">
.root { .root {
height: 100vh; height: calc(100vh - 44px);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-width: 360px; min-width: 360px;
@@ -28,14 +28,14 @@
export let courseData: Course = { export let courseData: Course = {
id: '', id: '',
title: '', title: '',
description: '', description: ''
}; };
const ratingsImg = [ const ratingsImg = [
'/images/rating-1.svg', '/images/rating-1.svg',
'/images/rating-2.svg', '/images/rating-2.svg',
'/images/rating-3.svg', '/images/rating-3.svg',
'/images/rating-4.svg', '/images/rating-4.svg',
'/images/rating-5.svg', '/images/rating-5.svg'
]; ];
let lessons: Array<Lesson> = []; let lessons: Array<Lesson> = [];
@@ -94,10 +94,7 @@
$: initPlyr(player, video); $: initPlyr(player, video);
$: { $: {
reviews = get(courseData, 'metadata.reviews') || []; reviews = get(courseData, 'metadata.reviews') || [];
totalRatings = reviews?.reduce( totalRatings = reviews?.reduce((acc = 0, review) => acc + (review?.rating || 0), 0);
(acc = 0, review) => acc + (review?.rating || 0),
0
);
averageRating = totalRatings / reviews?.length; averageRating = totalRatings / reviews?.length;
} }
</script> </script>
@@ -109,9 +106,7 @@
<div class="w-full bg-white dark:bg-gray-800 flex flex-col items-center"> <div class="w-full bg-white dark:bg-gray-800 flex flex-col items-center">
<!-- Header Section --> <!-- Header Section -->
<header id="header" class="banner w-full flex items-center justify-center p-"> <header id="header" class="banner w-full flex items-center justify-center p-">
<div <div class="md:w-5/6 w-full flex items-center justify-between flex-col-reverse md:flex-row">
class="md:w-5/6 w-full flex items-center justify-between flex-col-reverse md:flex-row"
>
<!-- Course Description --> <!-- Course Description -->
<div class="md:w-2/5 w-11/12 py-10"> <div class="md:w-2/5 w-11/12 py-10">
<h1 class="dark:text-white text-5xl text-white my-4 font-bold"> <h1 class="dark:text-white text-5xl text-white my-4 font-bold">
@@ -124,10 +119,7 @@
<p class="dark:text-white author my-3 text-sm"> <p class="dark:text-white author my-3 text-sm">
{get(courseData, 'metadata.instructor.name', '')} {get(courseData, 'metadata.instructor.name', '')}
</p> </p>
<PrimaryButton <PrimaryButton label="Start Course" className="px-6 py-5 mt-6 sm:w-fit" />
label="Start Course"
className="px-6 py-5 mt-6 sm:w-fit"
/>
</div> </div>
<!-- Banner Image getEmbedId(videoUrl) --> <!-- Banner Image getEmbedId(videoUrl) -->
@@ -184,7 +176,7 @@
<a <a
href="{$page.url.pathname}{navItem.key}" href="{$page.url.pathname}{navItem.key}"
class="{navItem.key === activeNav && class="{navItem.key === activeNav &&
'active text-blue-700'} rounded-lg px-2 mr-6 text-slate-700 font-normal hover:bg-slate-100 hover:text-slate-900" 'active text-blue-700'} rounded-lg px-2 mr-6 text-slate-700 font-normal hover:bg-slate-100 hover:text-slate-900 dark:text-white"
> >
{navItem.label} {navItem.label}
</a> </a>
@@ -219,13 +211,10 @@
<section class="border-b border-gray-300 mt-8 pb-10"> <section class="border-b border-gray-300 mt-8 pb-10">
<h3 class="text-2xl font-bold mt-0">Certificate</h3> <h3 class="text-2xl font-bold mt-0">Certificate</h3>
<p class="dark:text-white text-sm font-light mb-3"> <p class="dark:text-white text-sm font-light mb-3">
When you complete all of the courses in the program, you'll earn a When you complete all of the courses in the program, you'll earn a Certificate to share
Certificate to share with your professional network. with your professional network.
</p> </p>
<img <img src="/images/certificate-template.svg" alt="certificate template" />
src="/images/certificate-template.svg"
alt="certificate template"
/>
</section> </section>
<!-- Sections - Lessons --> <!-- Sections - Lessons -->
@@ -238,14 +227,9 @@
</p> </p>
</div> </div>
{#each lessons as lesson, index} {#each lessons as lesson, index}
<div <div class="w-full flex md:items-center justify-between mb-6 flex-col md:flex-row">
class="w-full flex md:items-center justify-between mb-6 flex-col md:flex-row"
>
<div class="flex items-center mb-3"> <div class="flex items-center mb-3">
<Chip <Chip value={getLectureNo(index + 1, '0')} className="bg-blue-100 text-blue-700" />
value={getLectureNo(index + 1, '0')}
className="bg-blue-100 text-blue-700"
/>
<p class="dark:text-white ml-2 text-sm font-light"> <p class="dark:text-white ml-2 text-sm font-light">
{lesson.title} {lesson.title}
</p> </p>
@@ -296,11 +280,7 @@
<div class="flex flex-row item-start w-2/4 my-2"> <div class="flex flex-row item-start w-2/4 my-2">
<!-- image container --> <!-- image container -->
{#if review.avatar_url} {#if review.avatar_url}
<Avatar <Avatar src={review.avatar_url} name="Avatar" className="mt-1" />
src={review.avatar_url}
name="Avatar"
className="mt-1"
/>
{/if} {/if}
<!-- profile content --> <!-- profile content -->
@@ -309,18 +289,12 @@
<!-- ratings --> <!-- ratings -->
<div class="flex flex-row items-center"> <div class="flex flex-row items-center">
{#if review.rating} {#if review.rating}
<img <img src={ratingsImg[review.rating - 1]} class="w-24 mr-4 mt-1" alt="" />
src={ratingsImg[review.rating - 1]}
class="w-24 mr-4 mt-1"
alt=""
/>
{/if} {/if}
</div> </div>
<div <div
class="read-more-content mb-2" class="read-more-content mb-2"
style="max-height: {expandDescription[id] style="max-height: {expandDescription[id] ? 'none' : '50px'}"
? 'none'
: '50px'}"
> >
<p class="text-sm my-2 leading-5 text-gray-600"> <p class="text-sm my-2 leading-5 text-gray-600">
{review.description} {review.description}
@@ -329,15 +303,13 @@
{#if !expandDescription[id] && review.description.split(' ').length > 9} {#if !expandDescription[id] && review.description.split(' ').length > 9}
<button <button
class="underline text-blue-700 mt-2 font-normal" class="underline text-blue-700 mt-2 font-normal"
on:click={() => toggleDescription(id)} on:click={() => toggleDescription(id)}>See More</button
>See More</button
> >
{/if} {/if}
{#if expandDescription[id]} {#if expandDescription[id]}
<button <button
class="underline text-blue-700 mt-2 font-normal" class="underline text-blue-700 mt-2 font-normal"
on:click={() => toggleDescription(id)} on:click={() => toggleDescription(id)}>See Less</button
>See Less</button
> >
{/if} {/if}
</div> </div>
@@ -378,11 +350,7 @@
<div class="flex flex-row item-start w-full my-2"> <div class="flex flex-row item-start w-full my-2">
<!-- image container --> <!-- image container -->
{#if review.avatar_url} {#if review.avatar_url}
<Avatar <Avatar src={review.avatar_url} name={review.name} className="mt-1" />
src={review.avatar_url}
name={review.name}
className="mt-1"
/>
{/if} {/if}
<!-- profile content --> <!-- profile content -->
@@ -390,20 +358,14 @@
<p class="mb-0.5 font-medium">{review.name}</p> <p class="mb-0.5 font-medium">{review.name}</p>
<!-- ratings --> <!-- ratings -->
<div class="flex flex-row"> <div class="flex flex-row">
<img <img src="/images/rating-full.svg" alt="" class="mr-2" />
src="/images/rating-full.svg"
alt=""
class="mr-2"
/>
<p class="text-xs text-gray-600"> <p class="text-xs text-gray-600">
{dayjs(review.created_at).fromNow(true)} ago {dayjs(review.created_at).fromNow(true)} ago
</p> </p>
</div> </div>
<div <div
class="read-more-content mb-2" class="read-more-content mb-2"
style="max-height: {expandDescription[id] style="max-height: {expandDescription[id] ? 'none' : '50px'}"
? 'none'
: '50px'}"
> >
<p class="text-sm my-2 leading-5 text-gray-600"> <p class="text-sm my-2 leading-5 text-gray-600">
{review.description} {review.description}
@@ -436,9 +398,7 @@
</p> </p>
<p class="dark:text-white text-md font-light flex items-center"> <p class="dark:text-white text-md font-light flex items-center">
<PlayFilled size={16} class="text-blue-700" /> <PlayFilled size={16} class="text-blue-700" />
<span class="ml-1" <span class="ml-1">{get(instructor, 'courseNo', '')} courses</span>
>{get(instructor, 'courseNo', '')} courses</span
>
</p> </p>
</div> </div>
</div> </div>
+1 -1
View File
@@ -45,7 +45,7 @@
bind:this={textareaRef} bind:this={textareaRef}
{placeholder} {placeholder}
style="height: {textAreaHeight}" style="height: {textAreaHeight}"
class="border border-gray rounded-md p-2" class="border border-gray rounded-md p-2 dark:bg-gray-500"
on:change={onInputChange} on:change={onInputChange}
/> />
{:else} {:else}
+2 -3
View File
@@ -21,9 +21,8 @@
</script> </script>
<div {id} class="w-full bg-white dark:bg-gray-800 {rootClass}"> <div {id} class="w-full bg-white dark:bg-gray-800 {rootClass}">
<div <button
class="w-full relative flex items-center justify-between {!disableContainerPadding && 'p-5'}" class="w-full relative flex items-center justify-between {!disableContainerPadding && 'p-5'}"
role="button"
tabindex="0" tabindex="0"
on:click={handleClick} on:click={handleClick}
> >
@@ -41,7 +40,7 @@
{:else} {:else}
<ChevronDownIcon size={24} class="carbon-icon dark:text-white" /> <ChevronDownIcon size={24} class="carbon-icon dark:text-white" />
{/if} {/if}
</div> </button>
{#if isExpanded} {#if isExpanded}
<div class="flex flex-col p-5"> <div class="flex flex-col p-5">
<slot /> <slot />
+1 -1
View File
@@ -38,7 +38,7 @@
{placeholder} {placeholder}
bind:value bind:value
required={isRequired} required={isRequired}
class="form-textarea border-l-0 border-r-0 border-t-0 border-b-2 border-gray-200 focus:border-l-0 focus:border-r-0 rounded-t-md focus:border-t-0 focus:border-b-2 focus:border-blue-600 mt-1 block w-full class="form-textarea dark:bg-gray-500 border-l-0 border-r-0 border-t-0 border-b-2 border-gray-200 focus:border-l-0 focus:border-r-0 rounded-t-md focus:border-t-0 focus:border-b-2 focus:border-blue-600 mt-1 block w-full
{bgColor} {!!errorMessage ? 'border-red-500' : 'border-gray-300'}" {bgColor} {!!errorMessage ? 'border-red-500' : 'border-gray-300'}"
on:change={onChange} on:change={onChange}
/> />
+1 -1
View File
@@ -11,7 +11,7 @@
export let className = ''; export let className = '';
export let inputClassName = ''; export let inputClassName = '';
export let labelClassName = ''; export let labelClassName = '';
export let bgColor = 'bg-gray-50 focus:bg-blue-50 dark:bg-white'; export let bgColor = 'bg-gray-100 focus:bg-blue-50 dark:bg-white';
export let type = 'text'; export let type = 'text';
export let isPassword = false; export let isPassword = false;
export let autoFocus = false; export let autoFocus = false;
+7 -4
View File
@@ -1,6 +1,7 @@
<script> <script>
import ToolTip from '../ToolTip/index.svelte'; import ToolTip from '../ToolTip/index.svelte';
export let onClick = () => {}; export let onClick = () => {};
export let stopPropagation = false;
export let disabled = false; export let disabled = false;
export let selected = false; export let selected = false;
export let buttonClassName = ''; export let buttonClassName = '';
@@ -12,10 +13,13 @@
export let toolTipProps = { export let toolTipProps = {
title: '', title: '',
hotkeys: [], hotkeys: [],
direction: '', direction: ''
}; };
function handleClick() { function handleClick(e) {
if (stopPropagation) {
e.stopPropagation();
}
onClick(value); onClick(value);
} }
</script> </script>
@@ -26,8 +30,7 @@
direction={toolTipProps.direction} direction={toolTipProps.direction}
> >
<button <button
class="root {color} {selected && 'active'} {size} {contained && class="root {color} {selected && 'active'} {size} {contained && 'contained'} {buttonClassName}"
'contained'} {buttonClassName}"
{disabled} {disabled}
{type} {type}
on:click={handleClick} on:click={handleClick}
@@ -30,7 +30,7 @@
<textarea <textarea
bind:value bind:value
{placeholder} {placeholder}
class="border border-gray rounded-md p-2" class="border border-gray rounded-md p-2 dark:bg-gray-500"
/> />
{:else} {:else}
<article class="m-auto preview prose prose-sm sm:prose p-2"> <article class="m-auto preview prose prose-sm sm:prose p-2">
+18 -2
View File
@@ -7,9 +7,12 @@
import IconButton from '$lib/components/IconButton/index.svelte'; import IconButton from '$lib/components/IconButton/index.svelte';
import { appStore } from '$lib/utils/store/app'; import { appStore } from '$lib/utils/store/app';
import { currentOrgPath } from '$lib/utils/store/org';
import { toggleBodyByTheme } from '$lib/utils/functions/app'; import { toggleBodyByTheme } from '$lib/utils/functions/app';
export let title = '';
export let navClass = ''; export let navClass = '';
export let isCoursePage = false;
function toggleTheme() { function toggleTheme() {
$appStore.isDark = !$appStore.isDark; $appStore.isDark = !$appStore.isDark;
@@ -20,13 +23,26 @@
localStorage.setItem('theme', $appStore.isDark ? 'dark' : ''); localStorage.setItem('theme', $appStore.isDark ? 'dark' : '');
} }
} }
function getPathName(_isCoursePage) {
if (!_isCoursePage) {
return $currentOrgPath;
}
return `${$currentOrgPath}/courses`;
}
</script> </script>
<nav class="{navClass} flex w-full p-2 md:px-6 bg-blue-600"> <nav class="{navClass} flex w-full p-2 md:px-6 bg-blue-600">
<ul class="flex w-full items-center"> <ul class="flex w-full items-center">
<div class=""> <div class="">
<a href={$page.url.pathname} title="Go to ClassroomIO Home" id="logo" class="text-lg"> <a
ClassroomIO href={getPathName(isCoursePage)}
title="Go to {isCoursePage ? 'Courses' : 'ClassroomIO Home'}"
id="logo"
class="text-lg"
>
{isCoursePage ? title : 'ClassroomIO'}
</a> </a>
</div> </div>
+3 -3
View File
@@ -63,8 +63,8 @@
<div class="flex"> <div class="flex">
<PrimaryButton <PrimaryButton
label="Login" label="Login"
variant={VARIANTS.NONE} variant={VARIANTS.OUTLINED}
className="py-3 px-6 mx-2 app-background-color" className="mx-2"
onClick={() => { onClick={() => {
console.log('clicked login'); console.log('clicked login');
goto('/login' + redirect); goto('/login' + redirect);
@@ -73,7 +73,7 @@
<PrimaryButton <PrimaryButton
label="Sign Up" label="Sign Up"
variant={VARIANTS.CONTAINED} variant={VARIANTS.CONTAINED}
className="py-3 px-6 mx-2" className="mx-2"
onClick={() => { onClick={() => {
console.log('clicked signup'); console.log('clicked signup');
goto('/signup' + redirect); goto('/signup' + redirect);
+14 -42
View File
@@ -26,23 +26,19 @@
const updates = { const updates = {
fullname: $profile.fullname, fullname: $profile.fullname,
username: $profile.username, username: $profile.username
}; };
if (avatar) { if (avatar) {
const filename = `user/${$profile.username + Date.now()}.webp`; const filename = `user/${$profile.username + Date.now()}.webp`;
const { data } = await supabase.storage const { data } = await supabase.storage.from('avatars').upload(filename, avatar, {
.from('avatars') cacheControl: '3600',
.upload(filename, avatar, { upsert: false
cacheControl: '3600', });
upsert: false,
});
if (data && data.Key) { if (data && data.Key) {
const { publicURL } = supabase.storage const { publicURL } = supabase.storage.from('avatars').getPublicUrl(filename);
.from('avatars')
.getPublicUrl(filename);
updates.avatar_url = publicURL; updates.avatar_url = publicURL;
$profile.avatar_url = publicURL; $profile.avatar_url = publicURL;
@@ -50,14 +46,11 @@
avatar = undefined; avatar = undefined;
} }
let { error } = await supabase let { error } = await supabase.from('profile').update(updates).match({ id: $profile.id });
.from('profile')
.update(updates)
.match({ id: $profile.id });
profile.update((_profile) => ({ profile.update((_profile) => ({
..._profile, ..._profile,
...updates, ...updates
})); }));
$snackbarStore.open = true; $snackbarStore.open = true;
$snackbarStore.message = 'Update successful'; $snackbarStore.message = 'Update successful';
@@ -85,11 +78,7 @@
<SectionTitle>Profile Picture</SectionTitle> <SectionTitle>Profile Picture</SectionTitle>
</Column> </Column>
<Column sm={2} md={6} lg={8}> <Column sm={2} md={6} lg={8}>
<UploadImage <UploadImage bind:avatar src={$profile.avatar_url} widthHeight="w-24 h-24" />
bind:avatar
src={$profile.avatar_url}
widthHeight="w-24 h-24"
/>
</Column> </Column>
</Row> </Row>
<Row class="py-7 border-bottom-c"> <Row class="py-7 border-bottom-c">
@@ -97,36 +86,19 @@
<SectionTitle>Personal Information</SectionTitle> <SectionTitle>Personal Information</SectionTitle>
</Column> </Column>
<Column sm={2} md={6} lg={8}> <Column sm={2} md={6} lg={8}>
<TextField <TextField label="Full Name" bind:value={$profile.fullname} className="w-60 mb-4" />
label="Full Name" <TextField label="Username" bind:value={$profile.username} className="w-60 mb-4" />
bind:value={$profile.fullname} <TextField label="Email" bind:value={$profile.email} className="w-60 mb-4" />
className="w-60 mb-4"
/>
<TextField
label="Username"
bind:value={$profile.username}
className="w-60 mb-4"
/>
<TextField
label="Email"
bind:value={$profile.email}
className="w-60 mb-4"
/>
</Column> </Column>
</Row> </Row>
<Row class="m-5 w-full flex items-center justify-center"> <Row class="m-5 w-full flex items-center justify-center">
<PrimaryButton <PrimaryButton
label="Update Profile" label="Update Profile"
className="px-6 py-3 mr-5" className="mr-5"
isLoading={loading} isLoading={loading}
isDisabled={loading} isDisabled={loading}
onClick={handleUpdate} onClick={handleUpdate}
/> />
<PrimaryButton <PrimaryButton variant={VARIANTS.CONTAINED_DANGER} label="Log out" onClick={logout} />
className="px-6 py-3"
variant={VARIANTS.CONTAINED_DANGER}
label="Log out"
onClick={logout}
/>
</Row> </Row>
</Grid> </Grid>
+19 -33
View File
@@ -2,6 +2,7 @@
import NewButton from '../PrimaryContainedButton/index.svelte'; import NewButton from '../PrimaryContainedButton/index.svelte';
import TextField from '../Form/TextField.svelte'; import TextField from '../Form/TextField.svelte';
import Chip from '../Chip/index.svelte'; import Chip from '../Chip/index.svelte';
export let title = ''; export let title = '';
export let overidableStyle = ''; export let overidableStyle = '';
export let navItems = []; export let navItems = [];
@@ -15,8 +16,7 @@
let dynamicRootClass = ''; let dynamicRootClass = '';
let enterEditTitleMode = false; let enterEditTitleMode = false;
$: dynamicRootClass = $: dynamicRootClass = Array.isArray(navItems) && navItems.length > 4 ? 'bring-down' : '';
Array.isArray(navItems) && navItems.length > 4 ? 'bring-down' : '';
</script> </script>
<div <div
@@ -27,13 +27,13 @@
{#if !!title} {#if !!title}
{#if isTitleEditable} {#if isTitleEditable}
{#if !enterEditTitleMode} {#if !enterEditTitleMode}
<h4 <button class="w-full" on:click={() => (enterEditTitleMode = true)}>
class="title editable-title hover:bg-gray-100 dark:bg-gray-700 px-3 rounded-md overflow-ellipsis" <h4
on:click={() => (enterEditTitleMode = true)} class="editable-title hover:bg-gray-100 dark:bg-gray-700 px-3 rounded-md overflow-ellipsis"
on:keydown={() => (enterEditTitleMode = true)} >
> {title}
{title} </h4>
</h4> </button>
{:else} {:else}
<TextField <TextField
bind:value={title} bind:value={title}
@@ -48,23 +48,26 @@
/> />
{/if} {/if}
{:else} {:else}
<div class="flex items-center" {title}> <div class="flex items-center w-full" {title}>
<slot name="image" /> <slot name="image" />
<h4 class="dark:text-white title">{title}</h4> <h4
class="{$$slots.image
? 'ml-2'
: ''} dark:text-white whitespace-nowrap truncate text-lg font-semibold"
>
{title}
</h4>
</div> </div>
{/if} {/if}
{/if} {/if}
{#if Array.isArray(navItems) && navItems.length} {#if Array.isArray(navItems) && navItems.length}
<div class="flex justify-evenly items-center"> <div class="flex justify-evenly items-center">
{#each navItems as item} {#each navItems as item}
<a <a class="mr-5 text-sm flex items-center {item.isActive && 'active'}" href={item.href}>
class="mr-5 text-sm flex items-center {item.isActive && 'active'}"
href={item.href}
>
{item.label} {item.label}
{#if typeof item.badgeValue === 'number'} {#if typeof item.badgeValue === 'number'}
<Chip value={item.badgeValue} className="ml-1 bg-gray-300" /> <Chip value={item.badgeValue} className="ml-1 bg-gray-300 dark:text-black" />
{/if} {/if}
</a> </a>
{/each} {/each}
@@ -88,23 +91,6 @@
top: 0; top: 0;
} }
.title {
font-size: 18px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
width: 250px;
margin-left: 5px;
}
h4 {
font-size: 16px;
font-weight: 900;
word-break: break-word;
overflow-wrap: break-word;
margin: 0;
}
.active { .active {
position: relative; position: relative;
display: inline-block; display: inline-block;
@@ -7,15 +7,14 @@ export const VARIANTS = {
OUTLINED: 'OUTLINED', OUTLINED: 'OUTLINED',
NONE: 'NONE', NONE: 'NONE',
TEXT: 'TEXT', TEXT: 'TEXT',
TEXT_DANGER: 'TEXT_DANGER', TEXT_DANGER: 'TEXT_DANGER'
}; };
export const VARIANTS_CLASS = { export const VARIANTS_CLASS = {
[VARIANTS.CONTAINED]: 'border-none bg-blue-700 hover:bg-blue-900 text-white', [VARIANTS.CONTAINED]: 'border-none bg-blue-700 hover:bg-blue-900 text-white font-bold',
[VARIANTS.CONTAINED_WHITE]: [VARIANTS.CONTAINED_WHITE]:
'border-none hover:border-black-300 hover:bg-blue-700 dark:hover:bg-blue-900 text-black hover:text-white bg-white', 'border-none hover:border-black-300 hover:bg-blue-700 dark:hover:bg-blue-900 text-black hover:text-white bg-white',
[VARIANTS.CONTAINED_INFO]: [VARIANTS.CONTAINED_INFO]: 'border-none bg-gray-400 hover:bg-gray-600 text-white',
'border-none bg-gray-400 hover:bg-gray-600 text-white',
[VARIANTS.CONTAINED_SUCCESS]: 'bg-green-700 hover:bg-green-900 text-white', [VARIANTS.CONTAINED_SUCCESS]: 'bg-green-700 hover:bg-green-900 text-white',
[VARIANTS.CONTAINED_DANGER]: 'bg-red-700 hover:bg-red-900 text-white', [VARIANTS.CONTAINED_DANGER]: 'bg-red-700 hover:bg-red-900 text-white',
[VARIANTS.OUTLINED]: [VARIANTS.OUTLINED]:
@@ -24,5 +23,5 @@ export const VARIANTS_CLASS = {
'border-none hover:border-black-300 hover:bg-gray-200 dark:hover:bg-gray-500 text-black', 'border-none hover:border-black-300 hover:bg-gray-200 dark:hover:bg-gray-500 text-black',
[VARIANTS.TEXT]: 'text-black hover:underline', [VARIANTS.TEXT]: 'text-black hover:underline',
[VARIANTS.TEXT_DANGER]: [VARIANTS.TEXT_DANGER]:
'text-red-500 hover:border border-t-0 border-l-0 border-r-0 border-red-300', 'text-red-500 hover:border border-t-0 border-l-0 border-r-0 border-red-300'
}; };
@@ -21,8 +21,8 @@
: 'cursor-pointer'} flex items-center h-auto {VARIANTS_CLASS[ : 'cursor-pointer'} flex items-center h-auto {VARIANTS_CLASS[
isLoading ? VARIANTS.OUTLINED : variant isLoading ? VARIANTS.OUTLINED : variant
]} {!disablePadding && ]} {!disablePadding &&
'py-3 px-5'} rounded-none {className} w-fit justify-center sm:w-auto {isLoading && 'py-2 px-6'} rounded-md {className} w-fit justify-center sm:w-auto {isLoading &&
'py-3 border-blue-700'} {variant !== VARIANTS.TEXT && 'py-2 border-blue-700'} {variant !== VARIANTS.TEXT &&
'hover:shadow-xl'} transition delay-150 duration-300 ease-in-out {variant !== 'hover:shadow-xl'} transition delay-150 duration-300 ease-in-out {variant !==
VARIANTS.CONTAINED_WHITE VARIANTS.CONTAINED_WHITE
? 'dark:text-white' ? 'dark:text-white'
+1 -1
View File
@@ -17,7 +17,7 @@
> >
{tab.label} {tab.label}
{#if typeof tab.badgeValue === 'number'} {#if typeof tab.badgeValue === 'number'}
<Chip value={tab.badgeValue} className="ml-1 bg-gray-300" /> <Chip value={tab.badgeValue} className="ml-1 bg-gray-300 dark:text-black" />
{/if} {/if}
{#if currentTab === tab.value} {#if currentTab === tab.value}
+2 -2
View File
@@ -38,14 +38,14 @@
{/if} {/if}
</div> </div>
<p <button
class="width-fit text-blue-700 flex items-center cursor-pointer text-sm mt-3" class="width-fit text-blue-700 flex items-center cursor-pointer text-sm mt-3"
on:click={() => { on:click={() => {
fileinput.click(); fileinput.click();
}} }}
> >
<Camera size={20} /> <span class="ml-2">Upload Image</span> <Camera size={20} /> <span class="ml-2">Upload Image</span>
</p> </button>
{#if errorMessage} {#if errorMessage}
<p class="text-sm text-red-500">{errorMessage}</p> <p class="text-sm text-red-500">{errorMessage}</p>
{/if} {/if}
+5 -12
View File
@@ -1,7 +1,4 @@
import { import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public';
PUBLIC_SUPABASE_ANON_KEY,
PUBLIC_SUPABASE_URL,
} from '$env/static/public';
import { createClient, SupabaseClient } from '@supabase/supabase-js'; import { createClient, SupabaseClient } from '@supabase/supabase-js';
import type { ConfigType } from '../types'; import type { ConfigType } from '../types';
@@ -12,17 +9,13 @@ export const getSupabase = (config?: ConfigType) => {
config = { config = {
supabaseConfig: { supabaseConfig: {
bucketPath: bucketPath: 'https://koxqonvbkeakwvmdegcf.supabase.in/storage/v1/object/sign/',
'https://koxqonvbkeakwvmdegcf.supabase.in/storage/v1/object/sign/',
url: PUBLIC_SUPABASE_URL || '', url: PUBLIC_SUPABASE_URL || '',
anonKey: PUBLIC_SUPABASE_ANON_KEY || '', anonKey: PUBLIC_SUPABASE_ANON_KEY || ''
}, }
}; };
supabase = createClient( supabase = createClient(config.supabaseConfig.url, config.supabaseConfig.anonKey);
config.supabaseConfig.url,
config.supabaseConfig.anonKey
);
return supabase; return supabase;
}; };
+16 -14
View File
@@ -22,6 +22,7 @@
// import Footer from '$lib/components/Footer/index.svelte'; // import Footer from '$lib/components/Footer/index.svelte';
import Apps from '$lib/components/Apps/index.svelte'; import Apps from '$lib/components/Apps/index.svelte';
import PlayQuiz from '$lib/components/Org/Quiz/Play/index.svelte'; import PlayQuiz from '$lib/components/Org/Quiz/Play/index.svelte';
import { course } from '$lib/components/Course/store';
import { isCoursesPage, isOrgPage, isLMSPage, isQuizPage } from '$lib/utils/functions/app'; import { isCoursesPage, isOrgPage, isLMSPage, isQuizPage } from '$lib/utils/functions/app';
import showAppsSideBar from '$lib/utils/functions/showAppsSideBar'; import showAppsSideBar from '$lib/utils/functions/showAppsSideBar';
import isPublicRoute from '$lib/utils/functions/routes/isPublicRoute'; import isPublicRoute from '$lib/utils/functions/routes/isPublicRoute';
@@ -71,9 +72,13 @@
const { const {
data: { session } data: { session }
} = await supabase.auth.getSession(); } = await supabase.auth.getSession();
const { user: authUser } = session; const { user: authUser } = session || {};
console.log('Get user profile', authUser); console.log('Get user profile', authUser);
if (!authUser) {
goto('/login');
}
// Check if user has profile // Check if user has profile
let { let {
data: profileData, data: profileData,
@@ -207,16 +212,13 @@
handleResize(); handleResize();
// if ( if (
// !localStorage.getItem('supabase.auth.token') && !localStorage.getItem('sb-koxqonvbkeakwvmdegcf-auth-token') &&
// !isPublicRoute($page.url.pathname) !isPublicRoute($page.url.pathname)
// ) { ) {
// console.log( console.log('No auth token and is not a public route, redirect to login', path);
// 'No auth token and is not a public route, redirect to login', return goto('/login');
// path }
// );
// return goto('/login');
// }
const { data: authListener } = supabase.auth.onAuthStateChange((event, session) => { const { data: authListener } = supabase.auth.onAuthStateChange((event, session) => {
// Log key events // Log key events
@@ -285,9 +287,9 @@
<Moon size="60" color="#1d4ed8" unit="px" duration="1s" /> <Moon size="60" color="#1d4ed8" unit="px" duration="1s" />
</Backdrop> </Backdrop>
{/if} {/if}
{#if !ROUTES_TO_HIDE_NAV.includes($page.url.pathname) && !isCoursesPage(path)} {#if !ROUTES_TO_HIDE_NAV.includes($page.url.pathname)}
{#if isOrgPage($page.url.pathname) || $page.url.pathname.includes('profile')} {#if isOrgPage($page.url.pathname) || $page.url.pathname.includes('profile') || isCoursesPage(path)}
<OrgNavigation /> <OrgNavigation bind:title={$course.title} isCoursePage={isCoursesPage(path)} />
{:else if isLMSPage($page.url.pathname)} {:else if isLMSPage($page.url.pathname)}
<LMSNavigation /> <LMSNavigation />
{:else} {:else}
-1
View File
@@ -79,7 +79,6 @@
type="email" type="email"
placeholder="Enter your email.." placeholder="Enter your email.."
className="mr-3 my-2" className="mr-3 my-2"
inputClassName="rounded-md"
isRequired={true} isRequired={true}
isDisabled={isAdding} isDisabled={isAdding}
/> />
@@ -13,6 +13,8 @@
// import { Tabs, Tab } from 'carbon-components-svelte'; // import { Tabs, Tab } from 'carbon-components-svelte';
// import TabContent from 'carbon-components-svelte/src/Tabs/TabContent.svelte'; // import TabContent from 'carbon-components-svelte/src/Tabs/TabContent.svelte';
import StudentCertificate from '$lib/components/Course/components/Ceritficate/StudentCertificate/Index.svelte'; import StudentCertificate from '$lib/components/Course/components/Ceritficate/StudentCertificate/Index.svelte';
import { fetchCourse } from '$lib/utils/services/courses';
export let courseId: string = ''; export let courseId: string = '';
let isStudent = false; let isStudent = false;
@@ -15,7 +15,7 @@
import { import {
fetchSubmissions, fetchSubmissions,
updateSubmission, updateSubmission,
updateQuestionAnswer, updateQuestionAnswer
} from '$lib/utils/services/submissions'; } from '$lib/utils/services/submissions';
import { formatAnswers } from '$lib/components/Course/function'; import { formatAnswers } from '$lib/components/Course/function';
import { snackbarStore } from '$lib/components/Snackbar/store'; import { snackbarStore } from '$lib/components/Snackbar/store';
@@ -33,20 +33,20 @@
id: 1, id: 1,
title: 'Submitted', title: 'Submitted',
value: 0, value: 0,
items: [], items: []
}, },
{ {
id: 2, id: 2,
title: 'In Progress', title: 'In Progress',
value: 0, value: 0,
items: [], items: []
}, },
{ {
id: 3, id: 3,
title: 'Graded', title: 'Graded',
value: 10, value: 10,
items: [], items: []
}, }
]; ];
let submissionIdData = {}; let submissionIdData = {};
let submissionId; let submissionId;
@@ -73,7 +73,7 @@
// Update key mapping for each submission also // Update key mapping for each submission also
submissionIdData[itemToWithNewStatus.id] = { submissionIdData[itemToWithNewStatus.id] = {
...submissionIdData[itemToWithNewStatus.id], ...submissionIdData[itemToWithNewStatus.id],
status_id: itemToWithNewStatus.statusId, status_id: itemToWithNewStatus.statusId
}; };
console.log( console.log(
`submissionIdData[itemToWithNewStatus.id]`, `submissionIdData[itemToWithNewStatus.id]`,
@@ -82,7 +82,7 @@
updateSubmission({ updateSubmission({
id: itemToWithNewStatus.id, id: itemToWithNewStatus.id,
status_id: itemToWithNewStatus.statusId, status_id: itemToWithNewStatus.statusId
}).then((res) => console.log('Updated submission', res)); }).then((res) => console.log('Updated submission', res));
} }
} }
@@ -118,24 +118,21 @@
}); });
console.log(`itemToWithNewStatus`, itemToWithNewStatus); console.log(`itemToWithNewStatus`, itemToWithNewStatus);
// Move to right column // Move to right column
sections[nextStatusId - 1].items = [ sections[nextStatusId - 1].items = [...sections[nextStatusId - 1].items, itemToWithNewStatus];
...sections[nextStatusId - 1].items,
itemToWithNewStatus,
];
// If something changed // If something changed
if (itemToWithNewStatus) { if (itemToWithNewStatus) {
// Update key mapping for each submission also // Update key mapping for each submission also
submissionIdData[itemToWithNewStatus.id] = { submissionIdData[itemToWithNewStatus.id] = {
...submissionIdData[itemToWithNewStatus.id], ...submissionIdData[itemToWithNewStatus.id],
status_id: itemToWithNewStatus.statusId, status_id: itemToWithNewStatus.statusId
}; };
// Update backend // Update backend
updateSubmission({ updateSubmission({
id: itemToWithNewStatus.id, id: itemToWithNewStatus.id,
status_id: itemToWithNewStatus.statusId, status_id: itemToWithNewStatus.statusId,
total, total
}).then((res) => console.log('Updated submission', res)); }).then((res) => console.log('Updated submission', res));
} }
} }
@@ -146,9 +143,7 @@
let totalPoints = 0; let totalPoints = 0;
const updates = Object.keys(questionAnswerByPoint).map((questionId) => { const updates = Object.keys(questionAnswerByPoint).map((questionId) => {
const questionAnswer = questionAnswers.find( const questionAnswer = questionAnswers.find((answer) => answer.question_id == questionId);
(answer) => answer.question_id == questionId
);
const point = questionAnswerByPoint[questionId]; const point = questionAnswerByPoint[questionId];
@@ -159,7 +154,7 @@
updateSubmission({ updateSubmission({
id: submissionId, id: submissionId,
total: totalPoints, total: totalPoints
}).then((res) => console.log('Updated submission', res)); }).then((res) => console.log('Updated submission', res));
$snackbarStore.open = true; $snackbarStore.open = true;
@@ -175,21 +170,11 @@
setCourse(data); setCourse(data);
} }
const { data: submissions } = await fetchSubmissions( const { data: submissions } = await fetchSubmissions(courseId || $course.id);
courseId || $course.id
);
const sectionById = {}; const sectionById = {};
for (const submission of submissions) { for (const submission of submissions) {
const { const { id, created_at, exercise, course, answers, groupmember, status_id } = submission;
id,
created_at,
exercise,
course,
answers,
groupmember,
status_id,
} = submission;
const isEarly = isSubmissionEarly(created_at, exercise.due_by); const isEarly = isSubmissionEarly(created_at, exercise.due_by);
const submissionItem = { const submissionItem = {
@@ -199,14 +184,14 @@
submittedAt: formatDate(created_at), submittedAt: formatDate(created_at),
exercise: { exercise: {
id: exercise.id, id: exercise.id,
title: exercise.title, title: exercise.title
}, },
answers, answers,
student: groupmember && groupmember.profile ? groupmember.profile : {}, student: groupmember && groupmember.profile ? groupmember.profile : {},
lesson: { lesson: {
id: exercise.lesson.id, id: exercise.lesson.id,
title: exercise.lesson.title, title: exercise.lesson.title
}, }
}; };
submissionIdData[id] = { submissionIdData[id] = {
@@ -222,7 +207,7 @@
acc[answer.question_id] = answer.point; acc[answer.question_id] = answer.point;
return acc; return acc;
}, {}), }, {})
}; };
if (Array.isArray(sectionById[status_id])) { if (Array.isArray(sectionById[status_id])) {
@@ -234,17 +219,13 @@
sections = sections.map((section, index) => ({ sections = sections.map((section, index) => ({
...section, ...section,
value: Array.isArray(sectionById[index + 1]) value: Array.isArray(sectionById[index + 1]) ? sectionById[index + 1].length : 0,
? sectionById[index + 1].length items: Array.isArray(sectionById[index + 1]) ? sectionById[index + 1] : []
: 0,
items: Array.isArray(sectionById[index + 1])
? sectionById[index + 1]
: [],
})); }));
}); });
$: { $: {
const query = new URLSearchParams($page.url.search) const query = new URLSearchParams($page.url.search);
submissionId = query.get('submissionId'); submissionId = query.get('submissionId');
openExercise = !!submissionId && submissionIdData[submissionId]; openExercise = !!submissionId && submissionIdData[submissionId];
} }
@@ -279,7 +260,7 @@
use:dndzone={{ use:dndzone={{
items, items,
flipDurationMs, flipDurationMs,
dropTargetStyle: { outline: 'blue' }, dropTargetStyle: { outline: 'blue' }
}} }}
on:consider={handleDndConsiderCards(idx)} on:consider={handleDndConsiderCards(idx)}
on:finalize={handleDndFinalizeCards(idx)} on:finalize={handleDndFinalizeCards(idx)}
@@ -291,11 +272,9 @@
: 'border-red-700'} w-full my-2 mx-0 rounded-md bg-white dark:bg-gray-800 py-3 px-3" : 'border-red-700'} w-full my-2 mx-0 rounded-md bg-white dark:bg-gray-800 py-3 px-3"
animate:flip={{ duration: flipDurationMs }} animate:flip={{ duration: flipDurationMs }}
> >
<div <a
class="flex items-center cursor-pointer text-black mb-2" class="flex w-full items-center cursor-pointer text-black mb-2"
on:click={() => { href={`${$page.url.pathname}?submissionId=${item.id}`}
goto(`${$page.url.pathname}?submissionId=${item.id}`);
}}
> >
<img <img
alt="Student avatar" alt="Student avatar"
@@ -305,7 +284,7 @@
<p class="dark:text-white ml-2 text-sm"> <p class="dark:text-white ml-2 text-sm">
{item.student.username} {item.student.username}
</p> </p>
</div> </a>
<a <a
class="text-blue-700 text-md font-bold" class="text-blue-700 text-md font-bold"
href="{$page.url.pathname}?submissionId={item.id}" href="{$page.url.pathname}?submissionId={item.id}"
@@ -314,19 +293,15 @@
</a> </a>
<a <a
class="flex items-center no-underline hover:underline text-black my-2" class="flex items-center no-underline hover:underline text-black my-2"
href="{$page.url.pathname.replace( href="{$page.url.pathname.replace('submissions', 'lessons')}/{item.lesson
'submissions', .id}/exercises/{item.exercise.id}"
'lessons'
)}/{item.lesson.id}/exercises/{item.exercise.id}"
> >
<p class="dark:text-white text-grey text-sm"> <p class="dark:text-white text-grey text-sm">
#{item.lesson.title} #{item.lesson.title}
{`${item.tutor ? 'created by' + item.tutor.name : ''}`} {`${item.tutor ? 'created by' + item.tutor.name : ''}`}
</p> </p>
</a> </a>
<p <p class="dark:text-white text-gray-500 dark:text-white text-xs">
class="dark:text-white text-gray-500 dark:text-white text-xs"
>
{item.submittedAt} {item.submittedAt}
</p> </p>
<!-- <div class="badge rounded-md px-2 bg-green-500 text-white"> <!-- <div class="badge rounded-md px-2 bg-green-500 text-white">
@@ -306,10 +306,9 @@
{#if question.author.id === $profile.id} {#if question.author.id === $profile.id}
<PrimaryButton <PrimaryButton
label={isEditMode ? 'Save' : 'Edit'} label={isEditMode ? 'Save' : 'Edit'}
variant={VARIANTS.CONTAINED_INFO} variant={VARIANTS.OUTLINED}
onClick={handleQuestionEdit} onClick={handleQuestionEdit}
className="py-3 px-6 rounded-sm h-fit" className="h-fit"
disablePadding={true}
/> />
{#if isEditMode} {#if isEditMode}
<PrimaryButton <PrimaryButton
+19 -42
View File
@@ -26,7 +26,7 @@
let { let {
data: profileData, data: profileData,
error, error,
status, status
} = await supabase.from('profile').select(`*`).eq('id', profileId).single(); } = await supabase.from('profile').select(`*`).eq('id', profileId).single();
if (error && !profileData && status === 406 && $user.currentSession) { if (error && !profileData && status === 406 && $user.currentSession) {
@@ -47,23 +47,19 @@
const updates = { const updates = {
fullname: currentProfile.fullname, fullname: currentProfile.fullname,
username: currentProfile.username, username: currentProfile.username
}; };
if (avatar) { if (avatar) {
const filename = `user/${currentProfile.username + Date.now()}.webp`; const filename = `user/${currentProfile.username + Date.now()}.webp`;
const { data } = await supabase.storage const { data } = await supabase.storage.from('avatars').upload(filename, avatar, {
.from('avatars') cacheControl: '3600',
.upload(filename, avatar, { upsert: false
cacheControl: '3600', });
upsert: false,
});
if (data && data.Key) { if (data && data.Key) {
const { publicURL } = supabase.storage const { publicURL } = supabase.storage.from('avatars').getPublicUrl(filename);
.from('avatars')
.getPublicUrl(filename);
updates.avatar_url = publicURL; updates.avatar_url = publicURL;
currentProfile.avatar_url = publicURL; currentProfile.avatar_url = publicURL;
@@ -71,15 +67,12 @@
avatar = undefined; avatar = undefined;
} }
let { error } = await supabase let { error } = await supabase.from('profile').update(updates).match({ id: profileId });
.from('profile')
.update(updates)
.match({ id: profileId });
if (isOwner) { if (isOwner) {
profile.update((_profile) => ({ profile.update((_profile) => ({
..._profile, ..._profile,
...updates, ...updates
})); }));
} }
@@ -117,9 +110,7 @@
<title>Profile of {currentProfile.username}</title> <title>Profile of {currentProfile.username}</title>
</svelte:head> </svelte:head>
<section <section class="root w-11/12 mt-3 m-auto flex items-start justify-between flex-col lg:flex-row">
class="root w-11/12 mt-3 m-auto flex items-start justify-between flex-col lg:flex-row"
>
{#if currentProfile.id} {#if currentProfile.id}
<div class="w-full lg:w-1/4 flex items-center flex-col"> <div class="w-full lg:w-1/4 flex items-center flex-col">
<!-- <img <!-- <img
@@ -129,11 +120,7 @@
width="260" width="260"
class="rounded-full bg-light-blue-100 mb-4" class="rounded-full bg-light-blue-100 mb-4"
/> --> /> -->
<UploadImage <UploadImage bind:avatar src={currentProfile.avatar_url} widthHeight="w-60 h-60" />
bind:avatar
src={currentProfile.avatar_url}
widthHeight="w-60 h-60"
/>
{#if isOwner} {#if isOwner}
<TextField <TextField
@@ -172,19 +159,16 @@
<div class="flex-grow flex flex-wrap mx-3 w-3/5"> <div class="flex-grow flex flex-wrap mx-3 w-3/5">
<div class="mb-3 w-full"> <div class="mb-3 w-full">
<h3 class="dark:text-white font-bold text-md m-0">Organizations</h3> <h3 class="dark:text-white font-bold text-md m-0">Organizations</h3>
<p class="dark:text-white text-gray-500 text-sm"> <p class="dark:text-white text-gray-500 text-sm">This are the organizations you work at</p>
This are the organizations you work at
</p>
<div class="hidden my-3 py-3 px-4 border shadow-sm rounded-md"> <div class="hidden my-3 py-3 px-4 border shadow-sm rounded-md">
<h4 class="dark:text-white m-0 font-normal"> <h4 class="dark:text-white m-0 font-normal">
<a class="text-blue-700 font-bold" href="/courses"> CitiBim </a> <a class="text-blue-700 font-bold" href="/courses"> CitiBim </a>
</h4> </h4>
<p class="dark:text-white text-gray-500"> <p class="dark:text-white text-gray-500">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Non aliquam Lorem ipsum dolor sit amet consectetur adipisicing elit. Non aliquam tenetur magnam.
tenetur magnam. Alias cumque quaerat expedita dolores placeat Alias cumque quaerat expedita dolores placeat laboriosam culpa, suscipit veritatis
laboriosam culpa, suscipit veritatis adipisci eius magni obcaecati. adipisci eius magni obcaecati. Sequi doloremque blanditiis earum.
Sequi doloremque blanditiis earum.
</p> </p>
<div class="mt-2 flex items-center"> <div class="mt-2 flex items-center">
<span class="flex mr-2 items-center"> <span class="flex mr-2 items-center">
@@ -199,13 +183,11 @@
<div class="hidden my-2 py-3 px-4 border shadow-sm rounded-md"> <div class="hidden my-2 py-3 px-4 border shadow-sm rounded-md">
<h4 class="dark:text-white m-0 font-normal"> <h4 class="dark:text-white m-0 font-normal">
<a class="text-blue-700 font-bold" href="/courses"> <a class="text-blue-700 font-bold" href="/courses"> Climate Change Group </a>
Climate Change Group
</a>
</h4> </h4>
<p class="dark:text-white text-gray-500"> <p class="dark:text-white text-gray-500">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Illo quasi Lorem ipsum dolor sit amet, consectetur adipisicing elit. Illo quasi autem quo possimus
autem quo possimus illum consequuntur repudiandae nobis error, porro illum consequuntur repudiandae nobis error, porro
</p> </p>
<div class="mt-2 flex items-center"> <div class="mt-2 flex items-center">
<span class="flex mr-2 items-center"> <span class="flex mr-2 items-center">
@@ -218,12 +200,7 @@
</div> </div>
</div> </div>
</div> </div>
<PrimaryButton <PrimaryButton variant={VARIANTS.CONTAINED_DANGER} label="Log out" onClick={logout} />
className="px-6 py-3"
variant={VARIANTS.CONTAINED_DANGER}
label="Log out"
onClick={logout}
/>
</div> </div>
<!-- {:else if loading} <!-- {:else if loading}
<Box> <Box>