CLASSROOMIO-T-46: Forum CRUD MVP

Co-authored-by: Rotimi Best <[email protected]>

Merge-request: CLASSROOMIO-MR-13
Merged-by: Digdippa Team <[email protected]>
This commit is contained in:
Rotimi Best
2023-05-20 23:53:28 +00:00
committed by Space Cloud
co-authored by Rotimi Best
parent 779827ba46
commit c36a49a1ee
42 changed files with 871 additions and 1573 deletions
-1
View File
@@ -22,7 +22,6 @@
}
.bg-set {
color: #586069;
background-color: #d1d5da80;
}
</style>
@@ -24,7 +24,7 @@
import { course } from '../../store';
import { updateCourse } from '../../../../utils/services/courses';
import { profile } from '../../../../utils/store/user';
import { currentOrg } from '../../../../utils/store/org';
import { currentOrg, currentOrgPath } from '../../../../utils/store/org';
import { isMobile } from '../../../../utils/store/useMobile';
// export let lessonId;
@@ -209,7 +209,7 @@
>
<IconButton
value="toggle"
onClick={() => goto(`/org/${$currentOrg.siteName}/courses`)}
onClick={() => goto(`${$currentOrgPath}/courses`)}
buttonClassName="mx-3"
>
<Home32 class="carbon-icon" />
@@ -16,6 +16,7 @@
import PrimaryButton from '../../../PrimaryButton/index.svelte';
import { VARIANTS } from '../../../PrimaryButton/constants';
import { updateCourse } from '../../../../utils/services/courses';
import generateSlug from '../../../../utils/functions/generateSlug';
export let course;
export let courseId;
@@ -66,13 +67,6 @@
};
}
function generateSlug(title) {
return title
.replaceAll(' ', '-')
.toLowerCase()
.concat(`-${new Date().getTime()}`);
}
async function handleSave() {
loading = true;
course.slug = course.slug || generateSlug(course.title);
+4 -1
View File
@@ -4,6 +4,9 @@
<div class="break-all">
{#each tags as tag}
<a class="text-blue-700 mr-5 text-sm" href="?filter={tag}">#{tag.toLowerCase()}</a>
<a
class="text-blue-700 dark:text-blue-500 mr-5 text-sm"
href="?filter={tag}">#{tag.toLowerCase()}</a
>
{/each}
</div>
+8 -4
View File
@@ -1,5 +1,4 @@
<script>
import { onMount } from 'svelte';
import Box from '../../Box/index.svelte';
import AudienceList from './AudienceList.svelte';
import AudienceEmptyIcon from '../../Icons/AudienceEmptyIcon.svelte';
@@ -7,11 +6,16 @@
import { orgAudience, currentOrg } from '../../../utils/store/org';
let isLoading = false;
onMount(async () => {
async function fetchInitData(orgId) {
if (!orgId) return;
isLoading = true;
await getOrgAudience($currentOrg.id);
await getOrgAudience(orgId);
isLoading = false;
});
}
$: fetchInitData($currentOrg.id);
</script>
<div
@@ -0,0 +1,37 @@
<script>
import Modal from '../../Modal/index.svelte';
import PrimaryButton from '../../PrimaryButton/index.svelte';
import { VARIANTS } from '../../PrimaryButton/constants';
export let onDelete = () => {};
export let onCancel = () => {};
export let open = false;
</script>
<Modal
onClose={() => (open = false)}
bind:open
width="w-96"
modalHeading="Delete comment"
>
<div>
<h1 class="dark:text-white text-lg">
Are you sure you want to delete this comment?
</h1>
<div class="mt-5 flex items-center justify-between">
<PrimaryButton
className="px-6 py-3"
variant={VARIANTS.OUTLINED}
label="No, keep it"
onClick={onCancel}
/>
<PrimaryButton
className="px-6 py-3"
variant={VARIANTS.CONTAINED_DANGER}
label="Yes, delete"
onClick={onDelete}
/>
</div>
</div>
</Modal>
+110
View File
@@ -0,0 +1,110 @@
<script>
import { goto } from '@sapper/app';
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import AddComment20 from 'carbon-icons-svelte/lib/AddComment20';
import Vote from '../../Vote/index.svelte';
import Box from '../../Box/index.svelte';
import CoursesEmptyIcon from '../../Icons/CoursesEmptyIcon.svelte';
import Space from '../../Space/index.svelte';
import { currentOrgPath, currentOrg } from '../../../utils/store/org';
import { supabase } from '../../../utils/functions/supabase';
let discussions = [];
dayjs.extend(relativeTime);
async function fetchCommunityQuestions(orgId) {
if (!orgId) return;
const { data, error } = await supabase
.from('community_question')
.select(
`
organization_id,
title,
votes,
created_at,
slug,
comments:community_answer(count),
author:organizationmember!community_question_author_id_fkey!inner(
profile!inner(fullname)
)
`
)
.eq('organization_id', orgId);
console.log('data', data);
console.log('error', error);
if (error) {
console.error('Error loading community', error);
return goto(`${currentOrgPath}`);
}
discussions =
data?.map((discussion) => ({
title: discussion.title,
slug: discussion.slug,
author: discussion?.author?.profile?.fullname,
comments: discussion.comments?.[0]?.count || 0,
votes: discussion.votes,
createdAt: dayjs(discussion.created_at).fromNow(true),
})) || [];
}
$: fetchCommunityQuestions($currentOrg.id);
</script>
<div
class="flex items-center justify-center lg:justify-start flex-wrap my-4 m-auto border-c rounded bg-gray-100 dark:bg-gray-700"
>
{#each discussions as discussion}
<div class="w-full flex border-bottom-c p-5">
<Vote value={discussion.votes} />
<div class="discussion-topic-author">
<h4>
<a
class="text-black dark:text-white"
rel="prefetch"
href="{$currentOrgPath}/community/{discussion.slug}"
>
{discussion.title}
</a>
</h4>
<span class="text-gray-600 dark:text-gray-100">
{discussion.author} asked {discussion.createdAt}
</span>
</div>
<Space />
<div class="flex items-center">
<AddComment20 />
<span class="ml-1">{discussion.comments}</span>
</div>
</div>
{:else}
<Box>
<CoursesEmptyIcon />
<h3 class="dark:text-white text-2xl my-5">No Questions asked</h3>
<p class="dark:text-white w-1/3 text-center">
Ask a question to the community
</p>
</Box>
{/each}
</div>
<style>
h4 {
font-size: 16px;
font-weight: 900;
word-break: break-word;
overflow-wrap: break-word;
margin: 0;
}
.discussion-topic-author {
margin-right: 20px;
}
.discussion-topic-author span {
font-size: 12px;
}
</style>
@@ -8,7 +8,7 @@
import { supabase } from '../../../utils/functions/supabase';
import { snackbarStore } from '../../Snackbar/store';
import { SNACKBAR_SEVERITY } from '../../Snackbar/constants';
import { currentOrg } from '../../../utils/store/org';
import { currentOrg, currentOrgPath } from '../../../utils/store/org';
import SectionTitle from '../SectionTitle.svelte';
let avatar;
@@ -74,7 +74,7 @@
}
function gotoSetting(pathname) {
goto(`/org/${$currentOrg.siteName}/settings${pathname}`);
goto(`${$currentOrgPath}/settings${pathname}`);
}
</script>
+51 -51
View File
@@ -1,6 +1,7 @@
<script>
import { stores } from '@sapper/app';
import Help from 'carbon-icons-svelte/lib/Help20';
import UserMultipleIcon from 'carbon-icons-svelte/lib/UserMultiple20';
import TextChip from '../../components/Chip/Text.svelte';
import OrgSelector from '../../components/OrgSelector/OrgSelector.svelte';
@@ -9,13 +10,35 @@
import SiteSettingsIcon from '../../components/Icons/SiteSettingsIcon.svelte';
import AudienceIcon from '../../components/Icons/AudienceIcon.svelte';
import Avatar from '../../components/Avatar/index.svelte';
import { currentOrg } from '../../utils/store/org';
import { currentOrg, currentOrgPath } from '../../utils/store/org';
import { profile } from '../../utils/store/user';
let orgSiteName = '';
let activeClass = 'bg-gray-200 dark:bg-gray-500';
let { page } = stores();
const menuItems = [
{
path: '',
label: 'Home',
},
{
path: '/courses',
label: 'Courses',
},
{
path: '/community',
label: 'Community',
},
{
path: '/site',
label: 'Site settings',
},
{
path: '/audience',
label: 'Audience',
},
];
function isActive(pagePath, itemPath) {
const pageLinkItems = pagePath.split('/');
const itemLinkItems = itemPath.split('/');
@@ -26,8 +49,6 @@
return pagePath.includes(itemPath);
}
$: orgSiteName = $currentOrg.siteName;
</script>
<aside class="root bg-gray-100 dark:bg-gray-700 h-full">
@@ -51,55 +72,34 @@
<OrgSelector />
<ul class="my-5">
<a href="/org/{orgSiteName}" class="text-black">
<li
class="flex items-center py-3 px-4 mb-2 rounded hover:bg-gray-200 dark:hover:bg-gray-500 {isActive(
$page.path,
`/org/${orgSiteName}`
) && activeClass}"
>
<HomeIcon />
<p class="dark:text-white ml-2">Home</p>
</li>
</a>
<a href="/org/{orgSiteName}/courses" class="text-black">
<li
class="flex items-center py-3 px-4 mb-2 rounded hover:bg-gray-200 dark:hover:bg-gray-500 {isActive(
$page.path,
`/org/${orgSiteName}/courses`
) && activeClass}"
>
<CourseIcon />
<p class="dark:text-white ml-2">Courses</p>
</li>
</a>
<a href="/org/{orgSiteName}/site" class="text-black">
<li
class="flex items-center py-3 px-4 mb-2 rounded hover:bg-gray-200 dark:hover:bg-gray-500 {isActive(
$page.path,
`/org/${orgSiteName}/site`
) && activeClass}"
>
<SiteSettingsIcon />
<p class="dark:text-white ml-2">Site settings</p>
</li>
</a>
<a href="/org/{orgSiteName}/audience" class="text-black">
<li
class="flex items-center py-3 px-4 mb-2 rounded hover:bg-gray-200 dark:hover:bg-gray-500 {isActive(
$page.path,
`/org/${orgSiteName}/audience`
) && activeClass}"
>
<AudienceIcon />
<p class="dark:text-white ml-2">Audience</p>
</li>
</a>
{#each menuItems as menuItem}
<a href="{$currentOrgPath}{menuItem.path}" class="text-black">
<li
class="flex items-center py-3 px-4 mb-2 rounded hover:bg-gray-200 dark:hover:bg-gray-500 {isActive(
$page.path,
`${$currentOrgPath}${menuItem.path}`
) && activeClass}"
>
{#if menuItem.path === ''}
<HomeIcon />
{:else if menuItem.path === '/courses'}
<CourseIcon />
{:else if menuItem.path === '/site'}
<SiteSettingsIcon />
{:else if menuItem.path === '/community'}
<UserMultipleIcon class="carbon-icon" />
{:else if menuItem.path === '/audience'}
<AudienceIcon />
{/if}
<p class="dark:text-white ml-2">{menuItem.label}</p>
</li>
</a>
{/each}
</ul>
</div>
<span class="flex-grow" />
<ul class="my-5 pb-5 px-4">
<a href="/org" class="text-black">
<a href={$currentOrgPath} class="text-black">
<li
class="flex items-center py-3 px-4 mb-2 rounded hover:bg-gray-200 dark:hover:bg-gray-500"
>
@@ -107,11 +107,11 @@
<p class="dark:text-white ml-2">Help</p>
</li>
</a>
<a href="/org/{orgSiteName}/settings" class="text-black">
<a href="{$currentOrgPath}/settings" class="text-black">
<li
class="flex items-center py-3 px-4 mb-2 rounded hover:bg-gray-200 dark:hover:bg-gray-500 {isActive(
$page.path,
`/org/${orgSiteName}/settings`
`${$currentOrgPath}/settings`
) && activeClass}"
>
<Avatar
+1 -1
View File
@@ -20,7 +20,7 @@
</script>
<div
class="header dark:bg-gray-700 flex items-center justify-between {!disableSticky &&
class="header dark:bg-gray-700 bg-white flex items-center justify-between {!disableSticky &&
'sticky'} {paddingClass} {dynamicRootClass}"
style={overidableStyle}
>
+4 -2
View File
@@ -1,5 +1,6 @@
export const VARIANTS = {
CONTAINED: 'CONTAINED',
CONTAINED_INFO: 'CONTAINED_INFO',
CONTAINED_DANGER: 'CONTAINED_DANGER',
CONTAINED_SUCCESS: 'CONTAINED_SUCCESS',
OUTLINED: 'OUTLINED',
@@ -10,14 +11,15 @@ export const VARIANTS = {
export const VARIANTS_CLASS = {
[VARIANTS.CONTAINED]: 'border-none bg-blue-700 hover:bg-blue-900 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]:
'border border-blue-700 hover:border-black-300 hover:bg-blue-200 text-black',
[VARIANTS.NONE]:
'border-none hover:border-black-300 hover:bg-gray-200 dark:hover:bg-gray-500 text-black',
[VARIANTS.TEXT]:
'text-black hover:border border-t-0 border-l-0 border-r-0 border-black-300',
[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',
};
+4 -3
View File
@@ -16,11 +16,12 @@
</script>
<button
class="{isDisabled &&
'bg-opacity-25 hover:bg-opacity-25 cursor-not-allowed'} flex items-center h-auto {VARIANTS_CLASS[
class="{isDisabled
? 'bg-opacity-25 hover:bg-opacity-25 cursor-not-allowed'
: 'cursor-pointer'} flex items-center h-auto {VARIANTS_CLASS[
isLoading ? VARIANTS.OUTLINED : variant
]} {!disablePadding &&
'py-1 px-5'} rounded-none {className} py-3 w-fit justify-center sm:w-auto {isLoading &&
'py-3 px-5'} rounded-none {className} w-fit justify-center sm:w-auto {isLoading &&
'py-3 border-blue-700'} hover:shadow-xl transition delay-150 duration-300 ease-in-out"
on:click={onClick}
{name}
+20
View File
@@ -6,6 +6,7 @@
export let disable;
export let docId;
export let placeholder = 'Type something...';
export let errorMessage;
const settings = {
modules: {
@@ -74,4 +75,23 @@
<div class="p-2 w-full h-full">
<div bind:this={container} />
{#if errorMessage}
<p class="dark:text-white text-red-500 text-sm">
{errorMessage}
</p>
{/if}
</div>
<style>
:global(.dark .ql-stroke) {
stroke: #fff;
}
:global(.dark .ql-snow .ql-picker) {
color: #fff;
}
:global(.dark .ql-editor.ql-blank::before) {
color: #fff;
}
</style>
+6 -5
View File
@@ -3,13 +3,14 @@
import Chip from '../Chip/index.svelte';
export let value = 0;
function upVote() {
value = value + 1;
}
export let upVote = () => {};
export let disabled = false;
</script>
<div class="vote border-none cursor-pointer" on:click={upVote}>
<div
class="vote border-none {disabled ? 'cursor-not-allowed' : 'cursor-pointer'}"
on:click={disabled ? undefined : upVote}
>
<ArrowUp16 />
<Chip {value} />
</div>
-4
View File
@@ -16,10 +16,6 @@
<svelte:head>
<title>{status}</title>
<link
rel="stylesheet"
href="https://unpkg.com/carbon-components-svelte/css/g90.css"
/>
</svelte:head>
<div class="w-3/5 h-3/5 m-auto flex flex-col items-center justify-center">
+3
View File
@@ -102,6 +102,9 @@
console.error('Error adding user to organisation', error);
} else {
console.log('Success adding user to organisation', data);
const memberId = data?.[0]?.id || '';
$currentOrg.memberId = memberId;
}
if (params.has('redirect')) {
-153
View File
@@ -1,153 +0,0 @@
<!-- <script context="module">
export async function preload(page, { user, config }) {
return { user, config };
}
</script> -->
<script>
import GroupPresentation32 from 'carbon-icons-svelte/lib/GroupPresentation32';
import UserSpeaker32 from 'carbon-icons-svelte/lib/UserSpeaker32';
import Book32 from 'carbon-icons-svelte/lib/Book32';
let borderBottomGrey = 'border-r-0 border-b border-l-0 border-gray-300';
let animate = 'transition delay-75 duration-300 ease-in-out';
let boxes = [
{
label: 'Students',
value: 50,
icon: 'students',
},
{
label: 'Tutors',
value: 10,
icon: 'tutor',
},
{
label: 'Courses',
value: 20,
icon: 'courses',
},
];
let topStudents = [
{
id: 1,
name: 'Brad Traversy',
score: 97.8,
},
{
id: 2,
name: 'Ebuka Jacobs',
score: 96,
},
{
id: 3,
name: 'Ngozi Okonjo Iwuala',
score: 95,
},
{
id: 4,
name: 'Nathaniel Bassey',
score: 80,
},
{
id: 5,
name: 'Barack Obama',
score: 79,
},
];
let topCourses = [
{
title: 'CityBIM Basic Module',
rating: 5,
},
{
title: 'CityBIM Advanced Module',
rating: 4.5,
},
{
title: 'Introduction to ClasroomIO',
rating: 4.2,
},
{
title: 'Advanced Builders Module',
rating: 4.1,
},
{
title: 'Artificial Intelligence - Basics',
rating: 4,
},
];
</script>
<svelte:head>
<title>Admin Dashboard - ClassroomIO</title>
</svelte:head>
<div class="root w-full py-10 bg-gray-100 dark:bg-gray-700">
<div class="flex items-center justify-evenly max-w-6xl m-auto">
{#each boxes as box}
<div
class="bg-white dark:bg-gray-800 rounded-md py-3 px-16 active shadow-md hover:shadow-2xl border-2 hover:border-blue-700 cursor-pointer {animate}"
>
{#if box.icon === 'students'}
<UserSpeaker32 class="carbon-icon" />
{:else if box.icon === 'tutor'}
<GroupPresentation32 class="carbon-icon" />
{:else if box.icon === 'courses'}
<Book32 class="carbon-icon" />
{/if}
<p class="dark:text-white text-xl">{box.label}</p>
<h3 class="dark:text-white text-3xl m-0 my-2">{box.value}</h3>
</div>
{/each}
</div>
<div class="flex items-center justify-evenly mt-16">
<div class="table rounded-md bg-white dark:bg-gray-800 shadow-md">
<div class="flex border-t-0 {borderBottomGrey} p-3">
<h3 class="dark:text-white m-0 py-3 pl-2 text-lg">
Outstanding students
</h3>
</div>
{#each topStudents as topStudent, index}
<div
class="flex items-center p-3 cursor-pointer {borderBottomGrey} hover:bg-gray-100 dark:bg-gray-700"
>
<span class="mr-2">{index + 1}</span>
<p class="dark:text-white text-lg">{topStudent.name}</p>
<span class="flex-grow" />
<p class="dark:text-white text-lg">{topStudent.score}</p>
</div>
{/each}
</div>
<div class="table rounded-md bg-white dark:bg-gray-800 shadow-md">
<div class="flex border-t-0 {borderBottomGrey} p-3">
<h3 class="dark:text-white m-0 py-3 pl-2 text-lg">Top rated courses</h3>
</div>
{#each topCourses as topCourse, index}
<div
class="flex items-center p-3 cursor-pointer {borderBottomGrey} hover:bg-gray-100 dark:bg-gray-700"
>
<span class="mr-2">{index + 1}</span>
<p class="dark:text-white text-lg">{topCourse.title}</p>
<span class="flex-grow" />
<p class="dark:text-white text-lg">{topCourse.rating}</p>
</div>
{/each}
</div>
</div>
</div>
<style>
.root {
min-height: 93vh;
}
.table {
width: 564px;
}
</style>
-399
View File
@@ -1,399 +0,0 @@
import { QUESTION_TYPE } from '../../components/Question/constants';
const ROLE = {
ADMIN: 0,
TUTOR: 1,
STUDENT: 2,
};
const tutors = [
{
id: 1,
text: 'Sergey Semko',
},
{
id: 3,
text: 'Jonathan Nelson',
},
{
id: 4,
text: 'Sasha Pokidin',
},
{
id: 5,
text: 'Natasha Rudenko',
},
{
id: 6,
text: 'Yulia Marushko',
},
];
const people = [
{
name: 'Alexander Pokidin',
title: 'Senior Software Engineer',
avatar: 'https://i.pravatar.cc/150?img=1',
role: ROLE.TUTOR,
telegramLink: 'https://t.me/seed_of_abraham',
mailLink: 'mailto:[email protected]',
},
{
name: 'Andrey Filatov',
title: 'Fullstack Software Engineer',
avatar: 'https://i.pravatar.cc/150?img=2',
role: ROLE.TUTOR,
telegramLink: 'https://t.me/seed_of_abraham',
mailLink: 'mailto:[email protected]',
},
{
name: 'Sergey Semko',
title: 'Senior Software Engineer',
avatar: 'https://i.pravatar.cc/150?img=3',
role: ROLE.TUTOR,
telegramLink: 'https://t.me/seed_of_abraham',
mailLink: 'mailto:[email protected]',
},
{
name: 'Natalia Fedii',
title: 'Project Manager',
avatar: 'https://i.pravatar.cc/150?img=4',
role: ROLE.TUTOR,
telegramLink: 'https://t.me/seed_of_abraham',
mailLink: 'mailto:[email protected]',
},
];
const courses = [
{
id: 1,
title: 'React.js Fundamentals',
status: '',
},
];
// many to many: id, tutor_id, course_id
const course_tutors = [
{
id: 1,
tutor_id: 1,
course_id: 1,
},
];
// id, course_id
const lessons = [
{
id: 1,
course_id: 1,
title: 'Soft skills - Team work',
to: '/courses/1/lessons/1',
tutor: {
userId: 1,
avatar: 'https://picsum.photos/32/32/?random',
name: 'Sergey Semko',
},
resources: [
{
label: 'lesson',
value: 1,
},
{
label: 'quiz',
value: 1,
},
{
label: 'home task',
value: 2,
},
],
date: new Date(),
isComplete: true,
},
{
id: 2,
course_id: 1,
title: 'Introduction to ReactJS',
to: '/courses/1/lessons/2',
tutor: {
userId: 1,
avatar: 'https://picsum.photos/32/32/?random',
name: 'Sergey Semko',
},
resources: [
{
label: 'lesson',
value: 1,
},
{
label: 'quiz',
value: 1,
},
{
label: 'home task',
value: 2,
},
],
date: new Date(),
isComplete: false,
},
{
id: 3,
course_id: 1,
title: 'Components - Functional and Class',
to: '/courses/1/lessons/3',
tutor: {
userId: 1,
avatar: 'https://picsum.photos/32/32/?random',
name: 'Sergey Semko',
},
resources: [
{
label: 'lesson',
value: 1,
},
{
label: 'quiz',
value: 1,
},
{
label: 'home task',
value: 2,
},
],
date: new Date(),
isComplete: false,
},
{
id: 4,
course_id: 1,
title: 'State and Props in components',
to: '/courses/1/lessons/4',
tutor: {
userId: 1,
avatar: 'https://picsum.photos/32/32/?random',
name: 'Sergey Semko',
},
resources: [
{
label: 'lesson',
value: 1,
},
{
label: 'quiz',
value: 1,
},
{
label: 'home task',
value: 2,
},
],
date: new Date(),
isComplete: false,
},
{
id: 5,
course_id: 1,
title: 'Life cycle methods',
to: '/courses/1/lessons/5',
tutor: {
userId: 1,
avatar: 'https://picsum.photos/32/32/?random',
name: 'Sergey Semko',
},
resources: [
{
label: 'lesson',
value: 1,
},
{
label: 'quiz',
value: 1,
},
{
label: 'home task',
value: 2,
},
],
date: new Date(),
isComplete: false,
},
];
const lessonData = [
{
lesson_id: 1,
materials: {
note: `# Introduction\n\nWelcome to this course, we are glad you made it here\n\n## What you'l learn\n\n1. Build powerful, fast, user-friendly and reactive web apps.\n2. Provide amazing user experiences by leveraging the power of JavaScript with ease.\n3. Apply for high-paid jobs or work as a freelancer in one the most-demanded sectors you can find in web dev right now.\n4. Learn all about React Hooks and React Components.`,
slide:
'https://docs.google.com/presentation/d/e/2PACX-1vS7mulzY5v1X_D0Gz68M2sggq2zjHCuoyY5JI07hcsOA4svDV5rxhLD9V3ckO_Rz7TK1YniQJtu0CXM/embed?start=false&loop=false&delayms=3000',
video: 'https://www.youtube.com/embed/luM5uobewhA',
},
exercises: [
{
id: 1,
title: 'Practice Exercise',
},
{
id: 2,
title: 'Home task',
},
],
},
];
// id, lesson_id
const lesson_exercise = [
{
id: 1,
lesson_id: 1,
title: 'Home task 1',
description:
"You will be to answer 10 questions, it isn't timed so you can take your time to answer. You can also continue from where you left off, you don't need to worry cause everything is automatically syncronized in the cloud.",
questions: [
{
id: 'react-founder',
type: QUESTION_TYPE.CHECKBOX,
title: 'Who is the creator of React.js',
options: [
{
id: '1',
value: 'Dan Abrahmov',
},
{
id: '2',
value: 'Google',
},
{
id: '3',
value: 'Facebook',
},
{
id: '4',
value: 'Traversy Media',
},
],
answers: ['3'],
},
{
id: 'vue-founder',
type: QUESTION_TYPE.CHECKBOX,
title: 'Who is the creator of Vue.js',
code: `const name = 'Josh Perez';\nconst element = <h1>Hello, {name}</h1>;\n\nReactDOM.render(\n
element,\,
document.getElementById('root')
)
`,
options: [
{
id: '1',
value: 'Evan Vue',
},
{
id: '2',
value: 'Mark Zukerberg',
},
{
id: '3',
value: 'Prince Charles',
},
{
id: '4',
value: 'Bill Gates',
},
],
answers: ['3'],
},
{
id: 'svelte-founder',
type: QUESTION_TYPE.RADIO,
title: 'Who is the creator of Svelte.js',
options: [
{
id: '1',
value: 'Hillary Svelte',
},
{
id: '2',
value: 'Mircosoft',
},
{
id: '3',
value: 'Elevate',
},
{
id: '4',
value: 'Coding Train',
},
],
answers: ['2'],
},
{
id: 'angular-founder',
type: QUESTION_TYPE.TEXTAREA,
title: 'Who is the creator of Angular.js',
value: '',
},
],
},
];
const exercise_answers = [
{
lesson_id: 1,
exercise_id: 1,
taken_by: {},
answers: {},
scores: {},
currentQuestionIndex: 0,
isFinished: false,
progressValue: 100,
status: 'STATUS.PENDING',
},
];
// const lessonLookup = new Map();
// lessons.forEach((lesson) => {
// lessonLookup.set(`${lesson.id}`, JSON.stringify(lesson));
// });
export function getCourse(id) {
return {
id,
name: 'React.js',
people,
roles: ROLE,
lessons: getLessons(id),
};
}
export function getLessons(courseId) {
return lessons.filter((lesson) => lesson.course_id === courseId);
}
export function getLesson(id) {
return (
lessonData.find((lesson) => lesson.lesson_id === id) || {
lesson_id: null,
materials: {
note: '',
slide_url: '',
video_url: '',
},
exercises: getExercises(id),
}
);
}
export function getExercises(lessonId) {
return lesson_exercise.filter((exercise) => exercise.lesson_id === lessonId);
}
export function getExercise(exerciseId) {
return {
data: lesson_exercise.find((exercise) => exercise.id === exerciseId),
answers: exercise_answers.find(
(answer) => answer.exercise_id === exerciseId
),
};
}
-7
View File
@@ -1,7 +0,0 @@
import { getSupabase } from '../../utils/functions/supabase';
export function post(req, res) {
const supabase = getSupabase();
supabase.auth.api.setAuthCookie(req, res);
}
-27
View File
@@ -1,27 +0,0 @@
import { getCourse, getExercise, getLesson } from './_db';
function writeJsonHead(res, statusCode) {
res.writeHead(statusCode, {
'Content-Type': 'application/json',
});
}
export function get(req, res, next) {
const { id, lessonId, exerciseId } = req.query;
const result = {
...getCourse(parseInt(id, 2)),
};
if (lessonId) {
result.lesson = getLesson(parseInt(lessonId, 2));
}
if (exerciseId) {
result.exercise = getExercise(parseInt(exerciseId, 2));
}
writeJsonHead(res, 200);
return res.end(JSON.stringify(result));
}
-19
View File
@@ -1,19 +0,0 @@
import { getLessons } from './_db';
function writeJsonHead(res, statusCode) {
res.writeHead(statusCode, {
'Content-Type': 'application/json',
});
}
export function get(req, res, next) {
const { id } = req.query;
writeJsonHead(res, 200);
if (id) {
return res.end(JSON.stringify(getLesson(id)));
}
return res.end(JSON.stringify(getLessons()));
}
-15
View File
@@ -1,15 +0,0 @@
function writeJsonHead(res, statusCode) {
res.writeHead(statusCode, {
'Content-Type': 'application/json',
});
}
export function post(req, res) {
const { email } = req.body;
console.log(`req.session`, req.session);
writeJsonHead(res, 200);
return res.end(JSON.stringify({ success: true }));
}
-100
View File
@@ -1,100 +0,0 @@
<script>
import ChevronDown24 from 'carbon-icons-svelte/lib/ChevronDown24';
import TextEditor from '../components/TextEditor/index.svelte';
let questionTitle = '';
let questionDescription = '';
let facultyFilterOpened = false;
const faculties = [
'ИЭКСУ',
'ИМБТ',
'ИМИ',
'ИПТДМ',
'ИИИР',
'ГФ',
'ХТФ',
'ИДЗО',
'УИИ',
'УНИ',
];
let checkbox = faculties.reduce((acc, cur) => {
acc[cur] = false;
return acc;
}, {});
$: clickedTotal = Object.values(checkbox).filter((box) => !!box).length;
function handleFacultyFilters() {
facultyFilterOpened = facultyFilterOpened ? false : true;
}
</script>
<svelte:head>
<title>Спросить собшество вопрос</title>
</svelte:head>
<div class="root lg:w-3/5 mx-auto md:mx-10 lg:mb-20">
<h2 class="pb-2">Спросить собшество вопрос</h2>
<div class="main-title my-1 flex">
<div class="faculty-filter mr-1">
<button
class="flex border rounded-lg border-grey py-3 px-3 focus:outline-none focus:border-gray-400 focus:bg-gray-200 relative"
on:click={handleFacultyFilters}
>
<span class="flex flex-row"
>Факултеты <span>&nbsp;({clickedTotal})</span></span
>
<ChevronDown24 class="ml-2" />
</button>
{#if facultyFilterOpened}
<div
class="w-48 bg-white dark:bg-gray-800 border border-grey rounded-lg mt-2 py-3 absolute"
>
{#each faculties as faculty}
<div class="block px-4 py-3 border-b">
<input
type="checkbox"
class="border border-gray checked:bg-blue-600 checked:border-transparent mr-1 cursor-pointer"
bind:checked={checkbox[faculty]}
/>
<span>{faculty}</span>
</div>
{/each}
</div>
{/if}
</div>
<input
class="title rounded-lg border border-1 border-gray p-2 w-full focus:outline-none"
error="false"
autocomplete="off"
placeholder="Title"
aria-label="Title"
aria-describedby="title-input-validation"
type="text"
name="discussion[title]"
id="discussion_title"
value={questionTitle}
/>
</div>
<TextEditor
value={questionDescription}
placeholder="Задайте вопрос или начните разговор"
/>
</div>
<style>
.root {
max-width: 600px;
margin: 10px auto 0;
}
@media only screen and (max-width: 1002px) {
.faculty-filter {
margin-bottom: 10px;
}
.main-title {
flex-direction: column;
}
}
</style>
-96
View File
@@ -1,96 +0,0 @@
<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="root">
<PageNav title="All courses">
<div slot="widget">
{#if $profile.role === 'educator'}
<PrimaryButton
label="New"
onClick={() => ($createCourseModal.open = true)}
/>
{/if}
</div>
</PageNav>
<NewCourseModal />
<Courses />
</section>
<style>
.root {
width: 100%;
padding: 0 10px;
min-height: 90vh;
/* min-width: 600px;
margin: 0 auto;
border-left: 1px solid var(--border-color);
border-right: 1px solid var(--border-color); */
}
</style>
-229
View File
@@ -1,229 +0,0 @@
<script>
import { stores } from '@sapper/app';
import AddComment20 from 'carbon-icons-svelte/lib/AddComment20';
import Vote from '../components/Vote/index.svelte';
import Space from '../components/Space/index.svelte';
import HashTags from '../components/HashTags/index.svelte';
import PageNav from '../components/PageNav/index.svelte';
function getDiscussion(filterBy, allDiscussions) {
switch (filterBy) {
// case 'popular':
// return allDiscussions.filter((a, i) => i >= 0 && i < 2);
case 'recent':
return allDiscussions.filter((a, i) => i >= 2 && i < 5);
case 'upvoted':
return allDiscussions.filter((a, i) => i > 4 && i < 8);
default:
return allDiscussions;
}
}
const { page } = stores();
let filterBy = '';
let discussions = [];
$: filterBy = $page.query.filter || 'popular';
const _allDiscussions = [
{
id: 1,
title: 'How do I pass Anicimov?',
author: {
name: 'rotimibest',
},
tags: ['ибэит', 'икс'],
answered: false,
comments: 10,
votes: 10,
},
{
id: 2,
title:
'Where do I find the nearest restaurant. Where do I find the nearest restaurant?. Where do I find the nearest restaurant? Where do I find the nearest restaurant?',
author: {
name: 'attronaldo',
},
tags: ['иибрт', 'иее'],
answered: false,
comments: 2,
votes: 2,
},
{
id: 3,
title: 'Who is Nikolenko?',
author: {
name: 'donald',
},
tags: ['ибэит', 'иее', 'иибрт', 'ИПИГ'],
answered: false,
comments: 10,
votes: 10,
},
{
id: 4,
title: 'Archive discussion button',
author: {
name: 'acharlesvv',
},
tags: [
'ИЭКСУ',
'ИМБТ',
'ИМИ',
'ИПТДМ',
'ИИИР',
'ГФ',
'ХТФ',
'ИДЗО',
'УИИ',
'УНИ',
],
answered: false,
comments: 15,
votes: 15,
},
{
id: 1,
title: 'How do I pass Anicimov?',
author: {
name: 'rotimibest',
},
tags: ['ибэит', 'икс'],
answered: false,
comments: 7,
votes: 7,
},
{
id: 2,
title: 'Where do I find the nearest restaurant?',
author: {
name: 'attronaldo',
},
tags: ['иибрт', 'иее'],
answered: false,
comments: 4,
votes: 4,
},
{
id: 3,
title: 'Who is Nikolenko?',
author: {
name: 'donald',
},
tags: ['ибэит', 'иее', 'иибрт', 'ИПИГ'],
answered: false,
comments: 100,
votes: 100,
},
{
id: 4,
title: 'Archive discussion button',
author: {
name: 'acharlesvv',
},
tags: [
'ИЭКСУ',
'ИМБТ',
'ИМИ',
'ИПТДМ',
'ИИИР',
'ГФ',
'ХТФ',
'ИДЗО',
'УИИ',
'УНИ',
],
answered: false,
comments: 5,
votes: 5,
},
];
const allDiscussions = [];
_allDiscussions.forEach((d, i) => {
allDiscussions.push(d, _allDiscussions[_allDiscussions.length - (i + 1)]);
});
$: discussions = getDiscussion(filterBy, allDiscussions);
</script>
<svelte:head>
<title>ClassroomIO - Help students in your university</title>
</svelte:head>
<div class="root md:w-3/4 mx-auto md:mb-20">
<PageNav
title="Talks"
addButtonHref="/ask"
addButtonLabel="Ask"
navItems={[
{
label: 'Popular',
isActive: filterBy === 'popular',
href: '/discussions?filter=popular',
},
{
label: 'Recent',
isActive: filterBy === 'recent',
href: '/discussions?filter=recent',
},
{
label: 'Upvoted',
isActive: filterBy === 'upvoted',
href: '/discussions?filter=upvoted',
},
]}
/>
{#each discussions as discussion}
<div class="discussion-box">
<Vote value={discussion.votes} />
<div class="discussion-topic-author">
<h4 class="dark:text-white">
<a rel="prefetch" href="discussions/{discussion.id}"
>{discussion.title}</a
>
</h4>
<span>{discussion.author.name} asked 1 day ago</span>
<HashTags tags={discussion.tags} />
</div>
<Space />
<div class="flex items-center">
<AddComment20 />
<span>{discussion.comments}</span>
</div>
</div>
{/each}
</div>
<style>
.root {
max-width: 600px;
margin: 0 auto;
border-left: 1px solid var(--border-color);
border-right: 1px solid var(--border-color);
}
.discussion-box {
/* border: 1px solid var(--border-color); */
display: flex;
align-items: flex-start;
padding: 10px 15px;
/* border-radius: 10px; */
margin: 0px auto;
border-bottom: 1px solid #e2e8f0;
}
h4 {
font-size: 16px;
font-weight: 900;
word-break: break-word;
overflow-wrap: break-word;
margin: 0;
}
.discussion-topic-author {
margin-right: 20px;
}
.discussion-topic-author span {
color: #586069;
font-size: 12px;
}
</style>
-30
View File
@@ -1,30 +0,0 @@
import _discussions from './_discussions.js';
const lookup = new Map();
_discussions.forEach((discussion) => {
lookup.set(`${discussion.id}`, JSON.stringify(discussion));
});
export function get(req, res, next) {
// the `id` parameter is available because
// this file is called [id].json.js
const { id } = req.params;
if (lookup.has(id)) {
res.writeHead(200, {
'Content-Type': 'application/json',
});
res.end(lookup.get(id));
} else {
res.writeHead(404, {
'Content-Type': 'application/json',
});
res.end(
JSON.stringify({
message: `Not found`,
})
);
}
}
-108
View File
@@ -1,108 +0,0 @@
<script context="module">
export async function preload({ params }) {
// the `id` parameter is available because
// this file is called [id].svelte
const res = await this.fetch(`discussions/${params.id}.json`);
const data = await res.json();
if (res.status === 200) {
return { discussion: data };
} else {
this.error(res.status, data.message);
}
}
</script>
<script>
import marked from 'marked';
import Vote from '../../components/Vote/index.svelte';
import HashTags from '../../components/HashTags/index.svelte';
import TextEditor from '../../components/TextEditor/index.svelte';
import CheckmarkOutline20 from 'carbon-icons-svelte/lib/CheckmarkOutline20';
export let discussion;
let userComment = '';
</script>
<svelte:head>
<title>{discussion.title}</title>
</svelte:head>
<div class="root lg:w-3/5 mx-auto md:mx-10 lg:mb-20">
<div class="flex items-start">
<Vote value={discussion.votes} />
<h2 class="text-3xl mb-8">{discussion.title}</h2>
</div>
<div class="my-1 px-1 rounded-lg border border-1 border-gray">
<header class="flex items-center justify-between leading-none p-2">
<a
class="flex items-center no-underline hover:underline text-black"
href="/"
>
<img
alt="Placeholder"
class="block rounded-full"
src="https://picsum.photos/32/32/?random"
/>
<p class="dark:text-white ml-2 text-sm">{discussion.author.name}</p>
<p
class="dark:text-white ml-2 text-sm text-gray-500 dark:text-gray-200"
>
12h ago
</p>
</a>
</header>
<section class="prose prose-sm sm:prose p-2">
{@html marked(discussion.question)}
</section>
<section class="p-2">
<HashTags tags={discussion.tags} />
</section>
</div>
<div class="my-8"><strong>{discussion.comments.length} ответы</strong></div>
{#each discussion.comments as comment}
<div class="my-5 px-1 flex items-start">
<Vote value={comment.votes} />
<div class="w-full rounded-lg border border-1 border-gray">
<header class="flex items-center justify-between leading-none p-2">
<a
class="flex items-center no-underline hover:underline text-black"
href="/"
>
<img
alt="Placeholder"
class="block rounded-full"
width="24"
height="20"
src={comment.avatar}
/>
<p class="dark:text-white ml-2 text-sm">{comment.name}</p>
<p
class="dark:text-white ml-2 text-sm text-gray-500 dark:text-gray-200"
>
12h ago
</p>
</a>
<CheckmarkOutline20 />
</header>
<article class="prose prose-sm sm:prose p-2">
{@html marked(comment.comment)}
</article>
</div>
</div>
{/each}
<hr />
<TextEditor value={userComment} placeholder="Предлогайте ваш ответ" />
</div>
<style>
.root {
margin: 15px auto 5px;
max-width: 800px;
}
</style>
-181
View File
@@ -1,181 +0,0 @@
// Ordinarily, you'd generate this data from markdown files in your
// repo, or fetch them from a database of some kind. But in order to
// avoid unnecessary dependencies in the starter template, and in the
// service of obviousness, we're just going to leave it here.
// This file is called `_discussions.js` rather than `posts.js`, because
// we don't want to create an `/blog/posts` route — the leading
// underscore tells Sapper not to do that.
const question = "\ Hey!\n>\n> I'm working on a dashboard app that's where most things are login only and I like using ### Outlined a draft of the flows\n>\n> ### API Proposals\n> #### Through `getServerSideProps`\n> * Add new return property to `getServerSideProps` called i.e. `fetchPolicy` that can default to `always` being current behaviour and add new option `cache-and-network`/`stale-while-revalidate` as outlined above\n> * Add new router API event, called i.e. `routePropsChanged`, that can be triggered when page props change\n> * Maybe it could also send the `stale-while-revalidate` http header to the browser for instant page reloads?\n> * Problem: needs a way to clear cache when mutating to prevent a flash of unchanged content\n>\n> #### Through router\n> * Add new prop to Link/Router called `fetchPolicy: 'cache-and-network'` which could use a cache of the page's data if it exists and warm it up on load (similar to [`shallow`](https://nextjs.org/docs/routing/shallow-routing).)\n> * If `fetchPolicy: 'cache-and-network` is combined with `prefetch` it could actually preload the data on each page as well and make every route navigation change snappy, even when using getServerSideProps`\n> * This removes the \"flash of unchanged content\" on mutations as you can trigger a route change after a mutation w/o using the cache.\n>\n> Later on, this could be extended to allow for polling as well maybe, which could enable some real-time views without the need of any actual client-side query wiring.\n"
export default [
{
title: "What is Sapper?",
id: 1,
votes: 100,
author: {
name: "babdraa",
avatar: "https://avatars3.githubusercontent.com/u/24832873?s=60&v=4"
},
tags: [
'ИЭКСУ', 'ИМБТ', 'ИМИ', 'ИПТДМ', 'ИИИР', 'ГФ', 'ХТФ', 'ИДЗО', 'УИИ', 'УНИ'
],
question: '# Problem\n\nThere is a problem with my code please help me check it out.\n\n```js\nconst name = "dfdf";\n```',
createdAt: Date.now(),
comments: [
{
name: "babdraa",
avatar: "https://avatars3.githubusercontent.com/u/24832873?s=60&v=4",
votes: 5,
comment:
"getStaticProps must always be called from a page located in pages/*"
},
{
name: "crana",
avatar: "https://avatars1.githubusercontent.com/u/31749594?s=60&v=4",
votes: 0,
comment:
" Hello, thank you for reply. Of course I know that :-) I was just thinking if there is some way to do same as getStaticProps for other components too. I don't think such feature is completely necessary, though. Hello, thank you for reply. Of course I know that :-) I was just thinking if there is some way to do same as getStaticProps for other components too. I don't think such feature is completely necessary, though. Hello, thank you for reply. Of course I know that :-) I was just thinking if there is some way to do same as getStaticProps for other components too. I don't think such feature is completely necessary, though. Hello, thank you for reply. Of course I know that :-) I was just thinking if there is some way to do same as getStaticProps for other components too. I don't think such feature is completely necessary, though. Hello, thank you for reply. Of course I know that :-) I was just thinking if there is some way to do same as getStaticProps for other components too. I don't think such feature is completely necessary, though. Hello, thank you for reply. Of course I know that :-) I was just thinking if there is some way to do same as getStaticProps for other components too. I don't think such feature is completely necessary, though. Hello, thank you for reply. Of course I know that :-) I was just thinking if there is some way to do same as getStaticProps for other components too. I don't think such feature is completely necessary, though."
},
{
name: "loris",
avatar: "https://avatars3.githubusercontent.com/u/11896398?s=60&v=4",
votes: 100,
comment:
"getStaticProps must always be called from a page located in pages/*"
}
]
},
{
title: "How to use Sapper",
id: 2,
votes: 100,
author: {
name: "babdraa",
avatar: "https://avatars3.githubusercontent.com/u/24832873?s=60&v=4"
},
tags: [
'ИЭКСУ', 'ИМБТ', 'ИМИ', 'ИПТДМ', 'ИИИР', 'ГФ', 'ХТФ', 'ИДЗО', 'УИИ', 'УНИ'
],
question:
question,
createdAt: Date.now(),
comments: [
{
name: "rawe",
avatar: "https://avatars1.githubusercontent.com/u/31749594?s=60&v=4",
votes: 10,
comment:
"Hello, thank you for reply. Of course I know that :-) I was just thinking if there is some way to do same as getStaticProps for other components too. I don't think such feature is completely necessary, though."
},
{
name: "hlaow",
avatar: "https://avatars3.githubusercontent.com/u/11896398?s=60&v=4",
votes: 5,
comment:
"getStaticProps must always be called from a page located in pages/*"
}
]
},
{
title: "Why the name?",
votes: 100,
id: 3,
author: {
name: "babdraa",
avatar: "https://avatars3.githubusercontent.com/u/24832873?s=60&v=4"
},
tags: [
'ИЭКСУ', 'ИМБТ', 'ИМИ', 'ИПТДМ', 'ИИИР', 'ГФ', 'ХТФ', 'ИДЗО', 'УИИ', 'УНИ'
],
question:
question,
createdAt: Date.now(),
comments: [
{
name: "babdraa",
avatar: "https://avatars3.githubusercontent.com/u/24832873?s=60&v=4",
votes: 50,
comment:
"getStaticProps must always be called from a page located in pages/*"
},
{
name: "crana",
avatar: "https://avatars1.githubusercontent.com/u/31749594?s=60&v=4",
votes: 9,
comment:
"Hello, thank you for reply. Of course I know that :-) I was just thinking if there is some way to do same as getStaticProps for other components too. I don't think such feature is completely necessary, though."
},
{
name: "loris",
avatar: "https://avatars3.githubusercontent.com/u/11896398?s=60&v=4",
votes: 5,
comment:
"getStaticProps must always be called from a page located in pages/*"
}
]
},
{
title: "How is Sapper different from Next.js?",
id: 4,
votes: 10,
author: {
name: "babdraa",
avatar: "https://avatars3.githubusercontent.com/u/24832873?s=60&v=4"
},
tags: [
'ИЭКСУ', 'ИМБТ', 'ИМИ', 'ИПТДМ', 'ИИИР', 'ГФ', 'ХТФ', 'ИДЗО', 'УИИ', 'УНИ'
],
question: question,
createdAt: Date.now(),
comments: [
{
name: "babdraa",
avatar: "https://avatars3.githubusercontent.com/u/24832873?s=60&v=4",
votes: 0,
comment:
"getStaticProps must always be called from a page located in pages/*"
},
{
name: "crana",
avatar: "https://avatars1.githubusercontent.com/u/31749594?s=60&v=4",
votes: 1,
comment:
"Hello, thank you for reply. Of course I know that :-) I was just thinking if there is some way to do same as getStaticProps for other components too. I don't think such feature is completely necessary, though."
},
{
name: "loris",
avatar: "https://avatars3.githubusercontent.com/u/11896398?s=60&v=4",
comment:
"getStaticProps must always be called from a page located in pages/*"
}
]
},
{
title: "How can I get involved?",
id: 5,
votes: 7,
tags: [
'ИЭКСУ', 'ИМБТ', 'ИМИ', 'ИПТДМ', 'ИИИР', 'ГФ', 'ХТФ', 'ИДЗО', 'УИИ', 'УНИ'
],
author: {
name: "living",
avatar: "https://avatars3.githubusercontent.com/u/24832873?s=60&v=4"
},
question: question,
createdAt: Date.now(),
comments: [
{
name: "gbenga",
avatar: "https://avatars3.githubusercontent.com/u/11896398?s=60&v=4",
votes: 1000,
comment:
"getStaticProps must always be called from a page located in pages/*"
}
]
}
];
-15
View File
@@ -1,15 +0,0 @@
function writeJsonHead(res, statusCode) {
res.writeHead(statusCode, {
'Content-Type': 'application/json',
});
}
export function post(req, res) {
const { email } = req.body;
console.log(`req.session`, req.session);
writeJsonHead(res, 200);
return res.end(JSON.stringify({ success: true }));
}
+6 -1
View File
@@ -141,8 +141,13 @@
});
console.log('Create organisation member', data);
const memberId = Array.isArray(data) && data.length ? data[0].id : '';
orgs.set([orgData as never]);
currentOrg.set(orgData);
currentOrg.set({
...orgData,
memberId: memberId,
});
if (error) {
console.log('Error: create organisation member', error);
@@ -0,0 +1,431 @@
<script context="module">
export async function preload({ params }) {
return { slug: params.slug };
}
</script>
<script>
import marked from 'marked';
import dayjs from 'dayjs';
import pluralize from 'pluralize';
import relativeTime from 'dayjs/plugin/relativeTime';
import Delete16 from 'carbon-icons-svelte/lib/Delete16';
import { SkeletonPlaceholder, SkeletonText } from 'carbon-components-svelte';
import ArrowLeft24 from 'carbon-icons-svelte/lib/ArrowLeft24';
import Vote from '../../../../components/Vote/index.svelte';
import PrimaryButton from '../../../../components/PrimaryButton/index.svelte';
import { VARIANTS } from '../../../../components/PrimaryButton/constants';
import TextEditor from '../../../../components/TextEditor/index.svelte';
import Avatar from '../../../../components/Avatar/index.svelte';
import IconButton from '../../../../components/IconButton/index.svelte';
import CheckmarkOutline20 from 'carbon-icons-svelte/lib/CheckmarkOutline20';
import { currentOrgPath, currentOrg } from '../../../../utils/store/org';
import { profile } from '../../../../utils/store/user';
import { supabase } from '../../../../utils/functions/supabase';
import {
askCommunityValidation,
commentInCommunityValidation,
} from '../../../../utils/functions/validator';
import { SNACKBAR_SEVERITY } from '../../../../components/Snackbar/constants';
import { snackbarStore } from '../../../../components/Snackbar/store';
import TextField from '../../../../components/Form/TextField.svelte';
import DeleteCommentModal from '../../../../components/Org/Community/DeleteCommentModal.svelte';
dayjs.extend(relativeTime);
export let slug;
let question;
let comment = '';
let errors = {};
let isValidAnswer = false; // V2 allow admin mark an answer as accepted
let resetInput = 1;
let voted = { question: false, comment: {} };
let isEditMode = false;
let deleteComment = {
shouldDelete: false,
commentId: null,
};
let editContent = {
title: '',
body: '',
};
function mapResToQuestion(data) {
return {
id: data.id,
title: data.title,
votes: data.votes,
author: {
id: data?.author?.profile?.id || '',
name: data?.author?.profile?.fullname || '',
avatar: data?.author?.profile?.avatar_url || '',
},
body: data.body,
createdAt: dayjs(data.created_at).fromNow(true),
comments: data.comments.map((c) => ({
id: c.id,
authorId: c.author?.profile?.id || '',
name: c.author?.profile?.fullname || '',
avatar: c.author?.profile?.avatar_url || '',
votes: c.votes,
comment: c.body,
createdAt: dayjs(c.created_at).fromNow(true),
})),
totalComments: 0,
};
}
async function fetchCommunityQuestion(slug) {
if (!slug) return;
const { data, error } = await supabase
.from('community_question')
.select(
`
id,
title,
body,
votes,
created_at,
comments:community_answer(
id,
body,
votes,
created_at,
author:organizationmember!community_answer_author_id_fkey!inner(
profile!inner(id, fullname, avatar_url)
)
),
author:organizationmember!community_question_author_id_fkey!inner(
profile!inner(id, fullname, avatar_url)
)
`
)
.eq('slug', slug)
.single();
if (error) {
console.error('Error loading community', error);
return goto(`${currentOrgPath}`);
}
question = mapResToQuestion(data);
question.totalComments = question.comments.length;
}
async function submitComment() {
errors = commentInCommunityValidation({ comment });
console.log('submitComment errors', errors);
if (Object.keys(errors).length) {
return;
}
const { data, error } = await supabase.from('community_answer').insert({
id: undefined,
body: comment,
question_id: question.id,
author_id: $currentOrg.memberId,
votes: 0,
});
if (error) {
console.error('Error: commenting', error);
$snackbarStore.open = true;
$snackbarStore.message = 'Error - Please try again later';
$snackbarStore.severity = SNACKBAR_SEVERITY.ERROR;
} else {
console.log('Success: commenting', data);
// Notification
$snackbarStore.open = true;
$snackbarStore.message = 'Success';
$snackbarStore.severity = SNACKBAR_SEVERITY.SUCCESS;
// Add to comment
const _c = data?.[0];
question.comments = [
...question.comments,
{
id: _c.id,
name: $profile?.fullname || '',
avatar: $profile?.avatar_url || '',
votes: 0,
comment: _c.body,
createdAt: dayjs(_c.created_at).fromNow(true),
},
];
// Reset input
comment = '';
resetInput = new Date().getTime();
}
}
async function upvoteQuestion(type, commentId) {
const isQuestion = type === 'question';
if (isQuestion && voted.question) return;
if (!isQuestion && voted.comment[commentId]) return;
const table = isQuestion ? 'community_question' : 'community_answer';
const matchId = isQuestion ? question.id : commentId;
let votes = 0;
if (isQuestion) {
question.votes = question.votes + 1;
votes = question.votes;
} else {
question.comments = question.comments.map((c) => {
if (c.id === commentId) {
c.votes = c.votes + 1;
votes = c.votes;
}
return c;
});
}
const { error } = await supabase
.from(table)
.update({ votes })
.match({ id: matchId });
if (error) {
console.error('Error: upvoteQuestion', error);
$snackbarStore.open = true;
$snackbarStore.message = 'Error - Please try again later';
$snackbarStore.severity = SNACKBAR_SEVERITY.ERROR;
} else {
if (isQuestion) {
voted.question = true;
} else {
voted.comment[commentId] = true;
}
}
}
async function handleQuestionEdit() {
if (isEditMode) {
const fields = { title: editContent.title, body: editContent.body };
errors = askCommunityValidation(fields);
console.log('handleQuestionEdit errors', errors);
if (Object.keys(errors).length) {
return;
}
}
isEditMode = !isEditMode;
if (!isEditMode) {
const fields = { title: editContent.title, body: editContent.body };
errors = askCommunityValidation(fields);
console.log('handleQuestionEdit errors', errors);
if (Object.keys(errors).length) {
return;
}
const { error } = await supabase
.from('community_question')
.update(fields)
.match({ id: question.id });
if (error) {
console.error('Error: handleQuestionEdit', error);
$snackbarStore.open = true;
$snackbarStore.message = 'Error - Please try again later';
$snackbarStore.severity = SNACKBAR_SEVERITY.ERROR;
} else {
question.title = fields.title;
question.body = fields.body;
editContent.title = '';
editContent.body = '';
}
} else {
editContent.title = question.title;
editContent.body = question.body;
}
}
async function handleCommentDelete() {
const { error } = await supabase
.from('community_answer')
.delete()
.match({ id: deleteComment.commentId });
if (!error) {
question.comments = question.comments.filter(
(c) => c.id !== deleteComment.commentId
);
deleteComment.shouldDelete = false;
deleteComment.commentId = null;
}
}
$: fetchCommunityQuestion(slug);
</script>
<svelte:head>
<title>{question?.title || 'Question'}</title>
</svelte:head>
<DeleteCommentModal
bind:open={deleteComment.shouldDelete}
onCancel={() => {
deleteComment.shouldDelete = false;
deleteComment.commentId = null;
}}
onDelete={handleCommentDelete}
/>
<section class="max-w-3xl mx-auto md:mx-10 lg:mb-20">
{#if !question}
<div class="py-10 px-5 mb-3">
<SkeletonText style="width: 25%;" />
<SkeletonText style="width: 100%; margin-bottom: 2rem" />
<SkeletonPlaceholder style="width: 100%; height: 20rem;" />
</div>
{:else}
<div class="py-10 px-5">
<a
class="text-gray-500 dark:text-gray-200 text-md flex items-center"
href={`${$currentOrgPath}/community`}
>
<ArrowLeft24 class="carbon-icon" /> Go Back
</a>
<div class="my-5 flex justify-between items-center">
{#if isEditMode}
<TextField
bind:value={editContent.title}
className="w-full mr-2"
errorMessage={errors.title}
/>
{:else}
<div class="flex items-center">
<Vote
value={question.votes}
upVote={() => upvoteQuestion('question')}
disabled={voted.question}
/>
<h2 class="text-3xl">{question.title}</h2>
</div>
{/if}
{#if question.author.id === $profile.id}
<PrimaryButton
label={isEditMode ? 'Save' : 'Edit'}
variant={VARIANTS.CONTAINED_INFO}
onClick={handleQuestionEdit}
className="py-3 px-6 rounded-sm h-fit"
disablePadding={true}
/>
{#if isEditMode}
<PrimaryButton
label="Cancel"
variant={VARIANTS.TEXT}
onClick={() => (isEditMode = !isEditMode)}
className="py-3 px-6 rounded-sm h-fit"
disablePadding={true}
/>
{/if}
{/if}
</div>
<div class="my-1 px-1 rounded-lg border border-1 border-gray">
<header class="flex items-center justify-between leading-none p-2">
<div
class="flex items-center no-underline hover:underline text-black"
>
<Avatar
src={question.author.avatar}
name={question.author.name}
width="w-7"
height="h-7"
/>
<p class="dark:text-white ml-2 text-sm">{question.author.name}</p>
<p class="dark:text-white ml-2 text-sm text-gray-500">
{question.createdAt}
</p>
</div>
</header>
{#if isEditMode}
<div class="my-2">
<TextEditor
content={question.body}
onChange={(c) => (editContent.body = c)}
placeholder="Update question"
errorMessage={errors.body}
docId={resetInput}
/>
</div>
{:else}
<section class="prose prose-sm sm:prose p-2">
{@html marked(question.body)}
</section>
{/if}
</div>
<div class="my-8 font-bold">
{pluralize('answers', question.totalComments, true)}
</div>
{#each question.comments as comment}
<div class="my-5 px-1 flex items-start">
<Vote
value={comment.votes}
upVote={() => upvoteQuestion('comment', comment.id)}
disabled={voted.comment[comment.id]}
/>
<div class="w-full rounded-lg border border-1 border-gray">
<header class="flex items-center justify-between leading-none p-2">
<div class="flex items-center text-black">
<Avatar
src={comment.avatar}
name={comment.name}
width="w-7"
height="h-7"
/>
<p class="dark:text-white ml-2 text-sm">{comment.name}</p>
<p class="dark:text-white ml-2 text-sm text-gray-500">
{comment.createdAt}
</p>
</div>
{#if isValidAnswer}
<CheckmarkOutline20 />
{/if}
{#if comment.authorId === $profile.id}
<IconButton
value="delete-comment"
onClick={() => {
deleteComment.shouldDelete = true;
deleteComment.commentId = comment.id;
}}
>
<Delete16 class="carbon-icon" />
</IconButton>
{/if}
</header>
<article class="prose prose-sm sm:prose p-2">
{@html marked(comment.comment)}
</article>
</div>
</div>
{/each}
<hr />
<div>
<TextEditor
onChange={(c) => (comment = c)}
placeholder="Give an answer"
errorMessage={errors.comment}
docId={resetInput}
/>
<div class="flex justify-end mr-2">
<PrimaryButton label="Comment" onClick={submitComment} />
</div>
</div>
</div>
{/if}
</section>
@@ -0,0 +1,96 @@
<script>
import { goto } from '@sapper/app';
import ArrowLeft24 from 'carbon-icons-svelte/lib/ArrowLeft24';
import TextEditor from '../../../../components/TextEditor/index.svelte';
import { currentOrgPath, currentOrg } from '../../../../utils/store/org';
import PrimaryButton from '../../../../components/PrimaryButton/index.svelte';
import { askCommunityValidation } from '../../../../utils/functions/validator';
import { supabase } from '../../../../utils/functions/supabase';
import { SNACKBAR_SEVERITY } from '../../../../components/Snackbar/constants';
import { snackbarStore } from '../../../../components/Snackbar/store';
import generateSlug from '../../../../utils/functions/generateSlug';
let errors = {};
let fields = {
title: '',
body: '',
};
async function handleSave() {
errors = askCommunityValidation(fields);
console.log('handleSave errors', errors);
if (Object.keys(errors).length) {
return;
}
const { data: question, error } = await supabase
.from('community_question')
.insert({
...fields,
organization_id: $currentOrg.id,
author_id: $currentOrg.memberId,
votes: 0,
slug: generateSlug(fields.title),
});
if (error) {
console.error('Error: asking question', error);
$snackbarStore.open = true;
$snackbarStore.message = 'Error - Please try again later';
$snackbarStore.severity = SNACKBAR_SEVERITY.ERROR;
} else {
console.log('Success: asking question', question);
$snackbarStore.open = true;
$snackbarStore.message = 'Success';
$snackbarStore.severity = SNACKBAR_SEVERITY.SUCCESS;
goto(`${$currentOrgPath}/community/${question[0].slug}`);
}
}
</script>
<svelte:head>
<title>Ask the Community - ClassroomIO</title>
</svelte:head>
<section class="w-full max-w-3xl mx-auto">
<div class="p-5">
<a
class="text-gray-500 dark:text-gray-200 text-md flex items-center"
href={`${$currentOrgPath}/community`}
>
<ArrowLeft24 class="carbon-icon" /> Go Back
</a>
<div class="flex items-center justify-between">
<h1 class="dark:text-white text-3xl font-bold">Ask the community</h1>
<PrimaryButton label="Publish" onClick={handleSave} />
</div>
</div>
<div class="mb-3 p-2">
<input
class="title rounded-lg border border-1 border-gray p-2 w-full focus:outline-none"
error="false"
autocomplete="off"
placeholder="Title"
aria-label="Title"
aria-describedby="title-input-validation"
type="text"
name="discussion_title"
id="discussion_title"
bind:value={fields.title}
/>
{#if errors.title}
<p class="dark:text-white text-red-500 text-sm">
{errors.title}
</p>
{/if}
</div>
<TextEditor
onChange={(v) => (fields.body = v)}
placeholder="Ask the community any question you might have"
errorMessage={errors.body}
/>
</section>
@@ -0,0 +1,25 @@
<script>
import { goto } from '@sapper/app';
import Community from '../../../../components/Org/Community/index.svelte';
import PrimaryButton from '../../../../components/PrimaryButton/index.svelte';
import { currentOrgPath } from '../../../../utils/store/org';
function askCommunity() {
goto(`${$currentOrgPath}/community/ask`);
}
</script>
<svelte:head>
<title>Community - ClassroomIO</title>
</svelte:head>
<section class="w-full max-w-4xl mx-auto">
<div class="py-10 px-5">
<div class="flex items-center justify-between mb-10">
<h1 class="dark:text-white text-3xl font-bold">Community</h1>
<PrimaryButton label="Ask Community" onClick={askCommunity} />
</div>
<Community />
</div>
</section>
+1 -1
View File
@@ -65,7 +65,7 @@
<title>Courses - ClassroomIO</title>
</svelte:head>
<section class="w-full max-w-7xl mx-auto">
<section class="w-full max-w-6xl mx-auto">
<div class="py-10 px-5">
<div class="flex items-center justify-between mb-10">
<h1 class="dark:text-white text-3xl font-bold">Courses</h1>
@@ -1,6 +1,6 @@
<script>
import Domains from '../../../../components/Org/Settings/Domains.svelte';
import { currentOrg } from '../../../../utils/store/org';
import { currentOrgPath } from '../../../../utils/store/org';
</script>
<svelte:head>
@@ -11,7 +11,7 @@
<div class="py-10 px-5">
<a
class="text-gray-500 dark:text-gray-200 text-md"
href={`/org/${$currentOrg.siteName}/settings`}>Back to Settings</a
href={`${$currentOrgPath}/settings`}>Back to Settings</a
>
<div class="flex items-center justify-between mb-10">
<h1 class="dark:text-white text-3xl font-bold">Custom domain</h1>
+2 -2
View File
@@ -1,6 +1,6 @@
<script>
import TeamMembers from '../../../../components/Org/Settings/TeamMembers.svelte';
import { currentOrg } from '../../../../utils/store/org';
import { currentOrgPath } from '../../../../utils/store/org';
</script>
<svelte:head>
@@ -11,7 +11,7 @@
<div class="py-10 px-5">
<a
class="text-gray-500 dark:text-gray-200 text-md"
href={`/org/${$currentOrg.siteName}/settings`}>Back to Settings</a
href={`${$currentOrgPath}/settings`}>Back to Settings</a
>
<div class="flex items-center justify-between mb-10">
<h1 class="dark:text-white text-3xl font-bold">Team members</h1>
-92
View File
@@ -1,92 +0,0 @@
<script>
import AddAlt32 from 'carbon-icons-svelte/lib/AddAlt32';
import IconButton from '../components/IconButton/index.svelte';
import Avatar from '../components/Avatar/index.svelte';
let teachers = [
{
avatar: 'https://avatars.githubusercontent.com/u/43971542?s=96&v=4',
name: 'Imed Jaberi',
},
{
avatar: 'https://avatars.githubusercontent.com/u/3624098?s=96&v=4',
name: 'Andrew Clark',
},
{
avatar: 'https://avatars.githubusercontent.com/u/857709?s=96&v=4',
name: 'Daniel Lytkin',
},
];
let students = [
{
avatar: 'https://avatars.githubusercontent.com/u/20658625?s=96&v=4',
name: 'Aissaoui Ahmed',
},
{
avatar: 'https://avatars.githubusercontent.com/u/26669835?s=96&v=4',
name: 'Mahfoudh Arous',
},
];
let borderBottom = 'border-r-0 border-b border-l-0';
function addTeacher() {}
function addStudent() {}
</script>
<div class="root w-full py-5">
<div class="max-w-2xl mx-auto my-5">
<div
class="flex items-center justify-between {borderBottom} border-blue-700"
>
<h3 class="dark:text-white text-3xl text-blue-700">Teachers</h3>
<IconButton
toolTipProps={{ title: 'Add', direction: 'right' }}
onClick={addTeacher}
selected={true}
>
<AddAlt32 class="carbon-icon" />
</IconButton>
</div>
{#each teachers as teacher, index}
<div
class="flex items-center px-2 py-3 {teachers[index + 1] &&
borderBottom} border-gray-300"
>
<Avatar src={teacher.avatar} name={teacher.name} />
<p class="dark:text-white ml-2">{teacher.name}</p>
</div>
{/each}
</div>
<div class="max-w-2xl mx-auto my-5">
<div
class="flex items-center justify-between {borderBottom} border-blue-700"
>
<h3 class="dark:text-white text-3xl text-blue-700">Students</h3>
<IconButton
toolTipProps={{ title: 'Add', direction: 'right' }}
onClick={addStudent}
selected={true}
>
<AddAlt32 class="carbon-icon" />
</IconButton>
</div>
{#each students as student, index}
<div
class="flex items-center px-2 py-3 {students[index + 1] &&
borderBottom} border-gray-300"
>
<Avatar src={student.avatar} name={student.name} />
<p class="dark:text-white ml-2">{student.name}</p>
</div>
{/each}
</div>
</div>
<style>
.root {
min-height: 93vh;
}
</style>
+3 -3
View File
@@ -179,7 +179,7 @@
<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 dark:text-gray-200 text-sm">
<p class="dark:text-white text-gray-500 text-sm">
This are the organizations you work at
</p>
@@ -187,7 +187,7 @@
<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 dark:text-gray-200">
<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.
@@ -210,7 +210,7 @@
Climate Change Group
</a>
</h4>
<p class="dark:text-white text-gray-500 dark:text-gray-200">
<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
</p>
+9
View File
@@ -0,0 +1,9 @@
function generateSlug(title) {
return title
.match(/[^\W\s\d]*/g, '')
.join('')
.toLowerCase()
.concat(`-${new Date().getTime()}`);
}
export default generateSlug;
+29
View File
@@ -1,6 +1,23 @@
import z from 'zod';
import { validateEmail } from './validateEmail';
const askCommunityValidationSchema = z.object({
title: z.string().min(6, {
message: 'Must be 6 or more characters long',
invalid_type_error: 'Must not be empty',
}),
body: z.string().min(20, {
message: 'Must be 10 or more characters long',
invalid_type_error: 'Must not be empty',
}),
});
const commentInCommunityValidationSchema = z.object({
comment: z.string().min(6, {
message: 'Must be 6 or more characters long',
invalid_type_error: 'Must not be empty',
}),
});
const forgotValidationSchema = z.object({
email: z.string().email({
message: 'Invalid email address',
@@ -98,6 +115,18 @@ export const onboardingValidation = (fields = {}, step) => {
return processErrors(error);
};
export const askCommunityValidation = (fields = {}) => {
const { error } = askCommunityValidationSchema.safeParse(fields);
return processErrors(error);
};
export const commentInCommunityValidation = (fields = {}) => {
const { error } = commentInCommunityValidationSchema.safeParse(fields);
return processErrors(error);
};
export const getDisableSubmit = ({ password, confirmPassword }) => {
return !!(password && confirmPassword && password !== confirmPassword);
};
+9 -9
View File
@@ -52,8 +52,9 @@ export async function getOrganizations(userId) {
.from('organizationmember')
.select(
`
id,
profile_id,
organization (
organization!organizationmember_organization_id_fkey (
id,
name,
siteName,
@@ -64,7 +65,7 @@ export async function getOrganizations(userId) {
.eq('profile_id', userId);
const orgsArray = [];
if (data.length) {
if (Array.isArray(data) && data.length) {
data.forEach((orgMember) => {
orgsArray.push({
id: orgMember?.organization?.id,
@@ -73,6 +74,7 @@ export async function getOrganizations(userId) {
orgMember?.organization?.name?.substring(0, 2)?.toUpperCase() || '',
siteName: orgMember?.organization?.siteName,
avatar_url: orgMember?.organization?.avatar_url,
memberId: orgMember?.id,
});
});
@@ -98,16 +100,14 @@ export async function getOrgAudience(orgId) {
email,
avatar_url,
created_at,
groupmember!inner(
organizationmember!inner(
role_id,
group!inner(
organization!inner(id)
)
organization_id
)
`
)
.eq('groupmember.group.organization.id', orgId)
.eq('groupmember.role_id', 3); // is a student, tutor is 2 and admin is 1
.eq('organizationmember.organization_id', orgId)
.eq('organizationmember.role_id', 3); // is a student, tutor is 2 and admin is 1
console.log('data', data);
@@ -173,5 +173,5 @@ export async function getCurrentOrg(siteName) {
return goto('/404');
}
currentOrg.set(data);
currentOrg.set(data[0]);
}
+5 -1
View File
@@ -1,4 +1,4 @@
import { writable } from 'svelte/store';
import { writable, derived } from 'svelte/store';
export const orgs = writable([]);
export const currentOrg = writable({
@@ -10,3 +10,7 @@ export const currentOrg = writable({
});
export const orgAudience = writable([]);
export const orgTeam = writable([]);
export const currentOrgPath = derived(
currentOrg,
($currentOrg) => `/org/${$currentOrg.siteName}`
);