From be619ae6bd6ef28e7671e2960b669784b296c638 Mon Sep 17 00:00:00 2001 From: Rotimi Best Date: Thu, 14 Sep 2023 18:46:24 +0100 Subject: [PATCH] CLASSROOMIO-T-152: Send notification to teachers and student after student joins --- .../Course/components/People/utils.ts | 2 +- .../email/course/student_welcome/+server.js | 51 +++++++++++++++++ .../course/student_welcome/template.svelte | 15 +++++ .../course/student_welcome/welcome.svelte | 37 ++++++++++++ .../course/teacher_student_joined/+server.js | 49 ++++++++++++++++ .../teacher_student_joined/template.svelte | 13 +++++ .../teacher_student_joined/welcome.svelte | 44 ++++++++++++++ .../{welcome => teacher_welcome}/+server.js | 2 +- .../template.svelte | 0 .../welcome.svelte | 0 src/routes/invite/s/[hash]/+page.svelte | 28 ++++++++- src/routes/invite/s/[hash]/utils.ts | 56 ++++++++++++++++++ src/routes/login/+page.svelte | 9 +++ src/routes/signup/+page.svelte | 57 ++++++++++++++++--- 14 files changed, 351 insertions(+), 12 deletions(-) create mode 100644 src/routes/api/email/course/student_welcome/+server.js create mode 100644 src/routes/api/email/course/student_welcome/template.svelte create mode 100644 src/routes/api/email/course/student_welcome/welcome.svelte create mode 100644 src/routes/api/email/course/teacher_student_joined/+server.js create mode 100644 src/routes/api/email/course/teacher_student_joined/template.svelte create mode 100644 src/routes/api/email/course/teacher_student_joined/welcome.svelte rename src/routes/api/email/course/{welcome => teacher_welcome}/+server.js (94%) rename src/routes/api/email/course/{welcome => teacher_welcome}/template.svelte (100%) rename src/routes/api/email/course/{welcome => teacher_welcome}/welcome.svelte (100%) create mode 100644 src/routes/invite/s/[hash]/utils.ts diff --git a/src/lib/components/Course/components/People/utils.ts b/src/lib/components/Course/components/People/utils.ts index 58240efa..5b2e9f39 100644 --- a/src/lib/components/Course/components/People/utils.ts +++ b/src/lib/components/Course/components/People/utils.ts @@ -11,7 +11,7 @@ export async function sendTeacherCourseWelcome(params: { const accessToken = data.session?.access_token || ''; try { - const response = await fetch('/api/email/course/welcome', { + const response = await fetch('/api/email/course/teacher_welcome', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/routes/api/email/course/student_welcome/+server.js b/src/routes/api/email/course/student_welcome/+server.js new file mode 100644 index 00000000..0deb5ef4 --- /dev/null +++ b/src/routes/api/email/course/student_welcome/+server.js @@ -0,0 +1,51 @@ +import { render } from 'svelte-email'; +import { json } from '@sveltejs/kit'; +import WelcomeTemplate from './template.svelte'; +import { getSendgrid, SENDGRID_FROM_NOTIFY } from '$lib/utils/services/sendgrid'; +import { getSupabase } from '$lib/utils/functions/supabase'; + +const sendgrid = getSendgrid(); +const supabase = getSupabase(); + +// Sends email to student when they successfully join a course +export async function POST({ request }) { + const { to, orgName, courseName } = await request.json(); + const accessToken = request.headers.get('Authorization') || ''; + console.log('/POST api/email/course/student_welcome', to, orgName); + + if (!to || !orgName || !courseName) { + return json({ success: false, message: 'Missing required fields' }, { status: 400 }); + } + + let user; + try { + const { data } = await supabase.auth.getUser(accessToken); + user = data.user; + } catch (error) { + console.error(error); + } + + if (!user) { + return json({ success: false, message: 'Unauthenticated user' }, { status: 401 }); + } + + const options = { + from: SENDGRID_FROM_NOTIFY, + to, + subject: `[${orgName}] Welcome to Class`, + html: render({ + template: WelcomeTemplate, + props: { + orgName, + courseName + } + }) + }; + + sendgrid.send(options); + + return json({ + success: true, + message: 'Email sent' + }); +} diff --git a/src/routes/api/email/course/student_welcome/template.svelte b/src/routes/api/email/course/student_welcome/template.svelte new file mode 100644 index 00000000..7b63c5f3 --- /dev/null +++ b/src/routes/api/email/course/student_welcome/template.svelte @@ -0,0 +1,15 @@ + + + diff --git a/src/routes/api/email/course/student_welcome/welcome.svelte b/src/routes/api/email/course/student_welcome/welcome.svelte new file mode 100644 index 00000000..682838ef --- /dev/null +++ b/src/routes/api/email/course/student_welcome/welcome.svelte @@ -0,0 +1,37 @@ + + +Hey there, + + Congratulations, you’ve successfully joined “{courseName}" + + + Everything has been set up for you to have an amazing learning experience. + + + If you run into any issues, don’t fail to reach out to your instructor(s). + +Cheers, +{orgName} diff --git a/src/routes/api/email/course/teacher_student_joined/+server.js b/src/routes/api/email/course/teacher_student_joined/+server.js new file mode 100644 index 00000000..47cc09f7 --- /dev/null +++ b/src/routes/api/email/course/teacher_student_joined/+server.js @@ -0,0 +1,49 @@ +import { render } from 'svelte-email'; +import { json } from '@sveltejs/kit'; +import WelcomeTemplate from './template.svelte'; +import { getSendgrid, SENDGRID_FROM_NOTIFY } from '$lib/utils/services/sendgrid'; +import { getSupabase } from '$lib/utils/functions/supabase'; + +const sendgrid = getSendgrid(); +const supabase = getSupabase(); + +export async function POST({ request }) { + const { to, courseName } = await request.json(); + const accessToken = request.headers.get('Authorization') || ''; + console.log('/POST api/email/course/teacher_student_joined', to, courseName); + + if (!to || !courseName) { + return json({ success: false, message: 'Missing required fields' }, { status: 400 }); + } + + let user; + try { + const { data } = await supabase.auth.getUser(accessToken); + user = data.user; + } catch (error) { + console.error(error); + } + + if (!user) { + return json({ success: false, message: 'Unauthenticated user' }, { status: 401 }); + } + + const options = { + from: SENDGRID_FROM_NOTIFY, + to, + subject: `[${courseName}] You've got a new student!`, + html: render({ + template: WelcomeTemplate, + props: { + courseName + } + }) + }; + + sendgrid.send(options); + + return json({ + success: true, + message: 'Email sent' + }); +} diff --git a/src/routes/api/email/course/teacher_student_joined/template.svelte b/src/routes/api/email/course/teacher_student_joined/template.svelte new file mode 100644 index 00000000..1b981877 --- /dev/null +++ b/src/routes/api/email/course/teacher_student_joined/template.svelte @@ -0,0 +1,13 @@ + + + diff --git a/src/routes/api/email/course/teacher_student_joined/welcome.svelte b/src/routes/api/email/course/teacher_student_joined/welcome.svelte new file mode 100644 index 00000000..f27db4ba --- /dev/null +++ b/src/routes/api/email/course/teacher_student_joined/welcome.svelte @@ -0,0 +1,44 @@ + + +Hi amazing tutor, + + Congratulations, a new student has joined a course you are teaching: “{courseName}"" + + + We hope they have a great experience learning from the best (YOU). + + + If you run into any issues, please don’t fail to reach out to us, we’d love to make your teaching + experience as easy as possible. + + +Cheers + + ClassroomIO + diff --git a/src/routes/api/email/course/welcome/+server.js b/src/routes/api/email/course/teacher_welcome/+server.js similarity index 94% rename from src/routes/api/email/course/welcome/+server.js rename to src/routes/api/email/course/teacher_welcome/+server.js index cce9d134..6ed86ed0 100644 --- a/src/routes/api/email/course/welcome/+server.js +++ b/src/routes/api/email/course/teacher_welcome/+server.js @@ -10,7 +10,7 @@ const supabase = getSupabase(); export async function POST({ request }) { const { to, name, orgName, courseName, orgSiteName } = await request.json(); const accessToken = request.headers.get('Authorization') || ''; - console.log('/POST api/email/course/welcome', to, name, orgName); + console.log('/POST api/email/course/teacher_welcome', to, name, orgName); if (!to || !name || !orgName || !courseName || !orgSiteName) { return json({ success: false, message: 'Missing required fields' }, { status: 400 }); diff --git a/src/routes/api/email/course/welcome/template.svelte b/src/routes/api/email/course/teacher_welcome/template.svelte similarity index 100% rename from src/routes/api/email/course/welcome/template.svelte rename to src/routes/api/email/course/teacher_welcome/template.svelte diff --git a/src/routes/api/email/course/welcome/welcome.svelte b/src/routes/api/email/course/teacher_welcome/welcome.svelte similarity index 100% rename from src/routes/api/email/course/welcome/welcome.svelte rename to src/routes/api/email/course/teacher_welcome/welcome.svelte diff --git a/src/routes/invite/s/[hash]/+page.svelte b/src/routes/invite/s/[hash]/+page.svelte index 18f29853..0318682b 100644 --- a/src/routes/invite/s/[hash]/+page.svelte +++ b/src/routes/invite/s/[hash]/+page.svelte @@ -10,6 +10,7 @@ import type { CurrentOrg } from '$lib/utils/types/org.js'; import { ROLE } from '$lib/utils/constants/roles'; import { profile } from '$lib/utils/store/user.js'; + import { sendStudentCourseWelcome, sendTeacherStudentJoinedCourse } from './utils'; export let data; @@ -27,6 +28,18 @@ group_id: data.groupId, role_id: ROLE.STUDENT }; + + const teacherMembers = await supabase + .from('groupmember') + .select('id, profile(email)') + .eq('group_id', data.groupId) + .eq('role_id', ROLE.TUTOR); + + const teachers: Array = + teacherMembers.data?.map((teacher) => { + return teacher.profile?.email || ''; + }) || []; + addGroupMember(member).then((addedMember) => { loading = false; if (addedMember.error) { @@ -34,8 +47,19 @@ return; } + // Send email welcoming student to the course + sendStudentCourseWelcome({ + to: $profile.email, + orgName: data.currentOrg?.name, + courseName: data.name + }); + // Send notification to teacher(s) that a student has joined the course. - // also send welcome email to students when they join the course + sendTeacherStudentJoinedCourse({ + to: teachers, + courseName: data.name + }); + // go to lms return goto('/lms'); }); @@ -46,7 +70,7 @@ currentOrg.set(cOrg); } - onMount(() => { + onMount(async () => { setTheme(data.currentOrg?.theme || ''); }); diff --git a/src/routes/invite/s/[hash]/utils.ts b/src/routes/invite/s/[hash]/utils.ts new file mode 100644 index 00000000..dfbc3f1a --- /dev/null +++ b/src/routes/invite/s/[hash]/utils.ts @@ -0,0 +1,56 @@ +import { supabase } from '$lib/utils/functions/supabase'; + +export async function sendStudentCourseWelcome(params: { + to: string; + orgName: string; + courseName: string; +}) { + const { data } = await supabase.auth.getSession(); + const accessToken = data.session?.access_token || ''; + + try { + const response = await fetch('/api/email/course/student_welcome', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: accessToken + }, + body: JSON.stringify(params) + }); + if (!response.ok) { + throw new Error(await response.text()); + } + const result = await response.json(); + + console.log('Sent invite email', result); + } catch (error) { + console.error('Error sending invite email', error); + } +} + +export async function sendTeacherStudentJoinedCourse(params: { + to: Array; + courseName: string; +}) { + const { data } = await supabase.auth.getSession(); + const accessToken = data.session?.access_token || ''; + + try { + const response = await fetch('/api/email/course/teacher_student_joined', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: accessToken + }, + body: JSON.stringify(params) + }); + if (!response.ok) { + throw new Error(await response.text()); + } + const result = await response.json(); + + console.log('Sent invite email', result); + } catch (error) { + console.error('Error sending invite email', error); + } +} diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte index 5882ce77..ee1a63b7 100644 --- a/src/routes/login/+page.svelte +++ b/src/routes/login/+page.svelte @@ -1,4 +1,5 @@