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