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 h5,
.dark .prose h6,
.dark .prose li,
.dark .prose li::marker,
.dark .prose p {
color: #fff;
}
+1 -1
View File
@@ -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>
+1 -1
View File
@@ -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}
+2 -3
View File
@@ -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 />
+1 -1
View File
@@ -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}
/>
+1 -1
View File
@@ -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;
+7 -4
View File
@@ -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">
+18 -2
View File
@@ -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>
+3 -3
View File
@@ -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);
+14 -42
View File
@@ -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>
+19 -33
View File
@@ -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'
+1 -1
View File
@@ -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}
+2 -2
View File
@@ -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}
+5 -12
View File
@@ -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
View File
@@ -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}
-1
View File
@@ -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
+19 -42
View File
@@ -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>