diff --git a/apps/dashboard/.env.example b/apps/dashboard/.env.example index c79c7996..6d62a749 100644 --- a/apps/dashboard/.env.example +++ b/apps/dashboard/.env.example @@ -4,8 +4,8 @@ PUBLIC_SUPABASE_URL=http://localhost:54321 PUBLIC_SUPABASE_ANON_KEY= # Email setup - Optional if you want to get/send emails -SMTP_PASSWORD_HELP= -SMTP_USER_HELP= +SMTP_PASSWORD_NOTIFY= +SMTP_USER_NOTIFY= SMTP_HOST= SMTP_USER= SMTP_PASSWORD= diff --git a/apps/dashboard/src/lib/components/Course/components/Lesson/Exercise/ViewMode.svelte b/apps/dashboard/src/lib/components/Course/components/Lesson/Exercise/ViewMode.svelte index defaae93..d8dd90f0 100644 --- a/apps/dashboard/src/lib/components/Course/components/Lesson/Exercise/ViewMode.svelte +++ b/apps/dashboard/src/lib/components/Course/components/Lesson/Exercise/ViewMode.svelte @@ -46,7 +46,7 @@ return people.find((person) => person.profile_id === profileId); }; - const sendEmail = () => { + const notifyEducator = () => { const student = getStudent($group.students, $profile.id); const teacherFullname = $group.tutors[0]?.fullname; const teacherEmail = $group.tutors[0]?.email; @@ -118,7 +118,7 @@ $course.id, getGroupMemberId($group.people, $profile.id) ); - sendEmail(); + notifyEducator(); } } diff --git a/apps/dashboard/src/lib/components/Course/components/Lesson/Exercises/NewExerciseModal.svelte b/apps/dashboard/src/lib/components/Course/components/Lesson/Exercises/NewExerciseModal.svelte index 52725811..11cd47a3 100644 --- a/apps/dashboard/src/lib/components/Course/components/Lesson/Exercises/NewExerciseModal.svelte +++ b/apps/dashboard/src/lib/components/Course/components/Lesson/Exercises/NewExerciseModal.svelte @@ -14,7 +14,7 @@ import Confetti from '$lib/components/Confetti/index.svelte'; import { toggleConfetti } from '$lib/components/Confetti/store'; import { VARIANTS } from '$lib/components/PrimaryButton/constants'; - import { getTextFromHTML } from '$lib/utils/functions/course'; + import { getTextFromHTML } from '$lib/utils/functions/toHtml'; import { writable } from 'svelte/store'; import { Circle3 } from 'svelte-loading-spinners'; diff --git a/apps/dashboard/src/lib/components/Course/components/Lesson/Materials/components/ComponentNote.svelte b/apps/dashboard/src/lib/components/Course/components/Lesson/Materials/components/ComponentNote.svelte index d7f4389c..dc1d7173 100644 --- a/apps/dashboard/src/lib/components/Course/components/Lesson/Materials/components/ComponentNote.svelte +++ b/apps/dashboard/src/lib/components/Course/components/Lesson/Materials/components/ComponentNote.svelte @@ -1,22 +1,10 @@ -{#if !isNoteEmpty($lesson.materials?.note)} +{#if !isHtmlValueEmpty($lesson.materials?.note)}
diff --git a/apps/dashboard/src/lib/components/Course/components/Lesson/Materials/index.svelte b/apps/dashboard/src/lib/components/Course/components/Lesson/Materials/index.svelte index 7f30cd2e..da44ccfa 100644 --- a/apps/dashboard/src/lib/components/Course/components/Lesson/Materials/index.svelte +++ b/apps/dashboard/src/lib/components/Course/components/Lesson/Materials/index.svelte @@ -1,5 +1,4 @@ {#if name === 'Overview'} +{:else if name === 'News Feed'} + {:else if name === 'Lessons'} {:else if name === 'Attendance'} diff --git a/apps/dashboard/src/lib/components/Course/components/Navigation/index.svelte b/apps/dashboard/src/lib/components/Course/components/Navigation/index.svelte index ed9f9e4f..405a2628 100644 --- a/apps/dashboard/src/lib/components/Course/components/Navigation/index.svelte +++ b/apps/dashboard/src/lib/components/Course/components/Navigation/index.svelte @@ -146,7 +146,7 @@ $: { navItems = [ { - label: 'Overview', + label: 'News Feed', to: getNavItemRoute($course.id), hideSortIcon: true }, @@ -207,8 +207,6 @@ } ]; } - - $: console.log({ isStudent: $globalStore.isStudent });
` - }); + }).then((info) => console.log('Email sent:', info)); return json({ success: true, diff --git a/apps/dashboard/src/routes/api/email/invite/+server.js b/apps/dashboard/src/routes/api/email/invite/+server.js index d60d156c..6f73f436 100644 --- a/apps/dashboard/src/routes/api/email/invite/+server.js +++ b/apps/dashboard/src/routes/api/email/invite/+server.js @@ -12,7 +12,7 @@ export async function POST({ request }) { const accessToken = request.headers.get('Authorization'); console.log('/POST api/email/invite', body); - if (!org || !Object.keys(org).length || !email) { + if (!org || !Object.keys(org).length || !email || !accessToken) { return json( { success: false, message: 'Org data and Teacher name are required' }, { status: 400 } @@ -42,7 +42,7 @@ export async function POST({ request }) { const inviteLink = `${origin}/invite/t/${encodeURIComponent(btoa(inviteData))}`; console.log('inviteData', inviteData); - await sendEmail({ + sendEmail({ to: email, subject: `Join ${name} on ClassroomIO πŸ˜ƒ`, content: ` @@ -53,7 +53,7 @@ export async function POST({ request }) { `, isPersonalEmail: true - }); + }).then((info) => console.log('Email sent:', info)); return json({ success: true, diff --git a/apps/dashboard/src/routes/api/email/welcome/+server.js b/apps/dashboard/src/routes/api/email/welcome/+server.js index 842e103d..64b93ee2 100644 --- a/apps/dashboard/src/routes/api/email/welcome/+server.js +++ b/apps/dashboard/src/routes/api/email/welcome/+server.js @@ -9,7 +9,7 @@ export async function POST({ request }) { const accessToken = request.headers.get('Authorization'); console.log('/POST api/email/welcome', to, name); - if (!to || !name) { + if (!to || !name || !accessToken) { return json({ success: false, message: 'Name and To are required fields' }, { status: 400 }); } @@ -25,7 +25,7 @@ export async function POST({ request }) { return json({ success: false, message: 'Unauthenticated user' }, { status: 401 }); } - await sendEmail({ + sendEmail({ to, subject: 'Thank you so so so much for choosing ClassroomIO!', content: ` @@ -48,7 +48,7 @@ export async function POST({ request }) { `, isPersonalEmail: true - }); + }).then((info) => console.log('Email sent:', info)); return json({ success: true, diff --git a/apps/dashboard/src/routes/courses/[id]/+page.svelte b/apps/dashboard/src/routes/courses/[id]/+page.svelte index 372580ed..e1451bdb 100644 --- a/apps/dashboard/src/routes/courses/[id]/+page.svelte +++ b/apps/dashboard/src/routes/courses/[id]/+page.svelte @@ -1,10 +1,279 @@ - - + + + + ($isNewFeedModal.open = true)} /> + + + + + + + { + newsFeed = [newFeed, ...newsFeed]; + }} + {onEdit} + /> + + + {#if !newsFeed.length} + +
+ Lesson +

No post yet

+

+ Make a post to your class. Start by clicking on the Add button. +

+
+
+ {:else} + {#each newsFeed as feed} + {#if feed.isPinned} +
+ + +

Pinned

+
+ {/if} + + {/each} + {/if} +
diff --git a/apps/dashboard/src/routes/courses/[id]/submissions/+page.svelte b/apps/dashboard/src/routes/courses/[id]/submissions/+page.svelte index 29caec4c..06b204cc 100644 --- a/apps/dashboard/src/routes/courses/[id]/submissions/+page.svelte +++ b/apps/dashboard/src/routes/courses/[id]/submissions/+page.svelte @@ -97,7 +97,7 @@ return Object.values(grades).reduce((acc, grade) => acc + parseInt(grade), 0); } - const sendEmail = (submissionData) => { + const notifyStudent = (submissionData) => { maxMark = getMaxPoints(submissionData?.questions); totalMark = calculateTotal(submissionData?.questionAnswerByPoint); @@ -155,7 +155,7 @@ status_id: itemToWithNewStatus.statusId }; - sendEmail(submissionIdData[itemToWithNewStatus.id]); + notifyStudent(submissionIdData[itemToWithNewStatus.id]); updateSubmission({ id: itemToWithNewStatus.id, @@ -219,7 +219,7 @@ status_id: itemToWithNewStatus.statusId }; - sendEmail(submissionIdData[itemToWithNewStatus.id]); + notifyStudent(submissionIdData[itemToWithNewStatus.id]); // Update backend updateSubmission({ id: itemToWithNewStatus.id, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7cc5fd54..3f921bb3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4944,7 +4944,7 @@ packages: /@vue/compiler-core@3.3.8: resolution: {integrity: sha512-hN/NNBUECw8SusQvDSqqcVv6gWq8L6iAktUR0UF3vGu2OhzRqcOiAno0FmBJWwxhYEXRlQJT5XnoKsVq1WZx4g==} dependencies: - '@babel/parser': 7.23.4 + '@babel/parser': 7.23.9 '@vue/shared': 3.3.8 estree-walker: 2.0.2 source-map-js: 1.0.2 @@ -4960,7 +4960,7 @@ packages: /@vue/compiler-sfc@3.3.8: resolution: {integrity: sha512-WMzbUrlTjfYF8joyT84HfwwXo+8WPALuPxhy+BZ6R4Aafls+jDBnSz8PDz60uFhuqFbl3HxRfxvDzrUf3THwpA==} dependencies: - '@babel/parser': 7.23.4 + '@babel/parser': 7.23.9 '@vue/compiler-core': 3.3.8 '@vue/compiler-dom': 3.3.8 '@vue/compiler-ssr': 3.3.8 @@ -4982,7 +4982,7 @@ packages: /@vue/reactivity-transform@3.3.8: resolution: {integrity: sha512-49CvBzmZNtcHua0XJ7GdGifM8GOXoUMOX4dD40Y5DxI3R8OUhMlvf2nvgUAcPxaXiV5MQQ1Nwy09ADpnLQUqRw==} dependencies: - '@babel/parser': 7.23.4 + '@babel/parser': 7.23.9 '@vue/compiler-core': 3.3.8 '@vue/shared': 3.3.8 estree-walker: 2.0.2 diff --git a/supabase/migrations/20240212090837_course_newsfeed.sql b/supabase/migrations/20240212090837_course_newsfeed.sql new file mode 100644 index 00000000..65c0f2f6 --- /dev/null +++ b/supabase/migrations/20240212090837_course_newsfeed.sql @@ -0,0 +1,48 @@ +create table "public"."course_newsfeed" ( + "created_at" timestamp with time zone not null default now(), + "author_id" uuid, + "content" text, + "id" uuid not null default gen_random_uuid(), + "course_id" uuid, + "reaction" jsonb default '{"clap": [], "smile": [], "thumbsup": [], "thumbsdown": []}'::jsonb, + "is_pinned" boolean not null default false +); + + +create table "public"."course_newsfeed_comment" ( + "created_at" timestamp with time zone not null default now(), + "author_id" uuid, + "content" text, + "id" bigint generated by default as identity not null, + "course_newsfeed_id" uuid +); + + +CREATE UNIQUE INDEX course_newsfeed_comment_id_key ON public.course_newsfeed_comment USING btree (id); + +CREATE UNIQUE INDEX course_newsfeed_comment_pkey ON public.course_newsfeed_comment USING btree (id); + +CREATE UNIQUE INDEX course_newsfeed_pkey ON public.course_newsfeed USING btree (id); + +alter table "public"."course_newsfeed" add constraint "course_newsfeed_pkey" PRIMARY KEY using index "course_newsfeed_pkey"; + +alter table "public"."course_newsfeed_comment" add constraint "course_newsfeed_comment_pkey" PRIMARY KEY using index +"course_newsfeed_comment_pkey"; + +alter table "public"."course_newsfeed" add constraint "course_newsfeed_author_id_fkey" FOREIGN KEY (author_id) REFERENCES groupmember(id) not valid; + +alter table "public"."course_newsfeed" validate constraint "course_newsfeed_author_id_fkey"; + +alter table "public"."course_newsfeed" add constraint "course_newsfeed_course_id_fkey" FOREIGN KEY (course_id) REFERENCES course(id) not valid; + +alter table "public"."course_newsfeed" validate constraint "course_newsfeed_course_id_fkey"; + +alter table "public"."course_newsfeed_comment" add constraint "course_newsfeed_comment_id_key" UNIQUE using index "course_newsfeed_comment_id_key"; + +alter table "public"."course_newsfeed_comment" add constraint "course_newsfeed_comment_author_id_fkey" FOREIGN KEY (author_id) REFERENCES groupmember(id) not valid; + +alter table "public"."course_newsfeed_comment" validate constraint "course_newsfeed_comment_author_id_fkey"; + +alter table "public"."course_newsfeed_comment" add constraint "course_newsfeed_comment_course_newsfeed_id_fkey" FOREIGN KEY (course_newsfeed_id) REFERENCES course_newsfeed(id) not valid; + +alter table "public"."course_newsfeed_comment" validate constraint "course_newsfeed_comment_course_newsfeed_id_fkey"; \ No newline at end of file diff --git a/todo/v1.md b/todo/v1.md index 218055a3..9ef3122d 100644 --- a/todo/v1.md +++ b/todo/v1.md @@ -12,15 +12,18 @@ Features that would implemented internally with possible collaboration with the ### Backlog -- [] Fix video upload functionality and make it open source -- [] move email sending to defer.run +- [] rrefactor email sending + - [] don't send email address from FE + - [] don't await sending via nodemailer - [] implement multi language -- [] Implement supabase RLS -- [] Integrate lemonsqueezy +- [] implement supabase RLS +- [] integrate lemonsqueezy ### In progress - [] Course announcements - [] Create basic API for ClassroomIO +- [] Link shorterner API +- [] Fix video upload functionality and make it open source ### Done