CLASSROOMIO-T-152: Send notification to teachers and student after student joins
This commit is contained in:
@@ -11,7 +11,7 @@ export async function sendTeacherCourseWelcome(params: {
|
|||||||
const accessToken = data.session?.access_token || '';
|
const accessToken = data.session?.access_token || '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/email/course/welcome', {
|
const response = await fetch('/api/email/course/teacher_welcome', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
@@ -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'
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import EmailTemplate from '$lib/components/Email/EmailTemplate.svelte';
|
||||||
|
import Welcome from './welcome.svelte';
|
||||||
|
|
||||||
|
export let orgName = '';
|
||||||
|
export let courseName = '';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<EmailTemplate
|
||||||
|
children={Welcome}
|
||||||
|
props={{
|
||||||
|
orgName,
|
||||||
|
courseName
|
||||||
|
}}
|
||||||
|
/>
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Text } from 'svelte-email';
|
||||||
|
|
||||||
|
export let orgName = '';
|
||||||
|
export let courseName = '';
|
||||||
|
|
||||||
|
const fontFamily =
|
||||||
|
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif';
|
||||||
|
|
||||||
|
const paragraph = {
|
||||||
|
fontFamily,
|
||||||
|
fontSize: '16px',
|
||||||
|
lineHeight: '26px'
|
||||||
|
};
|
||||||
|
const paragraphContent = {
|
||||||
|
...paragraph,
|
||||||
|
margin: '20px 0'
|
||||||
|
};
|
||||||
|
|
||||||
|
const footerParagraph = {
|
||||||
|
...paragraph,
|
||||||
|
margin: '0px'
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Text style={paragraph}>Hey there,</Text>
|
||||||
|
<Text style={paragraphContent}>
|
||||||
|
Congratulations, you’ve successfully joined “{courseName}"
|
||||||
|
</Text>
|
||||||
|
<Text style={paragraphContent}>
|
||||||
|
Everything has been set up for you to have an amazing learning experience.
|
||||||
|
</Text>
|
||||||
|
<Text style={paragraphContent}>
|
||||||
|
If you run into any issues, don’t fail to reach out to your instructor(s).
|
||||||
|
</Text>
|
||||||
|
<Text style={footerParagraph}>Cheers,</Text>
|
||||||
|
<Text style={footerParagraph}>{orgName}</Text>
|
||||||
@@ -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'
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import EmailTemplate from '$lib/components/Email/EmailTemplate.svelte';
|
||||||
|
import Welcome from './welcome.svelte';
|
||||||
|
|
||||||
|
export let courseName = '';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<EmailTemplate
|
||||||
|
children={Welcome}
|
||||||
|
props={{
|
||||||
|
courseName
|
||||||
|
}}
|
||||||
|
/>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Text, Link } from 'svelte-email';
|
||||||
|
|
||||||
|
export let courseName = '';
|
||||||
|
|
||||||
|
const fontFamily =
|
||||||
|
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif';
|
||||||
|
|
||||||
|
const paragraph = {
|
||||||
|
fontFamily,
|
||||||
|
fontSize: '16px',
|
||||||
|
lineHeight: '26px'
|
||||||
|
};
|
||||||
|
const paragraphContent = {
|
||||||
|
...paragraph,
|
||||||
|
margin: '20px 0'
|
||||||
|
};
|
||||||
|
|
||||||
|
const footerParagraph = {
|
||||||
|
...paragraph,
|
||||||
|
margin: '0px'
|
||||||
|
};
|
||||||
|
|
||||||
|
const linkStyle = {
|
||||||
|
color: '#1d4ee2'
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Text style={paragraph}>Hi amazing tutor,</Text>
|
||||||
|
<Text style={paragraphContent}>
|
||||||
|
Congratulations, a new student has joined a course you are teaching: “{courseName}""
|
||||||
|
</Text>
|
||||||
|
<Text style={paragraphContent}>
|
||||||
|
We hope they have a great experience learning from the best (YOU).
|
||||||
|
</Text>
|
||||||
|
<Text style={paragraphContent}>
|
||||||
|
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.
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text style={footerParagraph}>Cheers</Text>
|
||||||
|
<Text style={footerParagraph}>
|
||||||
|
<Link style={linkStyle} href="https://classroomio.com">ClassroomIO</Link>
|
||||||
|
</Text>
|
||||||
+1
-1
@@ -10,7 +10,7 @@ const supabase = getSupabase();
|
|||||||
export async function POST({ request }) {
|
export async function POST({ request }) {
|
||||||
const { to, name, orgName, courseName, orgSiteName } = await request.json();
|
const { to, name, orgName, courseName, orgSiteName } = await request.json();
|
||||||
const accessToken = request.headers.get('Authorization') || '';
|
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) {
|
if (!to || !name || !orgName || !courseName || !orgSiteName) {
|
||||||
return json({ success: false, message: 'Missing required fields' }, { status: 400 });
|
return json({ success: false, message: 'Missing required fields' }, { status: 400 });
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
import type { CurrentOrg } from '$lib/utils/types/org.js';
|
import type { CurrentOrg } from '$lib/utils/types/org.js';
|
||||||
import { ROLE } from '$lib/utils/constants/roles';
|
import { ROLE } from '$lib/utils/constants/roles';
|
||||||
import { profile } from '$lib/utils/store/user.js';
|
import { profile } from '$lib/utils/store/user.js';
|
||||||
|
import { sendStudentCourseWelcome, sendTeacherStudentJoinedCourse } from './utils';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
|
|
||||||
@@ -27,6 +28,18 @@
|
|||||||
group_id: data.groupId,
|
group_id: data.groupId,
|
||||||
role_id: ROLE.STUDENT
|
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<string> =
|
||||||
|
teacherMembers.data?.map((teacher) => {
|
||||||
|
return teacher.profile?.email || '';
|
||||||
|
}) || [];
|
||||||
|
|
||||||
addGroupMember(member).then((addedMember) => {
|
addGroupMember(member).then((addedMember) => {
|
||||||
loading = false;
|
loading = false;
|
||||||
if (addedMember.error) {
|
if (addedMember.error) {
|
||||||
@@ -34,8 +47,19 @@
|
|||||||
return;
|
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.
|
// 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
|
// go to lms
|
||||||
return goto('/lms');
|
return goto('/lms');
|
||||||
});
|
});
|
||||||
@@ -46,7 +70,7 @@
|
|||||||
currentOrg.set(cOrg);
|
currentOrg.set(cOrg);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(async () => {
|
||||||
setTheme(data.currentOrg?.theme || '');
|
setTheme(data.currentOrg?.theme || '');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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<string>;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { page } from '$app/stores';
|
||||||
import TextField from '$lib/components/Form/TextField.svelte';
|
import TextField from '$lib/components/Form/TextField.svelte';
|
||||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||||
@@ -6,6 +7,7 @@
|
|||||||
import { LOGIN_FIELDS } from '$lib/utils/constants/authentication';
|
import { LOGIN_FIELDS } from '$lib/utils/constants/authentication';
|
||||||
import AuthUI from '$lib/components/AuthUI/index.svelte';
|
import AuthUI from '$lib/components/AuthUI/index.svelte';
|
||||||
import { currentOrg } from '$lib/utils/store/org';
|
import { currentOrg } from '$lib/utils/store/org';
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
let formRef;
|
let formRef;
|
||||||
let supabase = getSupabase();
|
let supabase = getSupabase();
|
||||||
@@ -14,6 +16,9 @@
|
|||||||
let loading = false;
|
let loading = false;
|
||||||
let errors = {};
|
let errors = {};
|
||||||
|
|
||||||
|
let query = new URLSearchParams($page.url.search);
|
||||||
|
let redirect = query.get('redirect');
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
const validationRes = authValidation(fields);
|
const validationRes = authValidation(fields);
|
||||||
console.log('validationRes', validationRes);
|
console.log('validationRes', validationRes);
|
||||||
@@ -31,6 +36,10 @@
|
|||||||
});
|
});
|
||||||
console.log('data', data);
|
console.log('data', data);
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|
||||||
|
if (redirect) {
|
||||||
|
goto(redirect);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
submitError = error.error_description || error.message;
|
submitError = error.error_description || error.message;
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import TextField from '$lib/components/Form/TextField.svelte';
|
import TextField from '$lib/components/Form/TextField.svelte';
|
||||||
@@ -11,15 +11,19 @@
|
|||||||
} from '$lib/utils/functions/validator';
|
} from '$lib/utils/functions/validator';
|
||||||
import { SIGNUP_FIELDS } from '$lib/utils/constants/authentication';
|
import { SIGNUP_FIELDS } from '$lib/utils/constants/authentication';
|
||||||
import AuthUI from '$lib/components/AuthUI/index.svelte';
|
import AuthUI from '$lib/components/AuthUI/index.svelte';
|
||||||
|
import { profile } from '$lib/utils/store/user';
|
||||||
|
|
||||||
let supabase = getSupabase();
|
let supabase = getSupabase();
|
||||||
let fields = Object.assign({}, SIGNUP_FIELDS);
|
let fields = Object.assign({}, SIGNUP_FIELDS);
|
||||||
let loading = false;
|
let loading = false;
|
||||||
let success = false;
|
let success = false;
|
||||||
let errors = {};
|
let errors = {};
|
||||||
let submitError;
|
let submitError: string;
|
||||||
let disableSubmit = false;
|
let disableSubmit = false;
|
||||||
let formRef;
|
let formRef: HTMLFormElement;
|
||||||
|
|
||||||
|
let query = new URLSearchParams($page.url.search);
|
||||||
|
let redirect = query.get('redirect');
|
||||||
|
|
||||||
async function handleSubmit(e) {
|
async function handleSubmit(e) {
|
||||||
const validationRes = authValidation(fields);
|
const validationRes = authValidation(fields);
|
||||||
@@ -32,18 +36,55 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
loading = true;
|
loading = true;
|
||||||
const { data, error } = await supabase.auth.signUp({
|
|
||||||
|
const {
|
||||||
|
data: { session },
|
||||||
|
error
|
||||||
|
} = await supabase.auth.signUp({
|
||||||
email: fields.email,
|
email: fields.email,
|
||||||
password: fields.password
|
password: fields.password
|
||||||
});
|
});
|
||||||
console.log('data', data);
|
console.log('session', session);
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|
||||||
|
const { user: authUser } = session || {};
|
||||||
|
if (!authUser) {
|
||||||
|
throw 'Error creating user';
|
||||||
|
}
|
||||||
|
|
||||||
|
const [regexUsernameMatch] = [...(authUser.email?.matchAll(/(.*)@/g) || [])];
|
||||||
|
|
||||||
|
const profileRes = await supabase
|
||||||
|
.from('profile')
|
||||||
|
.insert({
|
||||||
|
id: authUser.id,
|
||||||
|
username: regexUsernameMatch[1] + `${new Date().getTime()}`,
|
||||||
|
fullname: regexUsernameMatch[1],
|
||||||
|
email: authUser.email
|
||||||
|
})
|
||||||
|
.select();
|
||||||
|
console.log('profileRes', profileRes);
|
||||||
|
|
||||||
|
if (profileRes.error) {
|
||||||
|
throw profileRes.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setting profile
|
||||||
|
console.log('setting profile', profileRes.data[0]);
|
||||||
|
profile.set(profileRes.data[0]);
|
||||||
|
|
||||||
|
if (redirect) {
|
||||||
|
goto(redirect);
|
||||||
|
} else {
|
||||||
|
goto('/');
|
||||||
|
}
|
||||||
|
|
||||||
formRef?.reset();
|
formRef?.reset();
|
||||||
|
|
||||||
return goto('/login' + $page.url.search);
|
// return goto('/login' + $page.url.search);
|
||||||
// success = true;
|
success = true;
|
||||||
// fields = Object.assign({}, SIGNUP_FIELDS);
|
fields = Object.assign({}, SIGNUP_FIELDS);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
submitError = error.error_description || error.message;
|
submitError = error.error_description || error.message;
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Reference in New Issue
Block a user