Dashboard improvements + LMS community
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||
import GoogleIconColored from '../Icons/GoogleIconColored.svelte';
|
||||
import Avatar from '$lib/components/Avatar/index.svelte';
|
||||
import { currentOrg } from '$lib/utils/store/org';
|
||||
|
||||
export let supabase;
|
||||
export let handleSubmit;
|
||||
@@ -34,9 +36,17 @@
|
||||
<div class="container border border-gray bg-white dark:bg-gray-800">
|
||||
<div class="flex items-center flex-col p-2 lg:px-8 lg:py-3">
|
||||
{#if !showOnlyContent || showLogo}
|
||||
<div class="flex items-center w-full justify-center pt-4">
|
||||
<img src="/logo-192.png" alt="ClassroomIO logo" height="50" width="50" data-atf="1" />
|
||||
<h4 class="dark:text-white text-xl">ClassroomIO</h4>
|
||||
<div class="flex flex-col items-center w-full justify-center pt-2">
|
||||
<Avatar
|
||||
src={$currentOrg.avatar_url ? $currentOrg.avatar_url : '/logo-192.png'}
|
||||
name={$currentOrg.name ? $currentOrg.name : 'ClassroomIO'}
|
||||
shape="rounded-md"
|
||||
width="w-10"
|
||||
height="h-10"
|
||||
/>
|
||||
<h4 class="dark:text-white text-xl mt-1">
|
||||
{$currentOrg.name ? $currentOrg.name : 'ClassroomIO'}
|
||||
</h4>
|
||||
</div>
|
||||
{/if}
|
||||
<form
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
import { course } from '$lib/components/Course/store';
|
||||
import * as CONSTANTS from './constants';
|
||||
import { fetchLesson } from '$lib/utils/services/courses';
|
||||
import type { LessonPage } from '$lib/utils/types';
|
||||
|
||||
export let mode = MODES.view;
|
||||
export let prevMode = '';
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Courses - ClassroomIO</title>
|
||||
<title>Create a new course</title>
|
||||
</svelte:head>
|
||||
|
||||
<Modal
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
];
|
||||
</script>
|
||||
|
||||
<aside class=" root bg-gray-100 dark:bg-gray-700 h-full">
|
||||
<aside class="w-[250px] min-w-[250px] bg-gray-100 dark:bg-gray-700 h-full">
|
||||
<div class="h-full flex flex-col">
|
||||
<div class="border-b border-gray-200 pt-5 px-4">
|
||||
{#if $currentOrg.avatar_url}
|
||||
@@ -109,9 +109,3 @@
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<style>
|
||||
.root {
|
||||
width: 250px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
import { currentOrgPath, currentOrg } from '$lib/utils/store/org';
|
||||
import { supabase } from '$lib/utils/functions/supabase';
|
||||
|
||||
export let isLMS = false;
|
||||
|
||||
let discussions = [];
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
@@ -38,7 +40,7 @@
|
||||
|
||||
if (error) {
|
||||
console.error('Error loading community', error);
|
||||
return goto(`${currentOrgPath}`);
|
||||
return goto(isLMS ? '/lms' : $currentOrgPath);
|
||||
}
|
||||
|
||||
discussions =
|
||||
@@ -48,7 +50,7 @@
|
||||
author: discussion?.author?.profile?.fullname,
|
||||
comments: discussion.comments?.[0]?.count || 0,
|
||||
votes: discussion.votes,
|
||||
createdAt: dayjs(discussion.created_at).fromNow(true),
|
||||
createdAt: dayjs(discussion.created_at).fromNow(true)
|
||||
})) || [];
|
||||
}
|
||||
|
||||
@@ -65,7 +67,7 @@
|
||||
<h4>
|
||||
<a
|
||||
class="text-black dark:text-white"
|
||||
href="{$currentOrgPath}/community/{discussion.slug}"
|
||||
href="{isLMS ? '/lms' : $currentOrgPath}/community/{discussion.slug}"
|
||||
>
|
||||
{discussion.title}
|
||||
</a>
|
||||
@@ -84,9 +86,7 @@
|
||||
<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>
|
||||
<p class="dark:text-white w-1/3 text-center">Ask a question to the community</p>
|
||||
</Box>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -133,7 +133,9 @@
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
{$currentOrg.name}
|
||||
<title>
|
||||
{$currentOrg.name}'s Landing Page
|
||||
</title>
|
||||
</svelte:head>
|
||||
|
||||
<main>
|
||||
@@ -148,7 +150,7 @@
|
||||
class="md:w-11/12 lg:w-5/6 w-full flex items-center justify-between flex-col-reverse md:flex-row z-20 relative"
|
||||
>
|
||||
<!-- Course Description -->
|
||||
<div class="md:w-2/5 w-11/12 py-10 flex flex-col items-start">
|
||||
<div class="md:w-2/5 w-11/12 py-10 flex flex-col items-start mr-3">
|
||||
<p class=" text-blue-600 text-2xl font-semibold">Coding Dojo</p>
|
||||
<h1 class="text-4xl md:text-5xl lg:text-6xl my-4 font-bold">
|
||||
Web Developement Courses for <br /><span class="text-blue-600">Everyone</span>
|
||||
@@ -164,7 +166,7 @@
|
||||
onClick={() => goto('#contact')}
|
||||
/>
|
||||
</div>
|
||||
<div class="w-4/6 md:w-2/3 md:max-w-[650px] flex">
|
||||
<div class="w-4/6 md:w-1/2 md:max-w-[650px] flex">
|
||||
<video class="w-full rounded-xl" controls loop autoplay>
|
||||
<source src={video} type="video/mp4" />
|
||||
<!-- <source src="/path/to/video.webm" type="video/webm" /> -->
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
</script>
|
||||
|
||||
<aside
|
||||
class="root bg-gray-100 dark:bg-gray-800 h-full border border-l-0 border-t-0 border-b-0 border-r-1"
|
||||
class="w-[250px] min-w-[250px] bg-gray-100 dark:bg-gray-800 h-full border border-l-0 border-t-0 border-b-0 border-r-1"
|
||||
>
|
||||
<div class="h-full flex flex-col">
|
||||
<div class="border-b border-gray-200 pt-5 px-4">
|
||||
@@ -128,9 +128,3 @@
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<style>
|
||||
.root {
|
||||
width: 250px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1 +1,25 @@
|
||||
community page
|
||||
<script>
|
||||
import { goto } from '$app/navigation';
|
||||
import Community from '$lib/components/Org/Community/index.svelte';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import { currentOrgPath } from '$lib/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 isLMS={true} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export const load = ({ params = { slug: '' } }) => {
|
||||
return {
|
||||
slug: params.slug,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,414 @@
|
||||
<script>
|
||||
import { marked } from 'marked';
|
||||
import { goto } from '$app/navigation';
|
||||
import dayjs from 'dayjs';
|
||||
import pluralize from 'pluralize';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import TrashCanIcon from 'carbon-icons-svelte/lib/TrashCan.svelte';
|
||||
import { SkeletonPlaceholder, SkeletonText } from 'carbon-components-svelte';
|
||||
import ArrowLeftIcon from 'carbon-icons-svelte/lib/ArrowLeft.svelte';
|
||||
import Vote from '$lib/components/Vote/index.svelte';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||
import TextEditor from '$lib/components/TextEditor/index.svelte';
|
||||
import Avatar from '$lib/components/Avatar/index.svelte';
|
||||
import IconButton from '$lib/components/IconButton/index.svelte';
|
||||
import CheckmarkOutlineIcon from 'carbon-icons-svelte/lib/CheckmarkOutline.svelte';
|
||||
import { currentOrg } from '$lib/utils/store/org';
|
||||
import { profile } from '$lib/utils/store/user';
|
||||
import { supabase } from '$lib/utils/functions/supabase';
|
||||
import {
|
||||
askCommunityValidation,
|
||||
commentInCommunityValidation
|
||||
} from '$lib/utils/functions/validator';
|
||||
import { SNACKBAR_SEVERITY } from '$lib/components/Snackbar/constants';
|
||||
import { snackbarStore } from '$lib/components/Snackbar/store';
|
||||
import TextField from '$lib/components/Form/TextField.svelte';
|
||||
import DeleteCommentModal from '$lib/components/Org/Community/DeleteCommentModal.svelte';
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
export let data;
|
||||
const { slug } = data;
|
||||
|
||||
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('/lms/community');
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
.select();
|
||||
|
||||
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-white text-md flex items-center" href={`/lms/community`}>
|
||||
<ArrowLeftIcon size={24} class="carbon-icon dark:text-white" /> 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.OUTLINED}
|
||||
onClick={handleQuestionEdit}
|
||||
className="h-fit"
|
||||
/>
|
||||
{#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}
|
||||
<CheckmarkOutlineIcon size={20} />
|
||||
{/if}
|
||||
|
||||
{#if comment.authorId === $profile.id}
|
||||
<IconButton
|
||||
value="delete-comment"
|
||||
onClick={() => {
|
||||
deleteComment.shouldDelete = true;
|
||||
deleteComment.commentId = comment.id;
|
||||
}}
|
||||
>
|
||||
<TrashCanIcon size={16} class="carbon-icon dark:text-white" />
|
||||
</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,94 @@
|
||||
<script>
|
||||
import { goto } from '$app/navigation';
|
||||
import ArrowLeftIcon from 'carbon-icons-svelte/lib/ArrowLeft.svelte';
|
||||
import TextEditor from '$lib/components/TextEditor/index.svelte';
|
||||
import { currentOrg } from '$lib/utils/store/org';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import { askCommunityValidation } from '$lib/utils/functions/validator';
|
||||
import { supabase } from '$lib/utils/functions/supabase';
|
||||
import { SNACKBAR_SEVERITY } from '$lib/components/Snackbar/constants';
|
||||
import { snackbarStore } from '$lib/components/Snackbar/store';
|
||||
import generateSlug from '$lib/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)
|
||||
})
|
||||
.select();
|
||||
|
||||
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(`/lms/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-white text-md flex items-center" href={`/lms/community`}>
|
||||
<ArrowLeftIcon size={24} class="carbon-icon dark:text-white" /> 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>
|
||||
@@ -5,6 +5,7 @@
|
||||
import { authValidation } from '$lib/utils/functions/validator';
|
||||
import { LOGIN_FIELDS } from '$lib/utils/constants/authentication';
|
||||
import AuthUI from '$lib/components/AuthUI/index.svelte';
|
||||
import { currentOrg } from '$lib/utils/store/org';
|
||||
|
||||
let formRef;
|
||||
let supabase = getSupabase();
|
||||
@@ -39,7 +40,7 @@
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Welcome back to ClassroomIO</title>
|
||||
<title>Welcome back to {$currentOrg.name ?? 'ClassroomIO'}</title>
|
||||
</svelte:head>
|
||||
|
||||
<AuthUI {supabase} isLogin={true} {handleSubmit} isLoading={loading} bind:formRef>
|
||||
|
||||
Reference in New Issue
Block a user