fix: move feed state to a store and create feed loading UI (#189)
* style: Improved font size of the answers in the FAQ section of the landing page * feat: move all email to defer queue (#186) * fix: use relative part for defer func #185 * chore: move sendEmail into defer * fix: remove anon function to defer #185 * feat: implement batch email sending with defer.run #185 * chore: debug sendEmails #185 * chore: add robots.txt * chore: use promise.all in sendEmails * chore: debug defer sendEmails #185 * chore: try defer sendEmail without loop #105 * chore: comment code * chore: change loop style #185 * chore: use promise.all for vercel edge #185 * Moved the feed state to a store and added a loader * feat: add await to sendEmailNotification #185 * chore: use sendEmails #185 * fix: debug sendEmails #185 * fix: email not updating in LMS * fix: remove early return in sendEmails #185 * fixed the edit bug and moved the loader state to the file * added a loading state for the post button * change isDisabled to isLoading --------- Co-authored-by: Rotimi Best <best@classroomio.com>
This commit is contained in:
@@ -20,68 +20,65 @@
|
||||
export let editFeed: Feed;
|
||||
|
||||
let newPost = '';
|
||||
let isLoading = false;
|
||||
let createdFeed;
|
||||
|
||||
const onPost = async () => {
|
||||
if (!newPost) return;
|
||||
|
||||
// Edit state
|
||||
if (edit) {
|
||||
onEdit(editFeed.id, newPost);
|
||||
|
||||
snackbar.success('Feed edited successfully');
|
||||
|
||||
edit = false;
|
||||
newPost = '';
|
||||
$isNewFeedModal.open = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Create state
|
||||
isLoading = true;
|
||||
try {
|
||||
const {
|
||||
response: { data }
|
||||
} = await createNewFeed({
|
||||
content: newPost,
|
||||
author_id: author.id,
|
||||
course_id: courseId,
|
||||
reaction: {
|
||||
smile: [],
|
||||
thumbsup: [],
|
||||
thumbsdown: [],
|
||||
clap: []
|
||||
}
|
||||
});
|
||||
if (edit) {
|
||||
onEdit(editFeed.id, newPost);
|
||||
snackbar.success('Feed edited successfully');
|
||||
|
||||
if (!data) return;
|
||||
edit = false;
|
||||
newPost = '';
|
||||
$isNewFeedModal.open = false;
|
||||
} else {
|
||||
const {
|
||||
response: { data }
|
||||
} = await createNewFeed({
|
||||
content: newPost,
|
||||
author_id: author.id,
|
||||
course_id: courseId,
|
||||
reaction: {
|
||||
smile: [],
|
||||
thumbsup: [],
|
||||
thumbsdown: [],
|
||||
clap: []
|
||||
}
|
||||
});
|
||||
|
||||
createdFeed = data[0];
|
||||
if (!data) return;
|
||||
|
||||
createdFeed = data[0];
|
||||
|
||||
if (!createdFeed) return;
|
||||
|
||||
onSave({
|
||||
id: createdFeed.id,
|
||||
content: newPost,
|
||||
author: {
|
||||
profile: { ...author }
|
||||
},
|
||||
created_at: createdFeed.created_at,
|
||||
comment: [],
|
||||
reaction: createdFeed.reaction,
|
||||
isPinned: false
|
||||
});
|
||||
|
||||
snackbar.success('New feed added successfully');
|
||||
triggerSendEmail(NOTIFICATION_NAME.NEWSFEED, {
|
||||
authorId: createdFeed.author_id,
|
||||
feedId: createdFeed.id
|
||||
});
|
||||
resetEditor();
|
||||
}
|
||||
} catch (error) {
|
||||
return snackbar.error('An error occurred while creating feed');
|
||||
snackbar.error('An error occurred while ' + (edit ? 'editing feed' : 'creating feed'));
|
||||
} finally {
|
||||
isLoading = false;
|
||||
}
|
||||
|
||||
if (!createdFeed) return;
|
||||
|
||||
onSave({
|
||||
id: createdFeed.id,
|
||||
content: newPost,
|
||||
author: {
|
||||
profile: { ...author }
|
||||
},
|
||||
created_at: createdFeed.created_at,
|
||||
comment: [],
|
||||
reaction: createdFeed.reaction,
|
||||
isPinned: false
|
||||
});
|
||||
|
||||
snackbar.success('New feed added successfully');
|
||||
triggerSendEmail(NOTIFICATION_NAME.NEWSFEED, {
|
||||
authorId: createdFeed.author_id,
|
||||
feedId: createdFeed.id
|
||||
});
|
||||
|
||||
resetEditor();
|
||||
};
|
||||
|
||||
const resetEditor = () => {
|
||||
@@ -110,12 +107,8 @@
|
||||
/>
|
||||
<div class="flex items-center justify-end py-2">
|
||||
<div class="flex gap-2">
|
||||
<PrimaryButton
|
||||
label="Cancel"
|
||||
variant={VARIANTS.OUTLINED}
|
||||
onClick={() => ($isNewFeedModal.open = false)}
|
||||
/>
|
||||
<PrimaryButton label="Post" onClick={onPost} />
|
||||
<PrimaryButton label="Cancel" variant={VARIANTS.OUTLINED} onClick={resetEditor} />
|
||||
<PrimaryButton {isLoading} label="Post" onClick={onPost} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<script>
|
||||
import { SkeletonPlaceholder, SkeletonText } from 'carbon-components-svelte';
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-5 border border-gray-300 p-4 rounded-md mb-7 max-w-3xl">
|
||||
<div class="flex items-center gap-2">
|
||||
<SkeletonPlaceholder style="height: 2rem; width: 2rem;" class="rounded-full overflow-hidden" />
|
||||
<SkeletonText width="30%" />
|
||||
</div>
|
||||
<SkeletonText paragraph lines={3} />
|
||||
</div>
|
||||
@@ -1,5 +1,8 @@
|
||||
import type { Feed } from '$lib/utils/types/feed';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const isNewFeedModal = writable({
|
||||
open: false
|
||||
});
|
||||
|
||||
export const newsFeed = writable<Feed[]>([]);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
toggleFeedIsPinned,
|
||||
fetchNewsFeedReaction
|
||||
} from '$lib/utils/services/newsfeed';
|
||||
import { newsFeed } from '$lib/components/Course/components/NewsFeed/store';
|
||||
import Box from '$lib/components/Box/index.svelte';
|
||||
import { supabase } from '$lib/utils/functions/supabase';
|
||||
import { snackbar } from '$lib/components/Snackbar/store';
|
||||
@@ -28,11 +29,12 @@
|
||||
NOTIFICATION_NAME,
|
||||
triggerSendEmail
|
||||
} from '$lib/utils/services/notification/notification';
|
||||
|
||||
import NewsFeedLoader from '$lib/components/Course/components/NewsFeed/NewsFeedLoader.svelte';
|
||||
export let data;
|
||||
|
||||
let createdComment;
|
||||
let edit = false;
|
||||
let isLoading = false;
|
||||
let editFeed: Feed;
|
||||
let author = {
|
||||
id: '',
|
||||
@@ -40,14 +42,14 @@
|
||||
fullname: '',
|
||||
avatar_url: ''
|
||||
};
|
||||
let newsFeed: Feed[] = [];
|
||||
|
||||
let query = new URLSearchParams($page.url.search);
|
||||
let feedId = query.get('feedId') || '';
|
||||
|
||||
const onEdit = (id: string, content: string) => {
|
||||
handleEditFeed(id, content);
|
||||
|
||||
newsFeed = newsFeed.map((feed) => {
|
||||
$newsFeed = $newsFeed.map((feed) => {
|
||||
if (feed.id === id) {
|
||||
return { ...feed, content: content };
|
||||
}
|
||||
@@ -58,7 +60,7 @@
|
||||
|
||||
const deleteComment = (id: string) => {
|
||||
deleteNewsFeedComment(id);
|
||||
newsFeed = newsFeed.flatMap((feed) => ({
|
||||
$newsFeed = $newsFeed.flatMap((feed) => ({
|
||||
...feed,
|
||||
comment: feed.comment.filter((comment) => comment.id !== id)
|
||||
}));
|
||||
@@ -71,7 +73,7 @@
|
||||
|
||||
if (!data) return;
|
||||
|
||||
const reactedFeed = data || newsFeed.find((feed) => feed.id === feedId);
|
||||
const reactedFeed = data || $newsFeed.find((feed) => feed.id === feedId);
|
||||
|
||||
let reactedAuthorIds: string[] = reactedFeed.reaction[reactionType];
|
||||
|
||||
@@ -99,7 +101,7 @@
|
||||
return snackbar.error('An error occurred while reacting to news feed');
|
||||
}
|
||||
|
||||
newsFeed = newsFeed.map((feed) => {
|
||||
$newsFeed = $newsFeed.map((feed) => {
|
||||
if (feed.id === feedId) {
|
||||
feed.reaction = reactedFeed.reaction;
|
||||
}
|
||||
@@ -129,7 +131,7 @@
|
||||
comment
|
||||
});
|
||||
|
||||
newsFeed = newsFeed.map((feed) => {
|
||||
$newsFeed = $newsFeed.map((feed) => {
|
||||
if (feed.id === feedId) {
|
||||
const newComment = {
|
||||
id: createdComment.id,
|
||||
@@ -160,7 +162,7 @@
|
||||
return snackbar.error('Failed to toggle pinned feed');
|
||||
}
|
||||
|
||||
newsFeed = newsFeed.map((feed) => {
|
||||
$newsFeed = $newsFeed.map((feed) => {
|
||||
if (feed.id === feedId) {
|
||||
snackbar.success(`${newIsPinned ? 'Pinned' : 'Unpinned'} Successfully`);
|
||||
|
||||
@@ -175,28 +177,31 @@
|
||||
const deleteFeed = (id: string) => {
|
||||
deleteNewsFeed(id);
|
||||
|
||||
const deletedFeed = newsFeed.filter((feed) => feed.id !== id);
|
||||
const deletedFeed = $newsFeed.filter((feed) => feed.id !== id);
|
||||
snackbar.success('Feed deleted successfully');
|
||||
|
||||
newsFeed = deletedFeed;
|
||||
$newsFeed = deletedFeed;
|
||||
};
|
||||
|
||||
const initNewsFeed = async (courseId: string) => {
|
||||
if (!courseId) return;
|
||||
|
||||
const { data, error } = await fetchNewsFeeds(courseId);
|
||||
|
||||
if (error) {
|
||||
snackbar.error('Failed to fetch news feeds');
|
||||
return;
|
||||
try {
|
||||
isLoading = true;
|
||||
const { data, error } = await fetchNewsFeeds(courseId);
|
||||
if (error) {
|
||||
snackbar.error('Failed to fetch news feeds');
|
||||
}
|
||||
if (data) {
|
||||
$newsFeed = data.map((feedItem) => ({
|
||||
...feedItem,
|
||||
isPinned: feedItem.is_pinned
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
snackbar.error('An error occured while fetching feed');
|
||||
} finally {
|
||||
isLoading = false;
|
||||
}
|
||||
|
||||
if (!data) return;
|
||||
|
||||
newsFeed = data.map((feedItem) => ({
|
||||
...feedItem,
|
||||
isPinned: feedItem.is_pinned
|
||||
}));
|
||||
};
|
||||
|
||||
function setAuthor(groups, profileId) {
|
||||
@@ -214,7 +219,7 @@
|
||||
|
||||
$: setAuthor($group, $profile.id);
|
||||
|
||||
$: newsFeed = newsFeed.sort((a, b) => Number(b.isPinned) - Number(a.isPinned));
|
||||
$: $newsFeed = $newsFeed.sort((a, b) => Number(b.isPinned) - Number(a.isPinned));
|
||||
</script>
|
||||
|
||||
<CourseContainer bind:courseId={data.courseId}>
|
||||
@@ -234,13 +239,18 @@
|
||||
bind:edit
|
||||
bind:editFeed
|
||||
onSave={(newFeed) => {
|
||||
newsFeed = [newFeed, ...newsFeed];
|
||||
$newsFeed = [newFeed, ...$newsFeed];
|
||||
}}
|
||||
{onEdit}
|
||||
/>
|
||||
</RoleBasedSecurity>
|
||||
|
||||
{#if !newsFeed.length}
|
||||
{#if isLoading}
|
||||
<div>
|
||||
<NewsFeedLoader />
|
||||
<NewsFeedLoader />
|
||||
<NewsFeedLoader />
|
||||
</div>
|
||||
{:else if !$newsFeed.length}
|
||||
<Box>
|
||||
<div class="flex justify-between flex-col items-center w-[90%] md:w-96">
|
||||
<img src="/images/empty-lesson-icon.svg" alt="Lesson" class="my-2.5 mx-auto" />
|
||||
@@ -251,7 +261,7 @@
|
||||
</div>
|
||||
</Box>
|
||||
{:else}
|
||||
{#each newsFeed as feed}
|
||||
{#each $newsFeed as feed}
|
||||
{#if feed.isPinned}
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<PinFilled size={16} />
|
||||
|
||||
Reference in New Issue
Block a user