CLASSROOMIO-T-6: Dashboard, courses page and other tabs
This commit is contained in:
@@ -2,10 +2,12 @@
|
||||
export let name = '';
|
||||
export let src = '';
|
||||
export let className = '';
|
||||
export let width = 'w-10';
|
||||
export let height = 'h-10';
|
||||
</script>
|
||||
|
||||
<img
|
||||
alt={name}
|
||||
{src}
|
||||
class="w-10 h-10 rounded-full bg-light-blue-100 {className}"
|
||||
class="{className} {width} {height} rounded-full bg-light-blue-100"
|
||||
/>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<div class="w-full h-80 flex items-center justify-center flex-col">
|
||||
<div
|
||||
class="w-full h-80 flex items-center justify-center flex-col border rounded border-gray-200"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<script>
|
||||
export let className;
|
||||
export let value;
|
||||
export let size;
|
||||
</script>
|
||||
|
||||
<p
|
||||
class="{className} {size === 'sm'
|
||||
? 'py-1 px-1'
|
||||
: 'py-2 px-2'} org-avatar bg-blue-200 font-bold rounded-md text-center"
|
||||
>
|
||||
{value}
|
||||
</p>
|
||||
|
||||
<style>
|
||||
.org-avatar {
|
||||
width: fit-content;
|
||||
}
|
||||
</style>
|
||||
@@ -21,9 +21,10 @@
|
||||
import Settings from '../Settings/index.svelte';
|
||||
import { settingsDialog } from '../Settings/store';
|
||||
import { lessons } from '../Lesson/store/lessons';
|
||||
import { course, group } from '../../store';
|
||||
import { course } from '../../store';
|
||||
import { updateCourse } from '../../../../utils/services/courses';
|
||||
import { profile } from '../../../../utils/store/user';
|
||||
import { currentOrg } from '../../../../utils/store/org';
|
||||
import { isMobile } from '../../../../utils/store/useMobile';
|
||||
|
||||
// export let lessonId;
|
||||
@@ -208,7 +209,7 @@
|
||||
>
|
||||
<IconButton
|
||||
value="toggle"
|
||||
onClick={() => goto('/courses')}
|
||||
onClick={() => goto(`/org/${$currentOrg.siteName}/courses`)}
|
||||
buttonClassName="mx-3"
|
||||
>
|
||||
<Home32 class="carbon-icon" />
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
import { goto } from '@sapper/app';
|
||||
import ChevronRight24 from 'carbon-icons-svelte/lib/ChevronRight24';
|
||||
import ArrowLeft24 from 'carbon-icons-svelte/lib/ArrowLeft24';
|
||||
import ArrowUpRight24 from 'carbon-icons-svelte/lib/ArrowUpRight24';
|
||||
|
||||
import Container from './Container.svelte';
|
||||
import IconButton from '../../../IconButton/index.svelte';
|
||||
import CloseButton from '../../../Buttons/Close/index.svelte';
|
||||
@@ -94,6 +96,14 @@
|
||||
const link = `${window.location.origin}/course/${course.slug}`;
|
||||
window.open(link, '_blank');
|
||||
}
|
||||
|
||||
async function handlePublish() {
|
||||
loading = true;
|
||||
await updateCourse(courseId, undefined, {
|
||||
is_published: true,
|
||||
});
|
||||
loading = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Container show={true}>
|
||||
@@ -111,11 +121,14 @@
|
||||
isLoading={loading}
|
||||
/>
|
||||
<PrimaryButton
|
||||
label="Preview"
|
||||
label="Publish"
|
||||
type="button"
|
||||
onClick={handlePreview}
|
||||
isDisabled={loading || !course.slug}
|
||||
onClick={handlePublish}
|
||||
isDisabled={loading}
|
||||
/>
|
||||
<IconButton onClick={handlePreview} disabled={loading || !course.slug}>
|
||||
<ArrowUpRight24 class="carbon-icon" title="Preview" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between items-center px-2 w-full mb-2">
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import PrimaryButton from '../../PrimaryButton/index.svelte';
|
||||
import { VARIANTS } from '../../PrimaryButton/constants';
|
||||
import type { Course } from '../../../utils/types';
|
||||
import getCurrencyFormatter from '../../../utils/functions/getCurrencyFormatter';
|
||||
import { get } from 'lodash';
|
||||
|
||||
export let className = '';
|
||||
@@ -26,11 +27,7 @@
|
||||
|
||||
function setFormatter(currency: string | undefined) {
|
||||
if (!currency) return;
|
||||
formatter = new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: currency,
|
||||
minimumFractionDigits: 2,
|
||||
});
|
||||
formatter = getCurrencyFormatter(currency);
|
||||
}
|
||||
|
||||
$: setFormatter(courseData.currency);
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<script lang="ts">
|
||||
import { SkeletonPlaceholder, SkeletonText } from 'carbon-components-svelte';
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="root w-72 border bg-white mr-4 mb-4 rounded-md transition ease-in-out relative"
|
||||
>
|
||||
<SkeletonPlaceholder style="width: 100%; height: 10rem;" />
|
||||
<div class="p-3 pb-0">
|
||||
<!-- Title -->
|
||||
<SkeletonText style="width: 50%;" />
|
||||
<!-- Description -->
|
||||
<SkeletonText style="width: 100%; margin-top: 1rem;" />
|
||||
<SkeletonText style="width: 100%; margin-bottom: 1rem;" />
|
||||
|
||||
<div class="flex justify-between mb-3">
|
||||
<SkeletonText style="width: 25%" />
|
||||
<SkeletonText style="width: 25%" />
|
||||
</div>
|
||||
<SkeletonText style="width: 25%" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.root {
|
||||
min-height: 370px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,13 +1,20 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import Dropdown from '../../../Dropdown/index.svelte';
|
||||
import { copyCourseModal } from '../../store';
|
||||
import { ROLE } from '../../../../utils/constants/roles';
|
||||
import OverflowMenuVertical24 from 'carbon-icons-svelte/lib/OverflowMenuVertical24';
|
||||
export let logo = '';
|
||||
import getCurrencyFormatter from '../../../../utils/functions/getCurrencyFormatter';
|
||||
import OverflowMenuHorizontal24 from 'carbon-icons-svelte/lib/OverflowMenuHorizontal24';
|
||||
|
||||
export let bannerImage: string | undefined;
|
||||
export let id = '';
|
||||
export let title = '';
|
||||
export let description = '';
|
||||
export let role_id = ROLE.STUDENT;
|
||||
export let isPublished = false;
|
||||
export let cost = 0;
|
||||
export let currency = 'NGN';
|
||||
|
||||
let formatter = getCurrencyFormatter(currency);
|
||||
|
||||
function getOptions() {
|
||||
return [
|
||||
@@ -34,53 +41,72 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="root w-72 border bg-white px-2 py-3 mr-4 mb-4 rounded-md hover:shadow-2xl transition ease-in-out relative"
|
||||
>
|
||||
<Dropdown
|
||||
options={getOptions()}
|
||||
classNames="absolute top-4 right-0"
|
||||
isIcon={true}
|
||||
<a rel="prefetch" href="/courses/{id}" class=" text-black ">
|
||||
<div
|
||||
class="root w-72 border bg-white mr-4 mb-4 rounded-md transition ease-in-out relative"
|
||||
>
|
||||
<OverflowMenuVertical24 class="carbon-icon" />
|
||||
</Dropdown>
|
||||
<img
|
||||
class="w-24 h-24 rounded-full border mx-auto"
|
||||
src={logo}
|
||||
alt="Course Logo"
|
||||
/>
|
||||
<a rel="prefetch" href="/courses/{id}" class="pt-3 text-center space-y-4">
|
||||
<h3 class="text-lg font-semibold hover:underline">
|
||||
{title}
|
||||
</h3>
|
||||
<div
|
||||
class="description text-left font-small flex flex-col text-gray-500"
|
||||
title={description}
|
||||
<Dropdown
|
||||
options={getOptions()}
|
||||
classNames="absolute top-4 right-4"
|
||||
isIcon={true}
|
||||
>
|
||||
{description}
|
||||
<div class="p-1 rounded-full bg-white">
|
||||
<OverflowMenuHorizontal24 class="carbon-icon" />
|
||||
</div>
|
||||
</Dropdown>
|
||||
<img
|
||||
class="h-2/5 w-full rounded-tl rounded-tr"
|
||||
src={bannerImage ?? '/images/classroomio-course-img-template.jpg'}
|
||||
alt="Course Logo"
|
||||
/>
|
||||
<div class="p-3 pb-0">
|
||||
<h4 class="title text-md font-bold">
|
||||
{title}
|
||||
</h4>
|
||||
<p
|
||||
class="description text-left text-sm font-small flex flex-col text-gray-500 my-3"
|
||||
title={description}
|
||||
>
|
||||
{description}
|
||||
</p>
|
||||
<div class="flex justify-between">
|
||||
<p>0 lessons</p>
|
||||
<div class="rounded bg-{isPublished ? 'green' : 'red'}-200 py-1 px-2">
|
||||
{#if isPublished}
|
||||
Published
|
||||
{:else}
|
||||
Unpublished
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-gray-500 text-xs mt-1">
|
||||
{!cost ? 'free' : formatter.format(cost)}
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<style>
|
||||
.root {
|
||||
min-height: 300px;
|
||||
min-height: 370px;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.title,
|
||||
.description {
|
||||
height: 40px;
|
||||
line-height: 20px;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-moz-box-orient: vertical;
|
||||
-ms-box-orient: vertical;
|
||||
box-orient: vertical;
|
||||
-webkit-line-clamp: 5;
|
||||
-moz-line-clamp: 5;
|
||||
-ms-line-clamp: 5;
|
||||
line-clamp: 5;
|
||||
-webkit-line-clamp: 2;
|
||||
-moz-line-clamp: 2;
|
||||
-ms-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script>
|
||||
import Box from '../Box/index.svelte';
|
||||
import { Moon } from 'svelte-loading-spinners';
|
||||
import Card from './components/Card/index.svelte';
|
||||
import CardLoader from './components/Card/Loader.svelte';
|
||||
import CoursesEmptyIcon from '../Icons/CoursesEmptyIcon.svelte';
|
||||
import CopyCourseModal from './components/CopyCourseModal/index.svelte';
|
||||
import { courses, courseMetaDeta } from './store';
|
||||
</script>
|
||||
@@ -12,21 +13,30 @@
|
||||
class="flex items-center justify-center lg:justify-start flex-wrap my-4 m-auto"
|
||||
>
|
||||
{#if $courseMetaDeta.isLoading}
|
||||
<Box>
|
||||
<Moon size="60" color="#1d4ed8" unit="px" duration="1s" />
|
||||
</Box>
|
||||
<CardLoader />
|
||||
<CardLoader />
|
||||
<CardLoader />
|
||||
{:else}
|
||||
{#each $courses as courseData}
|
||||
<Card
|
||||
id={courseData.id}
|
||||
logo={courseData.logo}
|
||||
bannerImage={courseData.banner_image}
|
||||
title={courseData.title}
|
||||
description={courseData.description}
|
||||
role_id={courseData.role_id}
|
||||
isPublished={courseData.is_published}
|
||||
cost={courseData.cost}
|
||||
currency={courseData.currency}
|
||||
/>
|
||||
{:else}
|
||||
<Box>
|
||||
<h3 class="text-2xl text-gray-500">No Course Assigned</h3>
|
||||
<CoursesEmptyIcon />
|
||||
<h3 class="text-2xl my-5 ">No Courses Created</h3>
|
||||
<p class="w-1/3 text-center">
|
||||
Share your knowledge with the world by creating engaging courses for
|
||||
your students.
|
||||
</p>
|
||||
</Box>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
@@ -32,7 +32,11 @@
|
||||
<div class="{classNames} z-10d {!options.length && 'hidden'}">
|
||||
<button
|
||||
class="flex flex-row items-center {!isIcon && notIconClass}"
|
||||
on:click={(e) => (open = !open)}
|
||||
on:click={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
open = !open;
|
||||
}}
|
||||
on:blur={onBlur}
|
||||
>
|
||||
<slot />
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
export let helperMessage = '';
|
||||
export let autoComplete = true;
|
||||
export let onChange = () => {}; // This is to know if element is 'dirty'
|
||||
export let onInputChange = () => {};
|
||||
export let onInputChange = (e) => {};
|
||||
|
||||
let ref;
|
||||
let fieldNode;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M15 1.5V3C15.9946 3 16.9484 3.39509 17.6516 4.09835C18.3549 4.80161 18.75 5.75544 18.75 6.75C18.75 7.74456 18.3549 8.69839 17.6516 9.40165C16.9484 10.1049 15.9946 10.5 15 10.5V12C16.3924 12 17.7277 11.4469 18.7123 10.4623C19.6969 9.47774 20.25 8.14239 20.25 6.75C20.25 5.35761 19.6969 4.02226 18.7123 3.03769C17.7277 2.05312 16.3924 1.5 15 1.5ZM15 22.5H16.5V18.75C16.4984 17.3581 15.9447 16.0237 14.9605 15.0395C13.9763 14.0553 12.6419 13.5016 11.25 13.5H6.75C5.35811 13.5016 4.0237 14.0553 3.03949 15.0395C2.05527 16.0237 1.50163 17.3581 1.5 18.75V22.5H3V18.75C3.00117 17.7558 3.39664 16.8027 4.09964 16.0996C4.80265 15.3966 5.7558 15.0012 6.75 15H11.25C12.2442 15.0012 13.1973 15.3966 13.9004 16.0996C14.6034 16.8027 14.9988 17.7558 15 18.75V22.5ZM21 22.5H22.5V18.75C22.4985 17.3581 21.9448 16.0236 20.9606 15.0394C19.9764 14.0552 18.6419 13.5015 17.25 13.5V15C18.2442 15.0011 19.1974 15.3966 19.9004 16.0996C20.6034 16.8026 20.9989 17.7558 21 18.75V22.5ZM11.0834 3.63199C10.4667 3.21993 9.74168 3 9 3C8.00544 3 7.05161 3.39509 6.34835 4.09835C5.64509 4.80161 5.25 5.75544 5.25 6.75C5.25 7.49168 5.46994 8.2167 5.88199 8.83339C6.29405 9.45007 6.87972 9.93072 7.56494 10.2145C8.25016 10.4984 9.00416 10.5726 9.73159 10.4279C10.459 10.2833 11.1272 9.9261 11.6517 9.40165C12.1761 8.8772 12.5333 8.20902 12.6779 7.48159C12.8226 6.75416 12.7484 6.00016 12.4646 5.31494C12.1807 4.62971 11.7001 4.04404 11.0834 3.63199ZM6.08326 2.38478C6.94662 1.80791 7.96165 1.5 9 1.5C10.3924 1.5 11.7277 2.05312 12.7123 3.03769C13.6969 4.02226 14.25 5.35761 14.25 6.75C14.25 7.78835 13.9421 8.80339 13.3652 9.66674C12.7883 10.5301 11.9684 11.203 11.0091 11.6004C10.0498 11.9977 8.99418 12.1017 7.97578 11.8991C6.95738 11.6966 6.02192 11.1965 5.28769 10.4623C4.55347 9.72808 4.05345 8.79262 3.85088 7.77422C3.64831 6.75582 3.75227 5.70022 4.14963 4.74091C4.54699 3.7816 5.2199 2.96166 6.08326 2.38478Z"
|
||||
fill="#7A797D"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,12 @@
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M18 22.5H6C5.60232 22.4995 5.22105 22.3414 4.93984 22.0602C4.65864 21.7789 4.50046 21.3977 4.5 21V3C4.5004 2.6023 4.65856 2.221 4.93978 1.93978C5.221 1.65856 5.6023 1.5004 6 1.5H18C18.3977 1.50046 18.779 1.65864 19.0602 1.93984C19.3414 2.22105 19.4995 2.60232 19.5 3V15.4636L15.75 13.5886L12 15.4636V3H6V21H18V18H19.5V21C19.4995 21.3977 19.3413 21.7789 19.0601 22.0601C18.7789 22.3413 18.3977 22.4995 18 22.5ZM15.75 11.9114L18 13.0364V3H13.5V13.0364L15.75 11.9114Z"
|
||||
fill="#7A797D"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 619 B |
@@ -0,0 +1,109 @@
|
||||
<svg
|
||||
width="115"
|
||||
height="68"
|
||||
viewBox="0 0 115 68"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect
|
||||
x="21.8751"
|
||||
y="1.61458"
|
||||
width="82.9271"
|
||||
height="58.3854"
|
||||
rx="7.125"
|
||||
fill="#FAF8F8"
|
||||
stroke="#1339A6"
|
||||
stroke-width="1.97917"
|
||||
/>
|
||||
<path
|
||||
d="M81.1511 64.4538H81.547V64.0579V61.3861H114.104V63.3652C114.104 65.4421 112.421 67.1257 110.344 67.1257H16.1355C14.0587 67.1257 12.3751 65.4421 12.3751 63.3652V61.3861H46.7136V64.0579V64.4538H47.1095H81.1511Z"
|
||||
stroke="#0F62FE"
|
||||
stroke-width="0.791667"
|
||||
/>
|
||||
<ellipse cx="55.224" cy="17.2487" rx="11.776" ry="0.791667" fill="#D9E0F5" />
|
||||
<circle
|
||||
cx="47.901"
|
||||
cy="30.2126"
|
||||
r="7.96354"
|
||||
fill="white"
|
||||
stroke="#1339A6"
|
||||
stroke-width="0.5"
|
||||
/>
|
||||
<path
|
||||
d="M43.2327 28.6708L51.8044 27.0029L48.963 35.2601L43.2327 28.6708Z"
|
||||
stroke="#1339A6"
|
||||
stroke-width="0.5"
|
||||
/>
|
||||
<rect
|
||||
x="71.0573"
|
||||
y="13.9837"
|
||||
width="16.0312"
|
||||
height="33.8438"
|
||||
rx="1.90104"
|
||||
stroke="#1339A6"
|
||||
stroke-width="0.197917"
|
||||
/>
|
||||
<circle cx="78.974" cy="17.3499" r="0.692708" fill="#D9E0F5" />
|
||||
<rect
|
||||
x="73.7291"
|
||||
y="22"
|
||||
width="9.10417"
|
||||
height="0.395833"
|
||||
rx="0.197917"
|
||||
fill="#D9E0F5"
|
||||
/>
|
||||
<rect
|
||||
x="75.1146"
|
||||
y="24.1768"
|
||||
width="9.10417"
|
||||
height="0.395833"
|
||||
rx="0.197917"
|
||||
fill="#D9E0F5"
|
||||
/>
|
||||
<rect
|
||||
x="73.7291"
|
||||
y="26.3535"
|
||||
width="9.10417"
|
||||
height="0.395833"
|
||||
rx="0.197917"
|
||||
fill="#D9E0F5"
|
||||
/>
|
||||
<rect
|
||||
x="73.7291"
|
||||
y="32.8848"
|
||||
width="9.10417"
|
||||
height="0.395833"
|
||||
rx="0.197917"
|
||||
fill="#D9E0F5"
|
||||
/>
|
||||
<rect
|
||||
x="73.7291"
|
||||
y="36.0518"
|
||||
width="5.34375"
|
||||
height="0.395833"
|
||||
rx="0.197917"
|
||||
fill="#D9E0F5"
|
||||
/>
|
||||
<rect
|
||||
x="73.7291"
|
||||
y="39.2178"
|
||||
width="5.34375"
|
||||
height="0.395833"
|
||||
rx="0.197917"
|
||||
fill="#D9E0F5"
|
||||
/>
|
||||
<rect
|
||||
x="73.7291"
|
||||
y="42.3838"
|
||||
width="9.10417"
|
||||
height="0.395833"
|
||||
rx="0.197917"
|
||||
fill="#D9E0F5"
|
||||
/>
|
||||
<path
|
||||
d="M3.26438 11.0703C1.94083 10.2071 2.52742 9.10995 3.3773 8.34703C3.9627 7.82153 4.55669 7.26974 5.17889 6.78836C6.20593 5.99377 7.88967 5.42142 9.39949 6.8151C11.9724 9.1901 9.30054 11.664 8.60783 8.89319C8.05366 6.67653 11.8736 6.78207 13.8528 7.11192C20.0872 8.59632 12.2691 18.888 4.94637 16.1172C-3.21625 13.0285 1.97783 7.50776 3.957 7.11192C5.93617 6.71609 5.54043 12.5546 3.26438 11.0703Z"
|
||||
stroke="#0F62FE"
|
||||
stroke-width="0.989583"
|
||||
stroke-linejoin="bevel"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,14 @@
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M3 1.5H21C21.3977 1.50046 21.7789 1.65864 22.0602 1.93984C22.3414 2.22105 22.4995 2.60232 22.5 3V21C22.4995 21.3977 22.3413 21.7789 22.0601 22.0601C21.7789 22.3413 21.3977 22.4995 21 22.5H3C2.60232 22.4995 2.22105 22.3414 1.93984 22.0602C1.65864 21.7789 1.50046 21.3977 1.5 21V3C1.5004 2.6023 1.65856 2.221 1.93978 1.93978C2.221 1.65856 2.6023 1.5004 3 1.5ZM10.5 8.25H21V3H10.5V8.25ZM9 8.25V3H3V8.25H9ZM3 9.75V21H21.0015L21.0005 9.75H3ZM8.25 19.5C7.2558 19.4988 6.30265 19.1034 5.59964 18.4004C4.89664 17.6973 4.50117 16.7442 4.5 15.75H6C6 16.195 6.13196 16.63 6.37919 17C6.62643 17.37 6.97783 17.6584 7.38896 17.8287C7.8001 17.999 8.2525 18.0436 8.68895 17.9568C9.12541 17.87 9.52632 17.6557 9.84099 17.341C10.1557 17.0263 10.37 16.6254 10.4568 16.189C10.5436 15.7525 10.499 15.3001 10.3287 14.889C10.1584 14.4778 9.87004 14.1264 9.50003 13.8792C9.13002 13.632 8.69501 13.5 8.25 13.5V12C9.24456 12 10.1984 12.3951 10.9017 13.0983C11.6049 13.8016 12 14.7554 12 15.75C12 16.7446 11.6049 17.6984 10.9017 18.4016C10.1984 19.1049 9.24456 19.5 8.25 19.5ZM15 12H16.5V19.5H15V12ZM18 15.75H19.5V19.5H18V15.75Z"
|
||||
fill="#262626"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,14 @@
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M20.9243 6.00098H22.5001V7.50098H20.9243C20.8276 7.97046 20.6412 8.41687 20.3753 8.81573L21.4928 9.93323L20.4323 10.9937L19.3148 9.87623C18.916 10.1421 18.4696 10.3284 18.0001 10.4252V12.001H16.5001V10.4252C16.0307 10.3284 15.5842 10.142 15.1854 9.87615L14.0679 10.9937L13.0074 9.93315L14.1249 8.81565C13.8591 8.41681 13.6727 7.97043 13.576 7.50098H12.0001V6.00098H13.576C13.6727 5.53149 13.8591 5.08508 14.125 4.68623L13.0075 3.56873L14.068 2.50823L15.1855 3.62573C15.5843 3.35989 16.0307 3.17351 16.5001 3.0768V1.50098H18.0001V3.0768C18.4696 3.17352 18.916 3.35993 19.3149 3.6258L20.4324 2.5083L21.4929 3.5688L20.3754 4.6863C20.6412 5.08514 20.8276 5.53152 20.9243 6.00098ZM16.0001 8.62178C16.3701 8.86902 16.8051 9.00098 17.2501 9.00098C17.8467 9.00032 18.4186 8.76306 18.8404 8.34124C19.2622 7.91943 19.4995 7.34751 19.5001 6.75098C19.5001 6.30597 19.3682 5.87095 19.1209 5.50094C18.8737 5.13093 18.5223 4.84255 18.1112 4.67225C17.7 4.50195 17.2476 4.45739 16.8112 4.54421C16.3747 4.63103 15.9738 4.84532 15.6591 5.15999C15.3445 5.47465 15.1302 5.87557 15.0434 6.31202C14.9566 6.74848 15.0011 7.20088 15.1714 7.61201C15.3417 8.02315 15.6301 8.37455 16.0001 8.62178ZM21 16.5002V12.7502H22.5V16.5002C22.5 16.8981 22.342 17.2796 22.0607 17.5609C21.7794 17.8422 21.3978 18.0002 21 18.0002H15V21.0002H18V22.5002H6V21.0002H9V18.0002H3C2.60218 18.0002 2.22064 17.8422 1.93934 17.5609C1.65804 17.2796 1.5 16.8981 1.5 16.5002V4.50024C1.5 4.10242 1.65804 3.72089 1.93934 3.43958C2.22064 3.15828 2.60218 3.00024 3 3.00024H10.5V4.50024H3V16.5002H21ZM10.5 21.0002H13.5V18.0002H10.5V21.0002Z"
|
||||
fill="#7A797D"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,91 @@
|
||||
<script>
|
||||
import NotificationIcon from 'carbon-icons-svelte/lib/Notification20';
|
||||
import Avatar from '../Avatar/index.svelte';
|
||||
import { user, profile } from '../../utils/store/user';
|
||||
|
||||
export let segment;
|
||||
export let navClass = '';
|
||||
</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="/" title="Go to ClassroomIO Home" id="logo" class="text-lg">
|
||||
ClassroomIO
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<span class="flex-grow" />
|
||||
|
||||
<li>
|
||||
<NotificationIcon class="text-white mr-2" />
|
||||
</li>
|
||||
<li>
|
||||
{#if $user.isLoggedIn}
|
||||
<a
|
||||
class="block"
|
||||
aria-current={segment === 'profile' ? 'page' : undefined}
|
||||
href={`/profile/${$profile.id}`}
|
||||
>
|
||||
<Avatar
|
||||
src={$profile.avatar_url}
|
||||
name={$profile.username}
|
||||
width="w-7"
|
||||
height="h-7"
|
||||
/>
|
||||
</a>
|
||||
{/if}
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
ul {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* clearfix */
|
||||
ul::after {
|
||||
content: '';
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* li.new-question {
|
||||
margin-top: 10px;
|
||||
} */
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* [aria-current] {
|
||||
color: var(--main-primary-color);
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
[aria-current]::after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
width: calc(100% - 3em);
|
||||
height: 2px;
|
||||
background-color: var(--main-primary-color);
|
||||
display: block;
|
||||
bottom: -17px;
|
||||
} */
|
||||
|
||||
@media only screen and (max-width: 1002px) {
|
||||
nav.hide {
|
||||
display: none;
|
||||
}
|
||||
ul {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
a {
|
||||
padding: 0 0.5em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -85,7 +85,8 @@
|
||||
<Avatar
|
||||
src={$profile.avatar_url}
|
||||
name={$profile.username}
|
||||
className="mr-2"
|
||||
width="w-7"
|
||||
height="h-7"
|
||||
/>
|
||||
</a>
|
||||
{:else if !disableLogin}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
<script>
|
||||
import { stores } from '@sapper/app';
|
||||
import UserAvatar from 'carbon-icons-svelte/lib/UserAvatar20';
|
||||
import Help from 'carbon-icons-svelte/lib/Help20';
|
||||
|
||||
import TextChip from '../../components/Chip/Text.svelte';
|
||||
import OrgSelector from '../../components/OrgSelector/OrgSelector.svelte';
|
||||
import HomeIcon from '../../components/Icons/HomeIcon.svelte';
|
||||
import CourseIcon from '../../components/Icons/CourseIcon.svelte';
|
||||
import SiteSettingsIcon from '../../components/Icons/SiteSettingsIcon.svelte';
|
||||
import AudienceIcon from '../../components/Icons/AudienceIcon.svelte';
|
||||
import { currentOrg } from '../../utils/store/org';
|
||||
|
||||
let orgName = '';
|
||||
let activeClass = 'bg-gray-200';
|
||||
let { page } = stores();
|
||||
|
||||
function isActive(pagePath, itemPath) {
|
||||
const pageLinkItems = pagePath.split('/');
|
||||
const itemLinkItems = itemPath.split('/');
|
||||
|
||||
if (itemLinkItems.length !== pageLinkItems.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return pagePath.includes(itemPath);
|
||||
}
|
||||
|
||||
$: orgName = $currentOrg.siteName;
|
||||
</script>
|
||||
|
||||
<aside class="root bg-gray-100 h-full">
|
||||
<div class="h-full flex flex-col">
|
||||
<div class="border-b border-gray-200 pt-5 px-4 ">
|
||||
<TextChip value="DA" />
|
||||
|
||||
<OrgSelector />
|
||||
|
||||
<ul class="my-5">
|
||||
<a href="/org/{orgName}" class="text-black">
|
||||
<li
|
||||
class="flex items-center py-3 px-4 mb-2 rounded hover:bg-gray-200 {isActive(
|
||||
$page.path,
|
||||
`/org/${orgName}`
|
||||
) && activeClass}"
|
||||
>
|
||||
<HomeIcon />
|
||||
<p class="ml-2">Home</p>
|
||||
</li>
|
||||
</a>
|
||||
<a href="/org/{orgName}/courses" class="text-black">
|
||||
<li
|
||||
class="flex items-center py-3 px-4 mb-2 rounded hover:bg-gray-200 {isActive(
|
||||
$page.path,
|
||||
`/org/${orgName}/courses`
|
||||
) && activeClass}"
|
||||
>
|
||||
<CourseIcon />
|
||||
<p class="ml-2">Courses</p>
|
||||
</li>
|
||||
</a>
|
||||
<a href="/org/{orgName}/site" class="text-black">
|
||||
<li
|
||||
class="flex items-center py-3 px-4 mb-2 rounded hover:bg-gray-200 {isActive(
|
||||
$page.path,
|
||||
`/org/${orgName}/site`
|
||||
) && activeClass}"
|
||||
>
|
||||
<SiteSettingsIcon />
|
||||
<p class="ml-2">Site settings</p>
|
||||
</li>
|
||||
</a>
|
||||
<a href="/org/{orgName}/audience" class="text-black">
|
||||
<li
|
||||
class="flex items-center py-3 px-4 mb-2 rounded hover:bg-gray-200 {isActive(
|
||||
$page.path,
|
||||
`/org/${orgName}/audience`
|
||||
) && activeClass}"
|
||||
>
|
||||
<AudienceIcon />
|
||||
<p class="ml-2">Audience</p>
|
||||
</li>
|
||||
</a>
|
||||
</ul>
|
||||
</div>
|
||||
<span class="flex-grow" />
|
||||
<ul class="my-5 pb-5 px-4 ">
|
||||
<a href="/help" class="text-black">
|
||||
<li class="flex items-center py-3 px-4 mb-2 rounded hover:bg-gray-200">
|
||||
<Help />
|
||||
<p class="ml-2">Help</p>
|
||||
</li>
|
||||
</a>
|
||||
<a href="/org/{orgName}/settings" class="text-black">
|
||||
<li
|
||||
class="flex items-center py-3 px-4 mb-2 rounded hover:bg-gray-200 {isActive(
|
||||
$page.path,
|
||||
`/org/${orgName}/settings`
|
||||
) && activeClass}"
|
||||
>
|
||||
<UserAvatar />
|
||||
<p class="ml-2">Settings</p>
|
||||
</li>
|
||||
</a>
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<style>
|
||||
.root {
|
||||
width: 250px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,87 @@
|
||||
<script context="module">
|
||||
export async function preload({ params }) {
|
||||
return params.slug;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { Popover } from 'carbon-components-svelte';
|
||||
import CaretSortIcon from 'carbon-icons-svelte/lib/CaretSort16';
|
||||
import OrgSelectorItem from './OrgSelectorItem.svelte';
|
||||
import Modal from '../Modal/index.svelte';
|
||||
import TextField from '../Form/TextField.svelte';
|
||||
import PrimaryButton from '../PrimaryButton/index.svelte';
|
||||
|
||||
let open = false;
|
||||
let openAddOrg = false;
|
||||
let loading = false;
|
||||
let orgName = '';
|
||||
|
||||
function handleAddOrg() {
|
||||
console.log('add org modal');
|
||||
open = false;
|
||||
openAddOrg = true;
|
||||
}
|
||||
|
||||
function createNewOrg() {}
|
||||
</script>
|
||||
|
||||
<div class="mt-5 relative" data-outline>
|
||||
<div
|
||||
class="flex items-center cursor-pointer"
|
||||
on:click={(e) => {
|
||||
e.stopPropagation();
|
||||
open = !open;
|
||||
}}
|
||||
>
|
||||
<p class="text-lg font-bold">DigDippa</p>
|
||||
<CaretSortIcon class="ml-2" />
|
||||
</div>
|
||||
<Popover bind:open closeOnOutsideClick align="bottom-left">
|
||||
<OrgSelectorItem
|
||||
size="sm"
|
||||
active={false}
|
||||
avatarText="DA"
|
||||
text="DigDippa"
|
||||
hasDivider={true}
|
||||
/>
|
||||
<OrgSelectorItem
|
||||
size="sm"
|
||||
active={true}
|
||||
avatarText="PA"
|
||||
text="Pepsi Co"
|
||||
hasDivider={true}
|
||||
/>
|
||||
<OrgSelectorItem text=" + Add Organization" onClick={handleAddOrg} />
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
{#if openAddOrg}
|
||||
<Modal
|
||||
onClose={() => (openAddOrg = false)}
|
||||
bind:open={openAddOrg}
|
||||
width="w-96"
|
||||
modalHeading="Create Organization"
|
||||
>
|
||||
<form on:submit|preventDefault={createNewOrg} class="px-2">
|
||||
<TextField
|
||||
label="Name of Organization"
|
||||
bind:value={orgName}
|
||||
autoFocus={true}
|
||||
placeholder="e.g Pepsi Co"
|
||||
className="mb-4"
|
||||
isRequired={true}
|
||||
autoComplete={false}
|
||||
/>
|
||||
|
||||
<div class="mt-5 flex items-center flex-row-reverse">
|
||||
<PrimaryButton
|
||||
className="px-6 py-3"
|
||||
label="Create Organization"
|
||||
type="submit"
|
||||
isLoading={loading}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
{/if}
|
||||
@@ -0,0 +1,27 @@
|
||||
<script>
|
||||
import TextChip from '../Chip/Text.svelte';
|
||||
|
||||
export let size;
|
||||
export let hasDivider = false;
|
||||
export let active = false;
|
||||
export let avatarText = '';
|
||||
export let text = '';
|
||||
export let onClick;
|
||||
</script>
|
||||
|
||||
<button
|
||||
class="flex items-center w-60 py-2 px-3 {hasDivider &&
|
||||
'border-b border-gray-100'} {active && 'bg-blue-100'}"
|
||||
on:click={onClick}
|
||||
>
|
||||
{#if avatarText}
|
||||
<TextChip value={avatarText} className="mr-2" {size} />
|
||||
{/if}
|
||||
{text}
|
||||
</button>
|
||||
|
||||
<style>
|
||||
button {
|
||||
min-height: 56px;
|
||||
}
|
||||
</style>
|
||||
@@ -12,14 +12,13 @@
|
||||
export let isDisabled = false;
|
||||
export let isLoading = false;
|
||||
|
||||
$: variant = isLoading ? VARIANTS.OUTLINED : variant;
|
||||
$: isDisabled = isLoading ? true : false;
|
||||
</script>
|
||||
|
||||
<button
|
||||
class="{isDisabled &&
|
||||
'bg-opacity-25 hover:bg-opacity-25 cursor-not-allowed'} flex items-center h-auto {VARIANTS_CLASS[
|
||||
variant
|
||||
isLoading ? VARIANTS.OUTLINED : variant
|
||||
]} {!disablePadding &&
|
||||
'py-1 px-5'} rounded-none {className} py-3 w-fit justify-center sm:w-auto {isLoading &&
|
||||
'py-3 border-blue-700'} hover:shadow-xl transition delay-150 duration-300 ease-in-out"
|
||||
|
||||
@@ -8,12 +8,15 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { derived } from 'svelte/store';
|
||||
import { stores, goto } from '@sapper/app';
|
||||
import _ from 'lodash';
|
||||
import * as Sentry from '@sentry/browser';
|
||||
import { Moon } from 'svelte-loading-spinners';
|
||||
import Tailwindcss from '../components/Tailwindcss.svelte';
|
||||
import Navigation from '../components/Navigation/index.svelte';
|
||||
import LandingNavigation from '../components/Navigation/index.svelte';
|
||||
import OrgNavigation from '../components/Navigation/app.svelte';
|
||||
import Snackbar from '../components/Snackbar/index.svelte';
|
||||
import Backdrop from '../components/Backdrop/index.svelte';
|
||||
import OrgSideBar from '../components/Org/SideBar.svelte';
|
||||
// import SideBar from "../components/SideBar/index.svelte";
|
||||
// import Footer from '../components/Footer/index.svelte';
|
||||
import Apps from '../components/Apps/index.svelte';
|
||||
@@ -22,6 +25,7 @@
|
||||
import { getSupabase } from '../utils/functions/supabase';
|
||||
import { isMobile } from '../utils/store/useMobile';
|
||||
import { ROUTES_TO_HIDE_NAV, ROUTE } from '../utils/constants/routes';
|
||||
import { getOrganizations } from '../utils/services/org';
|
||||
|
||||
export let segment;
|
||||
export let config;
|
||||
@@ -47,7 +51,7 @@
|
||||
status,
|
||||
} = await supabase
|
||||
.from('profile')
|
||||
.select(`*, org:organizationmember(*)`)
|
||||
.select(`*`)
|
||||
.eq('id', authUser.id)
|
||||
.single();
|
||||
|
||||
@@ -88,11 +92,14 @@
|
||||
// Set user in sentry
|
||||
Sentry.setUser($profile);
|
||||
|
||||
const orgRes = await getOrganizations($profile.id);
|
||||
|
||||
// If user coming to login page, then
|
||||
if (!$profile.role) {
|
||||
if (_.isEmpty(orgRes.orgs)) {
|
||||
goto(ROUTE.ONBOARDING);
|
||||
} else if (['login', 'signup', 'onboarding'].includes(path)) {
|
||||
goto('/dashboard');
|
||||
// By default redirect to first organization
|
||||
goto(`/org/${orgRes.currentOrg.siteName}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,6 +108,10 @@
|
||||
return /course[s]*\/[a-z 0-9 -]/.test(path);
|
||||
}
|
||||
|
||||
function isOrgPage(path) {
|
||||
return /org\/[a-z 0-9 -]/.test(path);
|
||||
}
|
||||
|
||||
function handleResize() {
|
||||
isMobile.update(() => window.innerWidth <= 760);
|
||||
}
|
||||
@@ -177,13 +188,26 @@
|
||||
</Backdrop>
|
||||
{/if}
|
||||
{#if !ROUTES_TO_HIDE_NAV.includes($page.path) && !isCoursePage(path)}
|
||||
<Navigation {segment} />
|
||||
{#if $page.path.includes('org') || $page.path.includes('profile')}
|
||||
<OrgNavigation />
|
||||
{:else}
|
||||
<LandingNavigation {segment} />
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<div class="flex justify-between">
|
||||
<slot />
|
||||
{#if isOrgPage($page.path)}
|
||||
<div class="org-root w-full flex items-center justify-between">
|
||||
<OrgSideBar />
|
||||
<div class="org-slot bg-white">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<slot />
|
||||
{/if}
|
||||
|
||||
{#if path.includes('courses')}
|
||||
{#if isCoursePage(path)}
|
||||
<Apps />
|
||||
{/if}
|
||||
</div>
|
||||
@@ -199,6 +223,15 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.org-root {
|
||||
height: calc(100vh - 44px);
|
||||
}
|
||||
.org-slot {
|
||||
width: calc(100vw - 250px);
|
||||
height: calc(100vh - 44px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
:global(a:hover) {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@@ -5,12 +5,10 @@
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { goto } from '@sapper/app';
|
||||
import TextField from '../components/Form/TextField.svelte';
|
||||
import PrimaryButton from '../components/PrimaryButton/index.svelte';
|
||||
import { getSupabase } from '../utils/functions/supabase';
|
||||
import { authValidation } from '../utils/functions/validator';
|
||||
import { ROUTE } from '../utils/constants/routes';
|
||||
import { LOGIN_FIELDS } from '../utils/constants/authentication';
|
||||
import AuthUI from '../components/AuthUI/index.svelte';
|
||||
|
||||
@@ -21,7 +19,6 @@
|
||||
let fields = Object.assign({}, LOGIN_FIELDS);
|
||||
let submitError;
|
||||
let loading = false;
|
||||
let success = false;
|
||||
let errors = {};
|
||||
|
||||
async function handleSubmit() {
|
||||
@@ -41,9 +38,6 @@
|
||||
});
|
||||
console.log('data', data);
|
||||
if (error) throw error;
|
||||
|
||||
success = true;
|
||||
return goto(ROUTE.DASHBOARD);
|
||||
} catch (error) {
|
||||
submitError = error.error_description || error.message;
|
||||
} finally {
|
||||
|
||||
+230
-193
@@ -1,25 +1,28 @@
|
||||
<script context="module">
|
||||
//@ts-ignore
|
||||
export function preload(page, { config }) {
|
||||
return { config };
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { goto } from '@sapper/app';
|
||||
import CheckmarkFilled20 from 'carbon-icons-svelte/lib/CheckmarkFilled20';
|
||||
import TextField from '../components/Form/TextField.svelte';
|
||||
import UserProfileIcon from '../components/Icons/UserProfileIcon.svelte';
|
||||
import PrimaryButton from '../components/PrimaryButton/index.svelte';
|
||||
import { VARIANTS } from '../components/PrimaryButton/constants';
|
||||
import { profile } from '../utils/store/user';
|
||||
import { ROUTE } from '../utils/constants/routes';
|
||||
import { orgs, currentOrg } from '../utils/store/org';
|
||||
import { onboardingValidation } from '../utils/functions/validator';
|
||||
import { supabase } from '../utils/functions/supabase';
|
||||
|
||||
enum OnboardingRoles {
|
||||
Educator = 'educator',
|
||||
Student = 'student',
|
||||
}
|
||||
|
||||
interface OnboardingField {
|
||||
fullname?: string;
|
||||
role?: OnboardingRoles;
|
||||
orgName?: string;
|
||||
siteName?: string;
|
||||
goal?: string;
|
||||
source?: string;
|
||||
metadata?: {};
|
||||
}
|
||||
|
||||
interface Goal {
|
||||
@@ -31,14 +34,14 @@
|
||||
|
||||
let fields: OnboardingField = {
|
||||
fullname: '',
|
||||
orgName: '',
|
||||
siteName: '',
|
||||
};
|
||||
let errors: OnboardingField = {};
|
||||
let progress = 50;
|
||||
let step = 1;
|
||||
let goals: Goal[] = [];
|
||||
let loading = false;
|
||||
|
||||
const roles = [OnboardingRoles.Educator, OnboardingRoles.Student];
|
||||
let isSiteNameTouched = false;
|
||||
|
||||
const educatorGoals: Goal[] = [
|
||||
{
|
||||
@@ -59,21 +62,6 @@
|
||||
},
|
||||
];
|
||||
|
||||
const studentGoals: Goal[] = [
|
||||
{
|
||||
label: 'Find new courses',
|
||||
value: 'find-course',
|
||||
},
|
||||
{
|
||||
label: 'Join my class online',
|
||||
value: 'join-class',
|
||||
},
|
||||
{
|
||||
label: 'Just here to explore',
|
||||
value: 'explore',
|
||||
},
|
||||
];
|
||||
|
||||
const sources = [
|
||||
{
|
||||
label: 'Articles',
|
||||
@@ -93,29 +81,85 @@
|
||||
},
|
||||
];
|
||||
|
||||
const onGoalInputChange = (event: KeyboardEvent) => {
|
||||
console.log('event', event);
|
||||
fields.goal = (event.target as HTMLInputElement).value;
|
||||
};
|
||||
async function setMetaData() {
|
||||
const response = await fetch('https://api.ipregistry.co/?key=tryout');
|
||||
const payload = await response.json();
|
||||
fields.metadata = payload;
|
||||
}
|
||||
|
||||
const onSourceInputChange = (event: KeyboardEvent) => {
|
||||
console.log('event', event);
|
||||
fields.source = (event.target as HTMLInputElement).value;
|
||||
};
|
||||
function setOrgSiteName(orgName: string | undefined, isTouched: boolean) {
|
||||
console.log('set org name', orgName, isTouched);
|
||||
if (!orgName || isTouched) return;
|
||||
|
||||
fields.siteName = orgName
|
||||
// @ts-ignore
|
||||
.replaceAll(' ', '-')
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
loading = true;
|
||||
// Run validation
|
||||
console.log(fields);
|
||||
errors = onboardingValidation(fields, step);
|
||||
console.log('errors', errors);
|
||||
|
||||
if (Object.keys(errors).length) {
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (step === 1) {
|
||||
const { data: org, error } = await supabase.from('organization').insert({
|
||||
name: fields.orgName,
|
||||
siteName: fields.siteName,
|
||||
});
|
||||
console.log('Create organisation', org);
|
||||
if (error) {
|
||||
console.log('Error: create organisation', error);
|
||||
errors.siteName = 'Sitename already exists.';
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(org) && org.length) {
|
||||
const orgData = org[0];
|
||||
const { data, error } = await supabase
|
||||
.from('organizationmember')
|
||||
.insert({
|
||||
organization_id: orgData.id,
|
||||
profile_id: $profile.id,
|
||||
role_id: 1,
|
||||
});
|
||||
console.log('Create organisation member', data);
|
||||
|
||||
orgs.set([orgData as never]);
|
||||
currentOrg.set(orgData);
|
||||
|
||||
if (error) {
|
||||
console.log('Error: create organisation member', error);
|
||||
errors.siteName =
|
||||
'Something went wrong while creating this organization. Please reload and try again';
|
||||
|
||||
// Delete organization so it can be recreated.
|
||||
await supabase
|
||||
.from('organization')
|
||||
.delete()
|
||||
.match({ siteName: fields.siteName });
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// client
|
||||
}
|
||||
|
||||
if (step === maxSteps) {
|
||||
// Submit filed
|
||||
loading = true;
|
||||
// Submit filled
|
||||
|
||||
// Set extra metadata based off location
|
||||
await setMetaData();
|
||||
|
||||
let { data, error } = await supabase
|
||||
.from('profile')
|
||||
.update(fields)
|
||||
@@ -124,184 +168,177 @@
|
||||
|
||||
console.log('data', data);
|
||||
console.log('error', error);
|
||||
return goto(ROUTE.DASHBOARD);
|
||||
return goto(`/org/${fields.siteName}`);
|
||||
}
|
||||
|
||||
// Move to next step
|
||||
step = step + 1;
|
||||
loading = false;
|
||||
};
|
||||
|
||||
$: {
|
||||
progress = Math.round((step / maxSteps) * 100);
|
||||
goals =
|
||||
fields.role === OnboardingRoles.Educator ? educatorGoals : studentGoals;
|
||||
}
|
||||
$: progress = Math.round((step / maxSteps) * 100);
|
||||
$: setOrgSiteName(fields.orgName, isSiteNameTouched);
|
||||
</script>
|
||||
|
||||
<div class="w-full min-h-screen flex justify-center">
|
||||
<div class="flex justify-center items-center flex-col w-9/12 lg:w-2/5">
|
||||
<!-- Header With Logo -->
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="flex items-center w-full justify-center mb-4">
|
||||
<img
|
||||
src="/logo-192.png"
|
||||
alt="ClassroomIO logo"
|
||||
height="50"
|
||||
width="50"
|
||||
data-atf="1"
|
||||
/>
|
||||
<h4 class="text-xl">ClassroomIO</h4>
|
||||
</div>
|
||||
|
||||
<!-- Loggedin Email -->
|
||||
<div
|
||||
class="w-64 flex items-center justify-center mb-6 py-6 bg-gray-100 rounded-2xl border border-gray-300"
|
||||
>
|
||||
<UserProfileIcon />
|
||||
<p class="text-sm ml-2">{$profile.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-container overflow-y-auto w-full pl-2">
|
||||
{#if step === 1}
|
||||
<!-- Role Question -->
|
||||
<div id="role-question" class="flex items-start flex-col mb-6">
|
||||
<!-- Full name questionn -->
|
||||
<TextField
|
||||
label="Full Name"
|
||||
bind:value={fields.fullname}
|
||||
type="text"
|
||||
placeholder="e.g Joke Silva"
|
||||
className="mb-5 w-7/12"
|
||||
labelClassName="text-lg font-normal"
|
||||
errorMessage={errors.fullname}
|
||||
{#if $profile.id}
|
||||
<div class="w-full min-h-screen flex justify-center">
|
||||
<div class="flex justify-center items-center flex-col w-9/12 max-w-md">
|
||||
<!-- Header With Logo -->
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="flex items-center w-full justify-center mb-4">
|
||||
<img
|
||||
src="/logo-192.png"
|
||||
alt="ClassroomIO logo"
|
||||
height="50"
|
||||
width="50"
|
||||
data-atf="1"
|
||||
/>
|
||||
|
||||
<!-- Roal question -->
|
||||
<div>
|
||||
<p class="text-lg font-normal mb-2">What's your main goal today?</p>
|
||||
<p class="text-sm mb-6">
|
||||
This will help us personalize your experience on Classroomio
|
||||
</p>
|
||||
|
||||
<!-- Roles -->
|
||||
<div class="flex items-center">
|
||||
{#each roles as role, index}
|
||||
<button
|
||||
class="w-52 h-44 p-4 {index === 0 &&
|
||||
'mr-4'} flex flex-col justify-between bg-gray-200 rounded-md {fields.role ===
|
||||
role &&
|
||||
'border-2 border-blue-700'} hover:shadow-xl hover:border-blue-700 transition delay-150 duration-300 ease-in-out"
|
||||
type="button"
|
||||
on:click={() => (fields.role = role)}
|
||||
>
|
||||
<div class="flex flex-col items-end w-full">
|
||||
{#if fields.role === role}
|
||||
<CheckmarkFilled20 class="text-blue-700" />
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-lg text-left capitalize">{role}</h4>
|
||||
<p class="text-sm text-black-200">
|
||||
{role === OnboardingRoles.Educator ? 'Teach' : 'Learn'} on
|
||||
ClassroomIO
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{#if errors.role}
|
||||
<p class="text-sm text-red-500">{errors.role}</p>
|
||||
{/if}
|
||||
</div>
|
||||
<h4 class="text-xl">ClassroomIO</h4>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Goal/Source Question -->
|
||||
<div id="goal-question" class="flex items-center flex-col mb-6">
|
||||
<div class="w-10/12">
|
||||
<!-- Goal Question -->
|
||||
<div class="w-full flex items-start flex-col justify-between mb-10">
|
||||
<label for="text-field" class="m-0 text-lg font-normal mb-3">
|
||||
What brings you to ClassroomIO?
|
||||
</label>
|
||||
|
||||
<!-- Loop through Goals -->
|
||||
{#each goals as goal}
|
||||
<label class="w-full inline-flex items-center mb-1 font-light">
|
||||
<input
|
||||
type="radio"
|
||||
bind:group={fields.goal}
|
||||
name="goal"
|
||||
value={goal.value}
|
||||
class="mr-2"
|
||||
/>
|
||||
{goal.label}
|
||||
<!-- Loggedin Email -->
|
||||
<div
|
||||
class="w-64 flex items-center justify-center mb-6 py-6 bg-gray-100 rounded-2xl border border-gray-300"
|
||||
>
|
||||
<UserProfileIcon />
|
||||
<p class="text-sm ml-2">{$profile.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-container overflow-y-auto w-full px-2">
|
||||
{#if step === 1}
|
||||
<!-- Name/Organization Question -->
|
||||
<div id="role-question" class="flex items-start flex-col mb-6">
|
||||
<!-- Full name -->
|
||||
<TextField
|
||||
label="Full Name"
|
||||
bind:value={fields.fullname}
|
||||
type="text"
|
||||
placeholder="e.g Joke Silva"
|
||||
className="mb-5 w-full"
|
||||
labelClassName="text-lg font-normal"
|
||||
errorMessage={errors.fullname}
|
||||
/>
|
||||
|
||||
<!-- Org name -->
|
||||
<TextField
|
||||
label="Name of Organization"
|
||||
bind:value={fields.orgName}
|
||||
type="text"
|
||||
placeholder="e.g Education For All"
|
||||
className="mb-5 w-full"
|
||||
labelClassName="text-lg font-normal"
|
||||
errorMessage={errors.orgName}
|
||||
onChange={() => (isSiteNameTouched = true)}
|
||||
/>
|
||||
|
||||
<!-- Org Site Name -->
|
||||
<TextField
|
||||
label="Organisation Site name"
|
||||
helperMessage={`https://${fields.siteName || ''}.classroomio.com`}
|
||||
bind:value={fields.siteName}
|
||||
type="text"
|
||||
placeholder="e.g edforall"
|
||||
className="mb-5 w-full"
|
||||
labelClassName="text-lg font-normal"
|
||||
errorMessage={errors.siteName}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Goal/Source Question -->
|
||||
<div id="goal-question" class="flex items-center flex-col mb-6">
|
||||
<div class="w-10/12">
|
||||
<!-- Goal Question -->
|
||||
<div
|
||||
class="w-full flex items-start flex-col justify-between mb-10"
|
||||
>
|
||||
<label for="text-field" class="m-0 text-lg font-normal mb-3">
|
||||
What brings you to ClassroomIO?
|
||||
</label>
|
||||
{/each}
|
||||
<!-- Goal: Error message -->
|
||||
{#if errors.goal}
|
||||
<p class="text-sm text-red-500">{errors.goal}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Source Question -->
|
||||
<div class="w-full flex items-start flex-col justify-between">
|
||||
<label for="text-field" class="m-0 text-lg font-normal mb-3">
|
||||
How did you hear about us?
|
||||
</label>
|
||||
<!-- Loop through Goals -->
|
||||
{#each educatorGoals as goal}
|
||||
<label
|
||||
class="w-full inline-flex items-center mb-1 font-light"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
bind:group={fields.goal}
|
||||
name="goal"
|
||||
value={goal.value}
|
||||
class="mr-2"
|
||||
/>
|
||||
{goal.label}
|
||||
</label>
|
||||
{/each}
|
||||
<!-- Goal: Error message -->
|
||||
{#if errors.goal}
|
||||
<p class="text-sm text-red-500">{errors.goal}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Loop through Goals -->
|
||||
{#each sources as source}
|
||||
<label class="w-full inline-flex items-center mb-1 font-light">
|
||||
<input
|
||||
type="radio"
|
||||
bind:group={fields.source}
|
||||
name="source"
|
||||
value={source.value}
|
||||
class="mr-2"
|
||||
/>
|
||||
{source.label}
|
||||
<!-- Source Question -->
|
||||
<div class="w-full flex items-start flex-col justify-between">
|
||||
<label for="text-field" class="m-0 text-lg font-normal mb-3">
|
||||
How did you hear about us?
|
||||
</label>
|
||||
{/each}
|
||||
<!-- Goal: Error message -->
|
||||
{#if errors.source}
|
||||
<p class="text-sm text-red-500">{errors.source}</p>
|
||||
{/if}
|
||||
|
||||
<!-- Loop through Goals -->
|
||||
{#each sources as source}
|
||||
<label
|
||||
class="w-full inline-flex items-center mb-1 font-light"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
bind:group={fields.source}
|
||||
name="source"
|
||||
value={source.value}
|
||||
class="mr-2"
|
||||
/>
|
||||
{source.label}
|
||||
</label>
|
||||
{/each}
|
||||
<!-- Goal: Error message -->
|
||||
{#if errors.source}
|
||||
<p class="text-sm text-red-500">{errors.source}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="flex justify-between items-center mt-8 w-full">
|
||||
<div class="w-24 h-2 bg-gray-300 relative">
|
||||
<span
|
||||
class="progress absolute top-0 left-0 bg-blue-700 h-full"
|
||||
style="width: {progress}%;"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
{#if step > 1}
|
||||
<PrimaryButton
|
||||
label="Back"
|
||||
variant={VARIANTS.NONE}
|
||||
className="py-3 px-6 mr-2 text-blue-700"
|
||||
onClick={() => (step = step - 1)}
|
||||
/>
|
||||
{/if}
|
||||
<PrimaryButton
|
||||
label="Continue"
|
||||
type="button"
|
||||
className="px-5 py-3"
|
||||
isLoading={loading}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="flex justify-between items-center mt-8 w-full">
|
||||
<div class="w-24 h-2 bg-gray-300 relative">
|
||||
<span
|
||||
class="progress absolute top-0 left-0 bg-blue-700 h-full"
|
||||
style="width: {progress}%;"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
{#if step > 1}
|
||||
<PrimaryButton
|
||||
label="Back"
|
||||
variant={VARIANTS.NONE}
|
||||
className="py-3 px-6 mr-2 text-blue-700"
|
||||
onClick={() => (step = step - 1)}
|
||||
/>
|
||||
{/if}
|
||||
<PrimaryButton
|
||||
label="Continue"
|
||||
variant={VARIANTS.CONTAINED}
|
||||
type="button"
|
||||
className="px-5 py-3"
|
||||
isLoading={loading}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.form-container {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Audience Page
|
||||
@@ -0,0 +1,90 @@
|
||||
<script context="module">
|
||||
import { profile } from '../../../utils/store/user';
|
||||
import { fetchCourses } from '../../../components/Courses/api';
|
||||
|
||||
export async function preload(page, params) {
|
||||
const parsedUser = JSON.parse(params.user);
|
||||
|
||||
// if (parsedUser && parsedUser.id) {
|
||||
// return fetchCourses(parsedUser.id);
|
||||
// }
|
||||
|
||||
return {
|
||||
allCourses: [],
|
||||
organizationId: null,
|
||||
cantFetch: true,
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import PageNav from '../../../components/PageNav/index.svelte';
|
||||
import Courses from '../../../components/Courses/index.svelte';
|
||||
import NewCourseModal from '../../../components/Courses/components/NewCourseModal/index.svelte';
|
||||
import PrimaryButton from '../../../components/PrimaryButton/index.svelte';
|
||||
import {
|
||||
courses,
|
||||
createCourseModal,
|
||||
courseMetaDeta,
|
||||
} from '../../../components/Courses/store';
|
||||
import { setProfileIdOfGroupMember } from '../../../utils/services/courses';
|
||||
|
||||
export let allCourses = [];
|
||||
export let cantFetch;
|
||||
|
||||
async function getCourses(userId) {
|
||||
if (process.browser) {
|
||||
if (cantFetch && typeof cantFetch === 'boolean' && !allCourses.length) {
|
||||
$courseMetaDeta.isLoading = true;
|
||||
|
||||
const updatedProfile = await setProfileIdOfGroupMember(
|
||||
$profile.email,
|
||||
userId
|
||||
);
|
||||
console.log(`updatedProfile`, updatedProfile);
|
||||
const coursesResult = await fetchCourses(userId);
|
||||
console.log(`coursesResult`, coursesResult);
|
||||
|
||||
$courseMetaDeta.isLoading = false;
|
||||
if (!coursesResult) return;
|
||||
|
||||
// organizationId = coursesResult.organizationId;
|
||||
courses.set(coursesResult.allCourses);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
courses.set(allCourses);
|
||||
});
|
||||
|
||||
$: getCourses($profile.id);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Courses - ClassroomIO</title>
|
||||
</svelte:head>
|
||||
|
||||
<section class="w-full">
|
||||
<div class="py-10 px-5">
|
||||
<div class="flex items-center justify-between mb-10">
|
||||
<h1 class="text-3xl font-bold">Courses</h1>
|
||||
<PrimaryButton
|
||||
label="Create Course"
|
||||
onClick={() => ($createCourseModal.open = true)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<NewCourseModal />
|
||||
|
||||
<Courses />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.root {
|
||||
width: 100%;
|
||||
padding: 0 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,398 @@
|
||||
<script context="module">
|
||||
export async function preload({ params }) {
|
||||
return {
|
||||
orgName: params.slug,
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
// import { profile } from '../../utils/store/user';
|
||||
// import { supabase } from '../../utils/functions/supabase';
|
||||
import { goto } from '@sapper/app';
|
||||
import PrimaryButton from '../../../components/PrimaryButton/index.svelte';
|
||||
import Avatar from '../../../components/Avatar/index.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { InlineCalendar, Datepicker } from 'svelte-calendar';
|
||||
import CheckmarkFilled20 from 'carbon-icons-svelte/lib/CheckmarkFilled20';
|
||||
|
||||
import {
|
||||
formatUserUpcomingData,
|
||||
formatDate,
|
||||
} from '../../../utils/functions/routes/dashboard';
|
||||
import { user } from '../../../utils/store/user';
|
||||
import { fetchUserUpcomingData } from '../../../utils/services/dashboard';
|
||||
import { isMobile } from '../../../utils/store/useMobile';
|
||||
import type {
|
||||
UserLessonDataType,
|
||||
LessonsByMonthIndexType,
|
||||
} from '../../../utils/types';
|
||||
|
||||
export let orgName: string;
|
||||
|
||||
function createCourse() {
|
||||
goto(`/org/${orgName}/courses?create=true`);
|
||||
}
|
||||
|
||||
const theme = {
|
||||
calendar: {
|
||||
width: '300px',
|
||||
height: '300px',
|
||||
shadow: '0px 0px 5px rgba(0, 0, 0, 0.25)',
|
||||
},
|
||||
};
|
||||
|
||||
let store;
|
||||
let calendar: HTMLElement | null = null;
|
||||
let lessonsByMonth: LessonsByMonthIndexType = {};
|
||||
let prevSelectedDate: string = new Date().toUTCString();
|
||||
let selectedDateLessonData: UserLessonDataType[] | [] = [];
|
||||
|
||||
const boxes = [
|
||||
{
|
||||
label: 'Revenue (NGN)',
|
||||
value: '0.00',
|
||||
},
|
||||
{
|
||||
label: 'Number of sales',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: 'Number of courses',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: 'Total students',
|
||||
value: 0,
|
||||
},
|
||||
];
|
||||
const activities = [
|
||||
{
|
||||
avatar:
|
||||
'https://lh3.googleusercontent.com/ogw/AOh-ky0MLuNJZQa6E_6mm3eZtLW2dmTIhriSgzQlRLA9=s64-c-mo',
|
||||
name: 'Nnancy Okoye',
|
||||
time: '1 hour ago',
|
||||
description: 'Submitted an assignment for lesson Figma prototyping',
|
||||
link: '/',
|
||||
},
|
||||
{
|
||||
avatar:
|
||||
'https://lh3.googleusercontent.com/ogw/AOh-ky0MLuNJZQa6E_6mm3eZtLW2dmTIhriSgzQlRLA9=s64-c-mo',
|
||||
name: 'Bro Shagi',
|
||||
time: '2 days ago',
|
||||
description: 'Submitted an assignment for lesson Figma prototyping',
|
||||
link: '/',
|
||||
},
|
||||
{
|
||||
avatar:
|
||||
'https://lh3.googleusercontent.com/ogw/AOh-ky0MLuNJZQa6E_6mm3eZtLW2dmTIhriSgzQlRLA9=s64-c-mo',
|
||||
name: 'Bill Gates',
|
||||
time: '13 hours ago',
|
||||
description: 'Submitted an assignment for lesson Figma prototyping',
|
||||
link: '/',
|
||||
},
|
||||
{
|
||||
avatar:
|
||||
'https://lh3.googleusercontent.com/ogw/AOh-ky0MLuNJZQa6E_6mm3eZtLW2dmTIhriSgzQlRLA9=s64-c-mo',
|
||||
name: 'Steve Jobs',
|
||||
time: 'yesterday',
|
||||
description: 'Submitted an assignment for lesson Figma prototyping',
|
||||
link: '/',
|
||||
},
|
||||
];
|
||||
|
||||
const addDotsToCalendar = (currentMonthIndexInRenderedMonth: number) =>
|
||||
setTimeout(() => {
|
||||
if (!calendar) calendar = document.getElementById('calendar');
|
||||
// console.log(
|
||||
// `currentMonthIndexInRenderedMonth`,
|
||||
// currentMonthIndexInRenderedMonth
|
||||
// );
|
||||
|
||||
const renderedMonths =
|
||||
calendar?.querySelectorAll('.contents .container .stage .grid .grid') ||
|
||||
[];
|
||||
// console.log(`renderedMonths`, renderedMonths);
|
||||
const currentMonthIndex = new Date($store?.selected).getMonth();
|
||||
const currentMonth = renderedMonths[currentMonthIndexInRenderedMonth];
|
||||
// console.log(`currentMonth`, currentMonth);
|
||||
// All elements for each day in the month - with prev or next month
|
||||
const currentMonthElements =
|
||||
currentMonth?.querySelectorAll<HTMLElement>('a:not(.outsider)') || [];
|
||||
// console.log(`currentMonthElements`, currentMonthElements, '\n\n');
|
||||
const currentMonthData = lessonsByMonth[currentMonthIndex];
|
||||
if (!currentMonthData) return;
|
||||
|
||||
for (let i in currentMonthElements) {
|
||||
if (!currentMonthElements.hasOwnProperty(i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const dayElement = currentMonthElements[i];
|
||||
dayElement.style.position = 'relative';
|
||||
|
||||
// The date is inside the element like <a>2</a>
|
||||
const dateIndex = Number(dayElement.innerHTML);
|
||||
if (!!currentMonthData[dateIndex]) {
|
||||
// console.log(`dateIndex`, dateIndex);
|
||||
// console.log(`lesson data`, currentMonthData[dateIndex]);
|
||||
dayElement.insertAdjacentHTML(
|
||||
'beforeend',
|
||||
'<span class="active-day">•</span>'
|
||||
);
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
|
||||
async function runFirstThings(currentSession: { id: string } | null) {
|
||||
if (!currentSession || !currentSession.id) return;
|
||||
|
||||
const userUpcomingData = await fetchUserUpcomingData(currentSession.id);
|
||||
|
||||
lessonsByMonth = formatUserUpcomingData(userUpcomingData);
|
||||
|
||||
// Only for desktop. Only run on mobile when the user clicks on the date
|
||||
if (!$isMobile) {
|
||||
addDotsToCalendar(1);
|
||||
}
|
||||
}
|
||||
|
||||
function getDataOfSelectedDate(
|
||||
_selectedDate: string
|
||||
): UserLessonDataType[] | [] {
|
||||
if (!_selectedDate) return [];
|
||||
|
||||
const selectedDate = new Date(_selectedDate).toUTCString();
|
||||
|
||||
const monthIndex = new Date(selectedDate).getMonth();
|
||||
const dateIndex = new Date(selectedDate).getDate();
|
||||
|
||||
// If we changed the month, update the calendar dots
|
||||
const prevMonthIndex = new Date(prevSelectedDate).getMonth();
|
||||
if (prevMonthIndex !== monthIndex) {
|
||||
const currentMonthIndexInRenderedMonth =
|
||||
monthIndex < prevMonthIndex ? 1 : 2;
|
||||
|
||||
addDotsToCalendar(currentMonthIndexInRenderedMonth);
|
||||
}
|
||||
|
||||
prevSelectedDate = selectedDate;
|
||||
|
||||
const monthLessonData = lessonsByMonth[monthIndex];
|
||||
if (!monthLessonData) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const dateLessonData = monthLessonData[dateIndex];
|
||||
if (!dateLessonData) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return dateLessonData;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
// Remove default shouldEnlargeDay=true when using <DatePicker /> on mobile
|
||||
if ($store && $store.shouldEnlargeDay) {
|
||||
$store.shouldEnlargeDay = false;
|
||||
}
|
||||
});
|
||||
|
||||
// This is here not called onMount because we want to call it as soon as we have the user's `currentSession` value
|
||||
$: runFirstThings($user.currentSession);
|
||||
|
||||
// Every time the date changes
|
||||
$: selectedDateLessonData = getDataOfSelectedDate($store?.selected);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Dashboard - ClassroomIO</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="py-10 px-5">
|
||||
<div class="flex items-center justify-between mb-10">
|
||||
<h1 class="text-3xl font-bold">Dashboard</h1>
|
||||
<PrimaryButton label="Create Course" onClick={createCourse} />
|
||||
</div>
|
||||
|
||||
<div class="flex items-start flex-wrap">
|
||||
{#each boxes as box}
|
||||
<div
|
||||
class="box flex flex-col rounded border border-gray-200 justify-center px-5 mr-5 mb-5"
|
||||
>
|
||||
<p class="mb-2 text-sm">{box.label}</p>
|
||||
<p class="text-xl font-bold">{box.value}</p>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap">
|
||||
<!-- Your Schedule -->
|
||||
<div>
|
||||
<p class="font-bold mb-7">Your Schedule</p>
|
||||
<div class="rounded border w-2/5 border-gray-200 mr-5 schedule-box">
|
||||
{#if !$isMobile}
|
||||
<div id="calendar" class="flex justify-center mt-5">
|
||||
<InlineCalendar bind:store {theme} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $isMobile}
|
||||
<div class="header w-full flex items-center">
|
||||
<Datepicker bind:store let:key let:send let:receive>
|
||||
<button
|
||||
class="text-lg font-bold text-blue-700 p-3 hover:bg-gray-300 rounded-md"
|
||||
in:receive|local={{ key }}
|
||||
out:send|local={{ key }}
|
||||
on:click={() => {
|
||||
addDotsToCalendar(1);
|
||||
}}
|
||||
>
|
||||
{formatDate($store?.selected)}
|
||||
</button>
|
||||
</Datepicker>
|
||||
</div>
|
||||
{:else}
|
||||
<p class="font-bold m-5">
|
||||
{formatDate($store?.selected)}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
{#each selectedDateLessonData as lessonData}
|
||||
<div class="flex items-center justify-between p-5 lesson-data">
|
||||
<div class="w-4/5">
|
||||
<a
|
||||
class="text-black-700 text-lg font-bold flex items-center"
|
||||
href={`/courses/${lessonData.course_id}/lessons/${
|
||||
lessonData.is_complete ? lessonData.lesson_id : ''
|
||||
}`}
|
||||
>
|
||||
{lessonData.lesson_title}
|
||||
{#if lessonData.is_complete}
|
||||
<span class="ml-2 success">
|
||||
<CheckmarkFilled20 class="carbon-icon" />
|
||||
</span>
|
||||
{:else}
|
||||
<span class="text-md ml-2">🔒</span>
|
||||
{/if}
|
||||
</a>
|
||||
<p class="text-grey text-sm flex items-center">
|
||||
<a
|
||||
class="underline text-blue-700 my-2"
|
||||
href="/courses/{lessonData.course_id}"
|
||||
>
|
||||
{` ${lessonData.course_title}`}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{#if lessonData.is_complete}
|
||||
<a
|
||||
class="join-call rounded-3xl bg-blue-600 text-white py-3 font-bold shadow-lg {!lessonData.call_url &&
|
||||
'opacity-50 pointer-events-none cursor-not-allowed'}"
|
||||
href={!!lessonData.call_url ? lessonData.call_url : undefined}
|
||||
target="_blank"
|
||||
title={!!lessonData.call_url
|
||||
? 'Click to join the call'
|
||||
: 'No call link was added for this lesson. Ask your trainer'}
|
||||
>
|
||||
Join call
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<p class="flex items-center justify-center w-full no-data">
|
||||
No lesson on this day
|
||||
</p>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Your Activities -->
|
||||
<div>
|
||||
<p class="font-bold mb-7">Your Activities</p>
|
||||
<div class="rounded border border-gray-200 w-5 activities-box py-4 px-5">
|
||||
{#each activities as activity}
|
||||
<div class="flex mb-3 pb-3 border-b border-gray-200">
|
||||
<Avatar src={activity.avatar} name="avatar" />
|
||||
<div class="ml-2 flex">
|
||||
<div class="mr-2">
|
||||
<p>{activity.name}</p>
|
||||
<p class="mb-2 mt-1">
|
||||
{activity.description}
|
||||
</p>
|
||||
<a href={activity.link}>View</a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p>{activity.time}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.root {
|
||||
min-height: 93vh;
|
||||
}
|
||||
|
||||
.box {
|
||||
width: 246px;
|
||||
height: 165px;
|
||||
}
|
||||
.schedule-box,
|
||||
.activities-box {
|
||||
min-width: 450px;
|
||||
height: 516px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.calendar-root {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.calendar-info {
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.header {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
min-height: 61px;
|
||||
}
|
||||
|
||||
.lesson-data {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.no-data {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.join-call {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
max-height: 48px;
|
||||
width: 110px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:global(span.active-day) {
|
||||
position: absolute;
|
||||
bottom: -8px;
|
||||
left: 40%;
|
||||
color: var(--main-primary-color);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
:global(span.active-day) {
|
||||
font-size: 30px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1 @@
|
||||
settings
|
||||
@@ -0,0 +1 @@
|
||||
Sites Page
|
||||
@@ -0,0 +1,7 @@
|
||||
export default (currency: string | undefined) => {
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: currency,
|
||||
minimumFractionDigits: 2,
|
||||
});
|
||||
};
|
||||
@@ -28,10 +28,8 @@ const resetValidationSchema = z.object({
|
||||
const onboardingValidationSchema = {
|
||||
stepOne: z.object({
|
||||
fullname: z.string().min(1, { message: 'Enter your fullname' }),
|
||||
role: z.string({
|
||||
required_error: 'Please select a goal',
|
||||
invalid_type_error: 'Please select a goal',
|
||||
}),
|
||||
orgName: z.string().min(1, { message: 'Enter your organisation name' }),
|
||||
siteName: z.string().min(1, { message: 'Enter your site name' }),
|
||||
}),
|
||||
stepTwo: z.object({
|
||||
goal: z
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { supabase } from '../../functions/supabase';
|
||||
import { orgs, currentOrg } from '../../store/org';
|
||||
|
||||
export async function getOrganizations(userId) {
|
||||
const { data, error } = await supabase
|
||||
.from('organizationmember')
|
||||
.select(
|
||||
`
|
||||
id,
|
||||
profile_id,
|
||||
organization (
|
||||
id,
|
||||
name,
|
||||
siteName
|
||||
)
|
||||
`
|
||||
)
|
||||
.eq('profile_id', userId);
|
||||
|
||||
const orgsArray = [];
|
||||
if (data.length) {
|
||||
data.forEach((orgMember) => {
|
||||
orgsArray.push({
|
||||
id: orgMember?.organization?.id,
|
||||
name: orgMember?.organization?.name,
|
||||
siteName: orgMember?.organization?.siteName,
|
||||
});
|
||||
});
|
||||
|
||||
orgs.set(orgsArray);
|
||||
currentOrg.set(orgsArray[0]);
|
||||
}
|
||||
|
||||
return {
|
||||
orgs: orgsArray,
|
||||
currentOrg: orgsArray[0],
|
||||
error,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const orgs = writable([]);
|
||||
export const currentOrg = writable({});
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
@@ -0,0 +1,3 @@
|
||||
# Supabase
|
||||
.branches
|
||||
.temp
|
||||
@@ -0,0 +1,71 @@
|
||||
# A string used to distinguish different Supabase projects on the same host. Defaults to the working
|
||||
# directory name when running `supabase init`.
|
||||
project_id = "classroomio"
|
||||
|
||||
[api]
|
||||
# Port to use for the API URL.
|
||||
port = 54321
|
||||
# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API
|
||||
# endpoints. public and storage are always included.
|
||||
schemas = []
|
||||
# Extra schemas to add to the search_path of every request.
|
||||
extra_search_path = ["extensions"]
|
||||
# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size
|
||||
# for accidental or malicious requests.
|
||||
max_rows = 1000
|
||||
|
||||
[db]
|
||||
# Port to use for the local database URL.
|
||||
port = 54322
|
||||
# The database major version to use. This has to be the same as your remote database's. Run `SHOW
|
||||
# server_version;` on the remote database to check.
|
||||
major_version = 12
|
||||
|
||||
[studio]
|
||||
# Port to use for Supabase Studio.
|
||||
port = 54323
|
||||
|
||||
# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they
|
||||
# are monitored, and you can view the emails that would have been sent from the web interface.
|
||||
[inbucket]
|
||||
# Port to use for the email testing server web interface.
|
||||
port = 54324
|
||||
smtp_port = 54325
|
||||
pop3_port = 54326
|
||||
|
||||
[storage]
|
||||
# The maximum file size allowed (e.g. "5MB", "500KB").
|
||||
file_size_limit = "50MiB"
|
||||
|
||||
[auth]
|
||||
# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used
|
||||
# in emails.
|
||||
site_url = "http://localhost:3000"
|
||||
# A list of *exact* URLs that auth providers are permitted to redirect to post authentication.
|
||||
additional_redirect_urls = ["https://localhost:3000"]
|
||||
# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 seconds (one
|
||||
# week).
|
||||
jwt_expiry = 3600
|
||||
# Allow/disallow new user signups to your project.
|
||||
enable_signup = true
|
||||
|
||||
[auth.email]
|
||||
# Allow/disallow new user signups via email to your project.
|
||||
enable_signup = true
|
||||
# If enabled, a user will be required to confirm any email change on both the old, and new email
|
||||
# addresses. If disabled, only the new email is required to confirm.
|
||||
double_confirm_changes = true
|
||||
# If enabled, users need to confirm their email address before signing in.
|
||||
enable_confirmations = false
|
||||
|
||||
# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`,
|
||||
# `discord`, `facebook`, `github`, `gitlab`, `google`, `twitch`, `twitter`, `slack`, `spotify`.
|
||||
[auth.external.apple]
|
||||
enabled = false
|
||||
client_id = ""
|
||||
secret = ""
|
||||
# Overrides the default auth redirectUrl.
|
||||
redirect_uri = ""
|
||||
# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure,
|
||||
# or any other third-party OIDC providers.
|
||||
url = ""
|
||||
Reference in New Issue
Block a user