CLASSROOMIO-T-213: Replace all email notifications with Nodemailer
This commit is contained in:
@@ -28,6 +28,7 @@
|
|||||||
"@types/paystack": "^2.0.1",
|
"@types/paystack": "^2.0.1",
|
||||||
"@types/pluralize": "0.0.30",
|
"@types/pluralize": "0.0.30",
|
||||||
"ai": "^2.1.31",
|
"ai": "^2.1.31",
|
||||||
|
"axios": "^1.5.1",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
"canvas-confetti": "^1.6.0",
|
"canvas-confetti": "^1.6.0",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
|
|||||||
@@ -5,12 +5,14 @@
|
|||||||
import IconButton from '$lib/components/IconButton/index.svelte';
|
import IconButton from '$lib/components/IconButton/index.svelte';
|
||||||
import { NavClasses } from '$lib/utils/constants/reusableClass';
|
import { NavClasses } from '$lib/utils/constants/reusableClass';
|
||||||
import NavIcons from './NavIcons.svelte';
|
import NavIcons from './NavIcons.svelte';
|
||||||
|
import { SkeletonPlaceholder, SkeletonText } from 'carbon-components-svelte';
|
||||||
|
|
||||||
export let handleClick = () => {};
|
export let handleClick = () => {};
|
||||||
export let label = '';
|
export let label = '';
|
||||||
export let isGroupActive = false;
|
export let isGroupActive = false;
|
||||||
export let isExpanded = true;
|
export let isExpanded = true;
|
||||||
export let total = 0;
|
export let total = 0;
|
||||||
|
export let isLoading = true;
|
||||||
// export let subMenuItems = [];
|
// export let subMenuItems = [];
|
||||||
|
|
||||||
function onClick() {
|
function onClick() {
|
||||||
@@ -23,32 +25,39 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="">
|
<div class="">
|
||||||
<button
|
{#if isLoading}
|
||||||
class="item relative flex items-center py-3 px-4 ml-2 mb-1 {NavClasses.item} {isGroupActive &&
|
<div class="w-11/12 mx-auto">
|
||||||
NavClasses.active} w-[95%]"
|
<SkeletonText class="py-3 px-4 mb-3 rounded-md" style="height: 40px;" />
|
||||||
tabindex="0"
|
|
||||||
on:click={onClick}
|
|
||||||
>
|
|
||||||
<NavIcons name={label} />
|
|
||||||
<span class="font-bold text-md">{label}</span>
|
|
||||||
{#if total}
|
|
||||||
<span class="ml-1">({total})</span>
|
|
||||||
{/if}
|
|
||||||
<span class="grow" />
|
|
||||||
|
|
||||||
{#if label === 'Lessons'}
|
|
||||||
<IconButton size="small" stopPropagation={true} onClick={toggleIsExpanded}>
|
|
||||||
{#if isExpanded}
|
|
||||||
<ChevronUpIcon class="carbon-icon dark:text-white" />
|
|
||||||
{:else}
|
|
||||||
<ChevronDownIcon class="carbon-icon dark:text-white" />
|
|
||||||
{/if}
|
|
||||||
</IconButton>
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
{#if isExpanded}
|
|
||||||
<div in:slide out:slide class="flex flex-col">
|
|
||||||
<slot />
|
|
||||||
</div>
|
</div>
|
||||||
|
{:else}
|
||||||
|
<button
|
||||||
|
class="item relative flex items-center py-3 px-4 ml-2 mb-1 {NavClasses.item} {isGroupActive &&
|
||||||
|
NavClasses.active} w-[95%]"
|
||||||
|
tabindex="0"
|
||||||
|
on:click={onClick}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
<NavIcons name={label} />
|
||||||
|
<span class="font-bold text-md">{label}</span>
|
||||||
|
{#if total}
|
||||||
|
<span class="ml-1">({total})</span>
|
||||||
|
{/if}
|
||||||
|
<span class="grow" />
|
||||||
|
|
||||||
|
{#if label === 'Lessons' && !isLoading}
|
||||||
|
<IconButton size="small" stopPropagation={true} onClick={toggleIsExpanded}>
|
||||||
|
{#if isExpanded}
|
||||||
|
<ChevronUpIcon class="carbon-icon dark:text-white" />
|
||||||
|
{:else}
|
||||||
|
<ChevronDownIcon class="carbon-icon dark:text-white" />
|
||||||
|
{/if}
|
||||||
|
</IconButton>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
{#if isExpanded}
|
||||||
|
<div in:slide out:slide class="flex flex-col">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -255,6 +255,7 @@
|
|||||||
isGroupActive={(path || $page.url.pathname) === navItem.to}
|
isGroupActive={(path || $page.url.pathname) === navItem.to}
|
||||||
isExpanded={isLessonActive}
|
isExpanded={isLessonActive}
|
||||||
total={navItem.isLecture ? ($lessons || []).length : 0}
|
total={navItem.isLecture ? ($lessons || []).length : 0}
|
||||||
|
isLoading={!$course.id}
|
||||||
>
|
>
|
||||||
{#if navItem.isLecture}
|
{#if navItem.isLecture}
|
||||||
{#each $lessons as item, index}
|
{#each $lessons as item, index}
|
||||||
|
|||||||
@@ -12,10 +12,12 @@
|
|||||||
import { currentOrg } from '$lib/utils/store/org';
|
import { currentOrg } from '$lib/utils/store/org';
|
||||||
import { getOrgTeam } from '$lib/utils/services/org';
|
import { getOrgTeam } from '$lib/utils/services/org';
|
||||||
import type { OrgTeamMember } from '$lib/utils/types/org';
|
import type { OrgTeamMember } from '$lib/utils/types/org';
|
||||||
import { sendTeacherCourseWelcome } from './utils';
|
|
||||||
import type { Course } from '$lib/utils/types';
|
|
||||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||||
import { getStudentInviteLink } from '$lib/utils/functions/course';
|
import { getStudentInviteLink } from '$lib/utils/functions/course';
|
||||||
|
import {
|
||||||
|
triggerSendEmail,
|
||||||
|
NOTIFICATION_NAME
|
||||||
|
} from '$lib/utils/services/notification/notification';
|
||||||
|
|
||||||
interface Tutor {
|
interface Tutor {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -52,7 +54,7 @@
|
|||||||
role_id: ROLE.TUTOR
|
role_id: ROLE.TUTOR
|
||||||
});
|
});
|
||||||
|
|
||||||
sendTeacherCourseWelcome({
|
triggerSendEmail(NOTIFICATION_NAME.WELCOME_TEACHER_TO_COURSE, {
|
||||||
to: tutor.email,
|
to: tutor.email,
|
||||||
name: tutor.text,
|
name: tutor.text,
|
||||||
orgName: $currentOrg.name,
|
orgName: $currentOrg.name,
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
import { supabase } from '$lib/utils/functions/supabase';
|
|
||||||
|
|
||||||
export async function sendTeacherCourseWelcome(params: {
|
|
||||||
to: string;
|
|
||||||
name: string;
|
|
||||||
orgName: string;
|
|
||||||
orgSiteName: string;
|
|
||||||
courseName: string;
|
|
||||||
}) {
|
|
||||||
const { data } = await supabase.auth.getSession();
|
|
||||||
const accessToken = data.session?.access_token || '';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api/email/course/teacher_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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,8 +4,11 @@
|
|||||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||||
import { coursePaymentValidation } from '$lib/utils/functions/validator';
|
import { coursePaymentValidation } from '$lib/utils/functions/validator';
|
||||||
import { Link } from 'carbon-components-svelte';
|
import { Link } from 'carbon-components-svelte';
|
||||||
import { sendTeacherStudentJoinRequest, sendStudentRequestReceipt } from './utils';
|
|
||||||
import { currentOrg } from '$lib/utils/store/org';
|
import { currentOrg } from '$lib/utils/store/org';
|
||||||
|
import {
|
||||||
|
triggerSendEmail,
|
||||||
|
NOTIFICATION_NAME
|
||||||
|
} from '$lib/utils/services/notification/notification';
|
||||||
|
|
||||||
export let open = false;
|
export let open = false;
|
||||||
export let paymentLink = '';
|
export let paymentLink = '';
|
||||||
@@ -41,7 +44,7 @@
|
|||||||
step = STEPS.STEP_2;
|
step = STEPS.STEP_2;
|
||||||
|
|
||||||
// Send email to first added teacher of student wanting to join
|
// Send email to first added teacher of student wanting to join
|
||||||
sendTeacherStudentJoinRequest({
|
triggerSendEmail(NOTIFICATION_NAME.SEND_TEACHER_STUDENT_BUY_REQUEST, {
|
||||||
to: teacherEmail,
|
to: teacherEmail,
|
||||||
courseName,
|
courseName,
|
||||||
studentEmail: fields.email,
|
studentEmail: fields.email,
|
||||||
@@ -54,7 +57,7 @@
|
|||||||
|
|
||||||
function onClickPaymentLink() {
|
function onClickPaymentLink() {
|
||||||
// Send email to student to send proof of payment to teacher
|
// Send email to student to send proof of payment to teacher
|
||||||
sendStudentRequestReceipt({
|
triggerSendEmail(NOTIFICATION_NAME.STUDENT_PROOVE_COURSE_PAYMENT, {
|
||||||
to: fields.email,
|
to: fields.email,
|
||||||
courseName,
|
courseName,
|
||||||
teacherEmail,
|
teacherEmail,
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
import { supabase } from '$lib/utils/functions/supabase';
|
|
||||||
|
|
||||||
export async function sendTeacherStudentJoinRequest(params: {
|
|
||||||
to: string;
|
|
||||||
courseName: string;
|
|
||||||
studentEmail: string;
|
|
||||||
studentFullname: string;
|
|
||||||
}) {
|
|
||||||
const { data } = await supabase.auth.getSession();
|
|
||||||
const accessToken = data.session?.access_token || '';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api/email/course/teacher_student_buycourse', {
|
|
||||||
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 sendStudentRequestReceipt(params: {
|
|
||||||
to: string;
|
|
||||||
courseName: string;
|
|
||||||
orgName: string;
|
|
||||||
studentFullname: string;
|
|
||||||
teacherEmail: string;
|
|
||||||
}) {
|
|
||||||
const { data } = await supabase.auth.getSession();
|
|
||||||
const accessToken = data.session?.access_token || '';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api/email/course/student_prove_payment', {
|
|
||||||
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,4 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import NotificationIcon from 'carbon-icons-svelte/lib/Notification.svelte';
|
import NotificationIcon from 'carbon-icons-svelte/lib/Notification.svelte';
|
||||||
@@ -28,6 +28,10 @@
|
|||||||
localStorage.setItem('mode', $appStore.isDark ? 'dark' : '');
|
localStorage.setItem('mode', $appStore.isDark ? 'dark' : '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function shortenName(name: string) {
|
||||||
|
return name?.substring(0, 2)?.toUpperCase() || '';
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav class="{navClass} flex w-full p-2 md:px-6 bg-primary-700 h-[48px]">
|
<nav class="{navClass} flex w-full p-2 md:px-6 bg-primary-700 h-[48px]">
|
||||||
@@ -58,7 +62,10 @@
|
|||||||
className="mr-2"
|
className="mr-2"
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<TextChip value={$currentOrg.shortName} className="bg-primary-200 font-bold" />
|
<TextChip
|
||||||
|
value={shortenName($currentOrg.name)}
|
||||||
|
className="bg-primary-200 font-bold mr-2"
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<span class="line-clamp-1">
|
<span class="line-clamp-1">
|
||||||
{$currentOrg.name}
|
{$currentOrg.name}
|
||||||
|
|||||||
@@ -14,7 +14,10 @@
|
|||||||
import { profile } from '$lib/utils/store/user';
|
import { profile } from '$lib/utils/store/user';
|
||||||
import { supabase } from '$lib/utils/functions/supabase';
|
import { supabase } from '$lib/utils/functions/supabase';
|
||||||
import SectionTitle from '../SectionTitle.svelte';
|
import SectionTitle from '../SectionTitle.svelte';
|
||||||
import { sendInvite } from './utils';
|
import {
|
||||||
|
triggerSendEmail,
|
||||||
|
NOTIFICATION_NAME
|
||||||
|
} from '$lib/utils/services/notification/notification';
|
||||||
|
|
||||||
let emailsStr = '';
|
let emailsStr = '';
|
||||||
let errorMessage = '';
|
let errorMessage = '';
|
||||||
@@ -68,11 +71,13 @@
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send email invite
|
triggerSendEmail(NOTIFICATION_NAME.INVITE_TEACHER, {
|
||||||
sendInvite(email, {
|
email: newMember.email,
|
||||||
id: $currentOrg.id,
|
org: {
|
||||||
name: $currentOrg.name,
|
id: $currentOrg.id,
|
||||||
siteName: $currentOrg.siteName
|
name: $currentOrg.name,
|
||||||
|
siteName: $currentOrg.siteName
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const isLast = index === emails.length - 1;
|
const isLast = index === emails.length - 1;
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import { supabase } from '$lib/utils/functions/supabase';
|
|
||||||
|
|
||||||
export async function sendInvite(email: string, org: any) {
|
|
||||||
const { data } = await supabase.auth.getSession();
|
|
||||||
const accessToken = data.session?.access_token || '';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api/email/invite', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: accessToken
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
email,
|
|
||||||
org
|
|
||||||
})
|
|
||||||
});
|
|
||||||
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,11 +1,24 @@
|
|||||||
import { supabase } from '$lib/utils/functions/supabase';
|
import { supabase } from '$lib/utils/functions/supabase';
|
||||||
|
|
||||||
export const NOTIFICATION_NAME = {
|
export const NOTIFICATION_NAME = {
|
||||||
WELCOME_TO_APP: 'WELCOME TO APP'
|
WELCOME_TO_APP: 'WELCOME TO APP',
|
||||||
|
INVITE_TEACHER: 'INVITE TEACHER',
|
||||||
|
WELCOME_TEACHER_TO_COURSE: 'WELCOME TEACHER TO COURSE',
|
||||||
|
SEND_TEACHER_STUDENT_BUY_REQUEST: 'SEND TEACHER STUDENT BUY REQUEST',
|
||||||
|
STUDENT_PROOVE_COURSE_PAYMENT: 'STUDENT PROOVE COURSE PAYMENT',
|
||||||
|
STUDENT_COURSE_WELCOME: 'STUDENT COURSE WELCOME',
|
||||||
|
TEACHER_STUDENT_JOINED: 'TEACHER STUDENT JOINED'
|
||||||
};
|
};
|
||||||
|
|
||||||
const NAME_TO_PATH = {
|
const NAME_TO_PATH = {
|
||||||
[NOTIFICATION_NAME.WELCOME_TO_APP]: '/api/email/welcome'
|
[NOTIFICATION_NAME.WELCOME_TO_APP]: '/api/email/welcome',
|
||||||
|
[NOTIFICATION_NAME.INVITE_TEACHER]: '/api/email/invite',
|
||||||
|
[NOTIFICATION_NAME.WELCOME_TEACHER_TO_COURSE]: '/api/email/course/teacher_welcome',
|
||||||
|
[NOTIFICATION_NAME.SEND_TEACHER_STUDENT_BUY_REQUEST]:
|
||||||
|
'/api/email/course/teacher_student_buycourse',
|
||||||
|
[NOTIFICATION_NAME.STUDENT_PROOVE_COURSE_PAYMENT]: '/api/email/course/student_prove_payment',
|
||||||
|
[NOTIFICATION_NAME.STUDENT_COURSE_WELCOME]: '/api/email/course/student_welcome',
|
||||||
|
[NOTIFICATION_NAME.TEACHER_STUDENT_JOINED]: '/api/email/course/teacher_student_joined'
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAccessToken = async () => {
|
const getAccessToken = async () => {
|
||||||
@@ -14,7 +27,7 @@ const getAccessToken = async () => {
|
|||||||
return data.session?.access_token || '';
|
return data.session?.access_token || '';
|
||||||
};
|
};
|
||||||
|
|
||||||
export const triggerSendEmail = async (name: string, body: { [k: string]: string }) => {
|
export const triggerSendEmail = async (name: string, body: { [k: string]: any }) => {
|
||||||
try {
|
try {
|
||||||
const accessToken = await getAccessToken();
|
const accessToken = await getAccessToken();
|
||||||
const path = NAME_TO_PATH[name];
|
const path = NAME_TO_PATH[name];
|
||||||
@@ -37,8 +50,8 @@ export const triggerSendEmail = async (name: string, body: { [k: string]: string
|
|||||||
}
|
}
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
console.log(`${name} sent email result`, result);
|
console.log(name, result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(`${name} sent email error`, error);
|
console.error(name, error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import nodemailer from 'nodemailer';
|
import nodemailer from 'nodemailer';
|
||||||
import { withEmailTemplate } from './template';
|
import { withEmailTemplate } from './template';
|
||||||
import { SMTP_HOST, SMTP_USER, SMTP_PASSWORD } from '$env/static/private';
|
import {
|
||||||
|
SMTP_HOST,
|
||||||
|
SMTP_USER,
|
||||||
|
SMTP_PASSWORD,
|
||||||
|
SMTP_USER_HELP,
|
||||||
|
SMTP_PASSWORD_HELP
|
||||||
|
} from '$env/static/private';
|
||||||
|
|
||||||
const content = `
|
const content = `
|
||||||
<div>
|
<div>
|
||||||
@@ -13,18 +19,20 @@ Your ClassroomIO Team
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const transporter = nodemailer.createTransport({
|
let transporter: nodemailer.Transporter;
|
||||||
host: SMTP_HOST,
|
|
||||||
port: 465,
|
|
||||||
secure: true,
|
|
||||||
auth: {
|
|
||||||
user: SMTP_USER,
|
|
||||||
pass: SMTP_PASSWORD
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const getTransporter = async (): Promise<boolean> => {
|
const getTransporter = async (isPersonal?: boolean): Promise<boolean> => {
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
|
transporter = nodemailer.createTransport({
|
||||||
|
host: SMTP_HOST,
|
||||||
|
port: 465,
|
||||||
|
secure: true,
|
||||||
|
auth: {
|
||||||
|
user: isPersonal ? SMTP_USER : SMTP_USER_HELP,
|
||||||
|
pass: isPersonal ? SMTP_PASSWORD : SMTP_PASSWORD_HELP
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
transporter.verify(function (error, success) {
|
transporter.verify(function (error, success) {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.log('Transporter error', error);
|
console.log('Transporter error', error);
|
||||||
@@ -38,19 +46,26 @@ const getTransporter = async (): Promise<boolean> => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const sendEmail = async ({
|
export const sendEmail = async ({
|
||||||
|
from,
|
||||||
to,
|
to,
|
||||||
subject,
|
subject,
|
||||||
content
|
content,
|
||||||
|
isPersonalEmail,
|
||||||
|
replyTo
|
||||||
}: {
|
}: {
|
||||||
|
from?: string;
|
||||||
to: string;
|
to: string;
|
||||||
subject: string;
|
subject: string;
|
||||||
content: string;
|
content: string;
|
||||||
|
isPersonalEmail?: boolean;
|
||||||
|
replyTo?: string;
|
||||||
}) => {
|
}) => {
|
||||||
await getTransporter();
|
await getTransporter(isPersonalEmail);
|
||||||
const info = await transporter.sendMail({
|
const info = await transporter.sendMail({
|
||||||
from: '"Best from ClassroomIO" <best@classroomio.com>', // sender address
|
from: from || '"Best from ClassroomIO" <best@classroomio.com>', // sender address
|
||||||
to, // list of receivers
|
to, // list of receivers
|
||||||
subject, // Subject line
|
subject, // Subject line
|
||||||
|
replyTo,
|
||||||
html: withEmailTemplate(content) // html body
|
html: withEmailTemplate(content) // html body
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export const withEmailTemplate = (content: string) =>
|
|||||||
|
|
||||||
.button {
|
.button {
|
||||||
margin-top:12px;
|
margin-top:12px;
|
||||||
background: #0f172a;
|
background: #0233BD;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
text-decoration: none !important;
|
text-decoration: none !important;
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
@@ -110,10 +110,10 @@ export const withEmailTemplate = (content: string) =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: #00c4b8;
|
color: #0233BD;
|
||||||
}
|
}
|
||||||
a:hover {
|
a:hover {
|
||||||
color: #00e6ca;
|
color: #0233BD;
|
||||||
}
|
}
|
||||||
h1,
|
h1,
|
||||||
h2,
|
h2,
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { render } from 'svelte-email';
|
|
||||||
import { json } from '@sveltejs/kit';
|
import { json } from '@sveltejs/kit';
|
||||||
import JoinRequestTemplate from './template.svelte';
|
|
||||||
import { getSendgrid, SENDGRID_FROM_NOREPLY } from '$lib/utils/services/sendgrid';
|
|
||||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||||
|
import { sendEmail } from '$lib/utils/services/notification/send';
|
||||||
|
|
||||||
const sendgrid = getSendgrid();
|
|
||||||
const supabase = getSupabase();
|
const supabase = getSupabase();
|
||||||
|
|
||||||
export async function POST({ request }) {
|
export async function POST({ request }) {
|
||||||
@@ -35,23 +32,19 @@ export async function POST({ request }) {
|
|||||||
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = {
|
await sendEmail({
|
||||||
from: SENDGRID_FROM_NOREPLY,
|
from: `"${orgName} - ClassroomIO" <help@classroomio.com>`,
|
||||||
to,
|
to,
|
||||||
replyTo: teacherEmail,
|
replyTo: teacherEmail,
|
||||||
subject: `[${courseName}] One more step left`,
|
subject: `[${courseName}] One more step left`,
|
||||||
html: render({
|
content: `
|
||||||
template: JoinRequestTemplate,
|
<p>Hi ${studentFullname},</p>
|
||||||
props: {
|
<p>You are one step closer to joining: ${courseName}</p>
|
||||||
courseName,
|
<p>Please send your proof of payment to: ${teacherEmail}, in order to join the course.</p>
|
||||||
studentFullname,
|
<p>Talk to you soon and see you in class.</p>
|
||||||
teacherEmail,
|
<p>${orgName}</p>
|
||||||
orgName
|
`
|
||||||
}
|
});
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
sendgrid.send(options);
|
|
||||||
|
|
||||||
return json({
|
return json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { Text } from 'svelte-email';
|
|
||||||
|
|
||||||
export let courseName = '';
|
|
||||||
export let studentFullname = '';
|
|
||||||
export let teacherEmail = '';
|
|
||||||
export let orgName = '';
|
|
||||||
|
|
||||||
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}>Hi {studentFullname},</Text>
|
|
||||||
<Text style={paragraphContent}>
|
|
||||||
You are one step closer to joining: {courseName}
|
|
||||||
</Text>
|
|
||||||
<Text style={paragraphContent}>
|
|
||||||
Please send your proof of payment to: {teacherEmail}, in order to join the course.
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Text style={footerParagraph}>Talk to you soon and see you in class.</Text>
|
|
||||||
<Text style={footerParagraph}>{orgName}</Text>
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import EmailTemplate from '$lib/components/Email/EmailTemplate.svelte';
|
|
||||||
import ProvePayment from './prove_payment.svelte';
|
|
||||||
|
|
||||||
export let courseName = '';
|
|
||||||
export let studentFullname = '';
|
|
||||||
export let orgName = '';
|
|
||||||
export let teacherEmail = '';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<EmailTemplate
|
|
||||||
children={ProvePayment}
|
|
||||||
props={{
|
|
||||||
courseName,
|
|
||||||
studentFullname,
|
|
||||||
orgName,
|
|
||||||
teacherEmail
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
import { render } from 'svelte-email';
|
|
||||||
import { json } from '@sveltejs/kit';
|
import { json } from '@sveltejs/kit';
|
||||||
import WelcomeTemplate from './template.svelte';
|
|
||||||
import { getSendgrid, SENDGRID_FROM_NOREPLY } from '$lib/utils/services/sendgrid';
|
|
||||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||||
|
import { sendEmail } from '$lib/utils/services/notification/send';
|
||||||
|
|
||||||
const sendgrid = getSendgrid();
|
|
||||||
const supabase = getSupabase();
|
const supabase = getSupabase();
|
||||||
|
|
||||||
// Sends email to student when they successfully join a course
|
// Sends email to student when they successfully join a course
|
||||||
@@ -29,20 +26,19 @@ export async function POST({ request }) {
|
|||||||
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = {
|
await sendEmail({
|
||||||
from: SENDGRID_FROM_NOREPLY,
|
from: `"${orgName} - ClassroomIO" <help@classroomio.com>`,
|
||||||
to,
|
to,
|
||||||
subject: `[${orgName}] Welcome to Class`,
|
subject: `[${orgName}] Welcome to Class 🎉`,
|
||||||
html: render({
|
content: `
|
||||||
template: WelcomeTemplate,
|
<p>Hi there,</p>
|
||||||
props: {
|
<p>Congratulations 🎉, you’ve successfully joined: <strong>${courseName}</strong></p>
|
||||||
orgName,
|
<p>Everything has been set up for you to have an amazing learning experience.</p>
|
||||||
courseName
|
<p>If you run into any issues, don’t fail to reach out to your instructor(s).</p>
|
||||||
}
|
<p>Cheers,</p>
|
||||||
})
|
<p>${orgName}</p>
|
||||||
};
|
`
|
||||||
|
});
|
||||||
sendgrid.send(options);
|
|
||||||
|
|
||||||
return json({
|
return json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
<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
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
import { render } from 'svelte-email';
|
|
||||||
import { json } from '@sveltejs/kit';
|
import { json } from '@sveltejs/kit';
|
||||||
import JoinRequestTemplate from './template.svelte';
|
|
||||||
import { getSendgrid, SENDGRID_FROM_NOREPLY } from '$lib/utils/services/sendgrid';
|
|
||||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||||
|
import { sendEmail } from '$lib/utils/services/notification/send';
|
||||||
|
|
||||||
const sendgrid = getSendgrid();
|
|
||||||
const supabase = getSupabase();
|
const supabase = getSupabase();
|
||||||
|
|
||||||
export async function POST({ request }) {
|
export async function POST({ request }) {
|
||||||
@@ -34,21 +31,20 @@ export async function POST({ request }) {
|
|||||||
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = {
|
await sendEmail({
|
||||||
from: SENDGRID_FROM_NOREPLY,
|
from: `ClassroomIO" <help@classroomio.com>`,
|
||||||
to,
|
to,
|
||||||
subject: `[${courseName}] Request to Join Course!`,
|
subject: `[${courseName}] Request to Join Course!`,
|
||||||
html: render({
|
content: `
|
||||||
template: JoinRequestTemplate,
|
<p>Hi amazing tutor,</p>
|
||||||
props: {
|
<p> A new student has requested to join a course you are teaching: “${courseName}"</p>
|
||||||
courseName,
|
<p style="font: bold;">Student details</p>
|
||||||
studentFullname,
|
<p>
|
||||||
studentEmail
|
Name: ${studentFullname}<br />
|
||||||
}
|
Email: ${studentEmail}
|
||||||
})
|
</p>
|
||||||
};
|
`
|
||||||
|
});
|
||||||
sendgrid.send(options);
|
|
||||||
|
|
||||||
return json({
|
return json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { Text, Link } from 'svelte-email';
|
|
||||||
|
|
||||||
export let courseName = '';
|
|
||||||
export let studentFullname = '';
|
|
||||||
export let studentEmail = '';
|
|
||||||
|
|
||||||
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}>
|
|
||||||
A new student has requested to join a course you are teaching: “{courseName}"
|
|
||||||
</Text>
|
|
||||||
<Text style={paragraphContent}>Student details</Text>
|
|
||||||
<Text style={paragraphContent}>
|
|
||||||
Name: {studentFullname}<br />
|
|
||||||
Email: {studentEmail}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Text style={footerParagraph}>Cheers</Text>
|
|
||||||
<Text style={footerParagraph}>
|
|
||||||
<Link style={linkStyle} href="https://classroomio.com">ClassroomIO</Link>
|
|
||||||
</Text>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import EmailTemplate from '$lib/components/Email/EmailTemplate.svelte';
|
|
||||||
import JoinRequest from './join_request.svelte';
|
|
||||||
|
|
||||||
export let courseName = '';
|
|
||||||
export let studentFullname = '';
|
|
||||||
export let studentEmail = '';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<EmailTemplate
|
|
||||||
children={JoinRequest}
|
|
||||||
props={{
|
|
||||||
courseName,
|
|
||||||
studentFullname,
|
|
||||||
studentEmail
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
import { render } from 'svelte-email';
|
|
||||||
import { json } from '@sveltejs/kit';
|
import { json } from '@sveltejs/kit';
|
||||||
import WelcomeTemplate from './template.svelte';
|
|
||||||
import { getSendgrid, SENDGRID_FROM_NOREPLY } from '$lib/utils/services/sendgrid';
|
|
||||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||||
|
import { sendEmail } from '$lib/utils/services/notification/send';
|
||||||
|
|
||||||
const sendgrid = getSendgrid();
|
|
||||||
const supabase = getSupabase();
|
const supabase = getSupabase();
|
||||||
|
|
||||||
export async function POST({ request }) {
|
export async function POST({ request }) {
|
||||||
@@ -28,19 +25,18 @@ export async function POST({ request }) {
|
|||||||
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = {
|
await sendEmail({
|
||||||
from: SENDGRID_FROM_NOREPLY,
|
from: `"ClassroomIO" <help@classroomio.com>`,
|
||||||
to,
|
to,
|
||||||
subject: `[${courseName}] You've got a new student!`,
|
subject: `[${courseName}] You've got a new student 🎉!`,
|
||||||
html: render({
|
content: `
|
||||||
template: WelcomeTemplate,
|
<p>Hi amazing tutor,</p>
|
||||||
props: {
|
<p>Congratulations 🎉, a new student has joined a course you are teaching: ${courseName}</p>
|
||||||
courseName
|
<p>We hope they have a great experience learning from the best (YOU).</p>
|
||||||
}
|
<p>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.</p>
|
||||||
};
|
`
|
||||||
|
});
|
||||||
sendgrid.send(options);
|
|
||||||
|
|
||||||
return json({
|
return json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
<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
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
<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,10 +1,7 @@
|
|||||||
import { render } from 'svelte-email';
|
|
||||||
import { json } from '@sveltejs/kit';
|
import { json } from '@sveltejs/kit';
|
||||||
import WelcomeTemplate from './template.svelte';
|
|
||||||
import { getSendgrid, SENDGRID_FROM_NOREPLY } from '$lib/utils/services/sendgrid';
|
|
||||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||||
|
import { sendEmail } from '$lib/utils/services/notification/send';
|
||||||
|
|
||||||
const sendgrid = getSendgrid();
|
|
||||||
const supabase = getSupabase();
|
const supabase = getSupabase();
|
||||||
|
|
||||||
export async function POST({ request }) {
|
export async function POST({ request }) {
|
||||||
@@ -31,22 +28,19 @@ export async function POST({ request }) {
|
|||||||
const origin = request.headers.get('origin');
|
const origin = request.headers.get('origin');
|
||||||
const inviteLink = `${origin}/org/${orgSiteName}/courses`;
|
const inviteLink = `${origin}/org/${orgSiteName}/courses`;
|
||||||
|
|
||||||
const options = {
|
await sendEmail({
|
||||||
from: SENDGRID_FROM_NOREPLY,
|
from: `"[${orgName}] - ClassroomIO" <help@classroomio.com>`,
|
||||||
to,
|
to,
|
||||||
subject: `You have been invited to a course in ${orgName}!`,
|
subject: `You have been invited to a course in ${orgName}!`,
|
||||||
html: render({
|
content: `
|
||||||
template: WelcomeTemplate,
|
<p>Hey ${name},</p>
|
||||||
props: {
|
<p> You have been given access to teach a course by ${orgName}</p>
|
||||||
name,
|
<p>The course is titled: ${courseName}</p>
|
||||||
orgName,
|
<div>
|
||||||
inviteLink,
|
<a class="button" href="${inviteLink}">Open Dashboard</a>
|
||||||
courseName
|
</div>
|
||||||
}
|
`
|
||||||
})
|
});
|
||||||
};
|
|
||||||
|
|
||||||
sendgrid.send(options);
|
|
||||||
|
|
||||||
return json({
|
return json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import EmailTemplate from '$lib/components/Email/EmailTemplate.svelte';
|
|
||||||
import Welcome from './welcome.svelte';
|
|
||||||
|
|
||||||
export let name = '';
|
|
||||||
export let orgName = '';
|
|
||||||
export let inviteLink = '';
|
|
||||||
export let courseName = '';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<EmailTemplate
|
|
||||||
children={Welcome}
|
|
||||||
props={{
|
|
||||||
name,
|
|
||||||
orgName,
|
|
||||||
inviteLink,
|
|
||||||
courseName
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { Text, Section, Button, Link } from 'svelte-email';
|
|
||||||
|
|
||||||
export let name = '';
|
|
||||||
export let orgName = '';
|
|
||||||
export let inviteLink = '';
|
|
||||||
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 btnContainer = {
|
|
||||||
textAlign: 'center' as const
|
|
||||||
};
|
|
||||||
|
|
||||||
const button = {
|
|
||||||
fontFamily,
|
|
||||||
backgroundColor: '#1d4ee2',
|
|
||||||
borderRadius: '5px',
|
|
||||||
color: '#fff',
|
|
||||||
fontSize: '16px',
|
|
||||||
textDecoration: 'none',
|
|
||||||
textAlign: 'center' as const,
|
|
||||||
display: 'block'
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Text style={paragraph}>Hey {name},</Text>
|
|
||||||
<Text style={paragraphContent}>
|
|
||||||
You have been given access to teach a course by {orgName}
|
|
||||||
</Text>
|
|
||||||
<Text style={paragraphContent}>
|
|
||||||
The course is titled: {courseName}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Section style={btnContainer}>
|
|
||||||
<Button pX={12} pY={12} style={button} href={inviteLink}>Open Dashboard</Button>
|
|
||||||
</Section>
|
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
import { render } from 'svelte-email';
|
|
||||||
import { json } from '@sveltejs/kit';
|
import { json } from '@sveltejs/kit';
|
||||||
import TeacherInviteTemplate from './template.svelte';
|
|
||||||
import { getSendgrid, SENDGRID_FROM_NOREPLY } from '$lib/utils/services/sendgrid';
|
|
||||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||||
|
import { sendEmail } from '$lib/utils/services/notification/send';
|
||||||
|
|
||||||
const sendgrid = getSendgrid();
|
|
||||||
const supabase = getSupabase();
|
const supabase = getSupabase();
|
||||||
|
|
||||||
// API to send invite to teacher
|
// API to send invite to teacher
|
||||||
@@ -45,20 +42,18 @@ export async function POST({ request }) {
|
|||||||
const inviteLink = `${origin}/invite/t/${encodeURIComponent(btoa(inviteData))}`;
|
const inviteLink = `${origin}/invite/t/${encodeURIComponent(btoa(inviteData))}`;
|
||||||
console.log('inviteData', inviteData);
|
console.log('inviteData', inviteData);
|
||||||
|
|
||||||
const options = {
|
await sendEmail({
|
||||||
from: SENDGRID_FROM_NOREPLY,
|
|
||||||
to: email,
|
to: email,
|
||||||
subject: `Join ${name} on ClassroomIO`,
|
subject: `Join ${name} on ClassroomIO 😃`,
|
||||||
html: render({
|
content: `
|
||||||
template: TeacherInviteTemplate,
|
<p>Hey there,</p>
|
||||||
props: {
|
<p> You have been invited to join ${name} on ClassroomIO 🎉🎉🎉.</p>
|
||||||
orgName: name,
|
<div>
|
||||||
inviteLink
|
<a class="button" href="${inviteLink}">Accept Invitation</a>
|
||||||
}
|
</div>
|
||||||
})
|
`,
|
||||||
};
|
isPersonalEmail: true
|
||||||
|
});
|
||||||
sendgrid.send(options);
|
|
||||||
|
|
||||||
return json({
|
return json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { Text, Section, Button, Link } from 'svelte-email';
|
|
||||||
|
|
||||||
export let orgName = '';
|
|
||||||
export let inviteLink = '';
|
|
||||||
|
|
||||||
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 btnContainer = {
|
|
||||||
textAlign: 'center' as const
|
|
||||||
};
|
|
||||||
|
|
||||||
const button = {
|
|
||||||
fontFamily,
|
|
||||||
backgroundColor: '#1d4ee2',
|
|
||||||
borderRadius: '5px',
|
|
||||||
color: '#fff',
|
|
||||||
fontSize: '16px',
|
|
||||||
textDecoration: 'none',
|
|
||||||
textAlign: 'center' as const,
|
|
||||||
display: 'block'
|
|
||||||
};
|
|
||||||
|
|
||||||
const linkStyle = {
|
|
||||||
color: '#1d4ee2'
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Text style={paragraph}>Hey there,</Text>
|
|
||||||
<Text style={paragraphContent}>
|
|
||||||
You have been invited to join {orgName} on ClassroomIO.
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Section style={btnContainer}>
|
|
||||||
<Button pX={12} pY={12} style={button} href={inviteLink}>Accept Invitation</Button>
|
|
||||||
</Section>
|
|
||||||
<Text style={footerParagraph}>Best regards</Text>
|
|
||||||
<Text style={footerParagraph}>Rotimi Best</Text>
|
|
||||||
<Text style={footerParagraph}
|
|
||||||
><Link style={linkStyle} href="https://classroomio.com">ClassroomIO</Link></Text
|
|
||||||
>
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import EmailTemplate from '$lib/components/Email/EmailTemplate.svelte';
|
|
||||||
import Invitation from './invitation.svelte';
|
|
||||||
|
|
||||||
export let orgName = '';
|
|
||||||
export let inviteLink = '';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<EmailTemplate
|
|
||||||
children={Invitation}
|
|
||||||
props={{
|
|
||||||
orgName,
|
|
||||||
inviteLink
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
@@ -1,11 +1,7 @@
|
|||||||
import { render } from 'svelte-email';
|
|
||||||
import { json } from '@sveltejs/kit';
|
import { json } from '@sveltejs/kit';
|
||||||
import { getSendgrid, SENDGRID_FROM } from '$lib/utils/services/sendgrid';
|
|
||||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||||
|
|
||||||
import { sendEmail } from '$lib/utils/services/notification/send';
|
import { sendEmail } from '$lib/utils/services/notification/send';
|
||||||
|
|
||||||
const sendgrid = getSendgrid();
|
|
||||||
const supabase = getSupabase();
|
const supabase = getSupabase();
|
||||||
|
|
||||||
export async function POST({ request }) {
|
export async function POST({ request }) {
|
||||||
@@ -50,7 +46,8 @@ export async function POST({ request }) {
|
|||||||
<div>
|
<div>
|
||||||
<a href="https://app.classroomio.com">Go to Dashboard</a>
|
<a href="https://app.classroomio.com">Go to Dashboard</a>
|
||||||
</div>
|
</div>
|
||||||
`
|
`,
|
||||||
|
isPersonalEmail: true
|
||||||
});
|
});
|
||||||
|
|
||||||
return json({
|
return json({
|
||||||
|
|||||||
@@ -10,7 +10,11 @@
|
|||||||
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';
|
import {
|
||||||
|
triggerSendEmail,
|
||||||
|
NOTIFICATION_NAME
|
||||||
|
} from '$lib/utils/services/notification/notification';
|
||||||
|
import { snackbar } from '$lib/components/Snackbar/store.js';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
|
|
||||||
@@ -57,19 +61,20 @@
|
|||||||
loading = false;
|
loading = false;
|
||||||
if (addedMember.error) {
|
if (addedMember.error) {
|
||||||
console.error('Error adding student to group', data.groupId);
|
console.error('Error adding student to group', data.groupId);
|
||||||
|
snackbar.error('Joining failed, please contact your admin');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send email welcoming student to the course
|
// Send email welcoming student to the course
|
||||||
sendStudentCourseWelcome({
|
triggerSendEmail(NOTIFICATION_NAME.STUDENT_COURSE_WELCOME, {
|
||||||
to: $profile.email,
|
to: $profile.email,
|
||||||
orgName: data.currentOrg?.name,
|
orgName: data.currentOrg?.name,
|
||||||
courseName: data.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.
|
||||||
sendTeacherStudentJoinedCourse({
|
triggerSendEmail(NOTIFICATION_NAME.TEACHER_STUDENT_JOINED, {
|
||||||
to: teachers,
|
to: teachers[0],
|
||||||
courseName: data.name
|
courseName: data.name
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
|
|
||||||
<section class="max-w-6xl gap-5 mx-auto">
|
<section class="max-w-6xl gap-5 mx-auto">
|
||||||
<div class="m-5">
|
<div class="m-5">
|
||||||
<h1 class="text-3xl font-semibold">Dashboard</h1>
|
<h1 class="text-3xl font-semibold">Welcome</h1>
|
||||||
<div
|
<div
|
||||||
class="w-full h-fit lg:h-[265px] flex md:items-center justify-between flex-col-reverse md:flex-row p-5 lg:p-10 rounded-md bg-primary-900 my-2"
|
class="w-full h-fit lg:h-[265px] flex md:items-center justify-between flex-col-reverse md:flex-row p-5 lg:p-10 rounded-md bg-primary-900 my-2"
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user