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:
committed by
Space Cloud
co-authored by
Rotimi Best
parent
779827ba46
commit
c36a49a1ee
@@ -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,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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
|
||||
@@ -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',
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user