Skip to content

Commit

Permalink
Merge pull request #251 from recogito/lwj/send-email-to-list
Browse files Browse the repository at this point in the history
Add emails to invite list of users
  • Loading branch information
lwjameson authored Sep 5, 2024
2 parents 72ca9a5 + 1a52320 commit 8e39490
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 135 deletions.
3 changes: 1 addition & 2 deletions src/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -429,9 +429,8 @@ export type GroupMember = {
};

export type ApiPostInviteUserToProject = {
email: string;
users: { email: string; projectGroupId: string }[];
projectId: string;
projectName: string;
projectGroupId: string;
invitedBy: string;
};
36 changes: 34 additions & 2 deletions src/backend/crud/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,9 @@ export const inviteUserToProject = (
): Promise<Invitation> =>
new Promise((resolve, reject) => {
const payload: ApiPostInviteUserToProject = {
email,
users: [{ email: email, projectGroupId: groupId }],
projectId: project.id,
projectName: project.name,
projectGroupId: groupId,
invitedBy: invitedBy || '',
};

Expand Down Expand Up @@ -166,6 +165,39 @@ export const inviteUsersToProject = (
groupIds: { [key: string]: string },
invitedBy?: string
): Response<Invitation[]> => {
new Promise((resolve, reject) => {
const payload: ApiPostInviteUserToProject = {
users: users.map((u) => ({
email: u.email,
projectGroupId: groupIds[u.role],
})),
projectId: project.id,
projectName: project.name,
invitedBy: invitedBy || '',
};

return supabase.auth.getSession().then(({ error, data }) => {
// Get Supabase session token first
if (error) {
// Shouldn't really happen at this point
reject(error);
} else {
const token = data.session?.access_token;
if (!token) {
// Shouldn't really happen at this point
reject('Not authorized');
} else {
fetch('/api/invite-user-to-project', {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
body: JSON.stringify(payload),
})
.then((res) => res.json())
.then(({ data }) => resolve(data as Invitation));
}
}
});
});
return supabase
.from('invites')
.insert(
Expand Down
267 changes: 136 additions & 131 deletions src/pages/api/invite-user-to-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,153 +22,158 @@ export const POST: APIRoute = async ({ request, cookies, url }) => {

const body: ApiPostInviteUserToProject = await request.json();

// Create the invite
const inviteResponse = await supabase
.from('invites')
.insert({
email: body.email.toLowerCase(),
project_id: body.projectId,
project_name: body.projectName,
project_group_id: body.projectGroupId,
invited_by_name: body.invitedBy,
})
.select()
.single();

if (inviteResponse.error) {
return new Response(JSON.stringify({ error: 'Failed to invite user' }), {
status: 500,
});
}
const respData = [];
// Create the invites
for (let i = 0; i < body.users.length; i++) {
const user = body.users[i];
const inviteResponse = await supabase
.from('invites')
.insert({
email: user.email.toLowerCase(),
project_id: body.projectId,
project_name: body.projectName,
project_group_id: user.projectGroupId,
invited_by_name: body.invitedBy,
})
.select()
.single();

if (inviteResponse.error) {
return new Response(JSON.stringify({ error: 'Failed to invite user' }), {
status: 500,
});
}

const i18n = getTranslations(request, 'email');
const { t, lang } = i18n;

const config: TReaderDocument = {
root: {
type: 'EmailLayout',
data: {
backdropColor: '#b5c8dc',
borderRadius: 8,
canvasColor: '#FFFFFF',
textColor: '#242424',
fontFamily: 'MODERN_SANS',
childrenIds: [
'block-1709571212684',
'block-1709571228545',
'block-1709571234315',
'block-1709571302968',
],
respData.push(inviteResponse.data);

const i18n = getTranslations(request, 'email');
const { t, lang } = i18n;

const config: TReaderDocument = {
root: {
type: 'EmailLayout',
data: {
backdropColor: '#b5c8dc',
borderRadius: 8,
canvasColor: '#FFFFFF',
textColor: '#242424',
fontFamily: 'MODERN_SANS',
childrenIds: [
'block-1709571212684',
'block-1709571228545',
'block-1709571234315',
'block-1709571302968',
],
},
},
},
'block-1709571212684': {
type: 'Image',
data: {
style: {
padding: {
top: 24,
bottom: 24,
right: 24,
left: 24,
'block-1709571212684': {
type: 'Image',
data: {
style: {
padding: {
top: 24,
bottom: 24,
right: 24,
left: 24,
},
},
props: {
url: `${url.protocol}//${url.host}/img/branding/email/top-logo.svg`,
alt: 'RecogitoStudio',
linkHref: url.host,
contentAlignment: 'middle',
},
},
props: {
url: `${url.protocol}//${url.host}/img/branding/email/top-logo.svg`,
alt: 'RecogitoStudio',
linkHref: url.host,
contentAlignment: 'middle',
},
},
},
'block-1709571228545': {
type: 'Text',
data: {
style: {
fontWeight: 'normal',
padding: {
top: 0,
bottom: 16,
right: 24,
left: 24,
'block-1709571228545': {
type: 'Text',
data: {
style: {
fontWeight: 'normal',
padding: {
top: 0,
bottom: 16,
right: 24,
left: 24,
},
},
props: {
text: t['Hello Recogito Studio User'],
},
},
props: {
text: t['Hello Recogito Studio User'],
},
},
},
'block-1709571234315': {
type: 'Text',
data: {
style: {
fontWeight: 'normal',
padding: {
top: 0,
bottom: 16,
right: 24,
left: 24,
'block-1709571234315': {
type: 'Text',
data: {
style: {
fontWeight: 'normal',
padding: {
top: 0,
bottom: 16,
right: 24,
left: 24,
},
},
props: {
text: t['_welcome_message_']
.replace('${sender}', body.invitedBy)
.replace('${project_name}', body.projectName)
.replace('${host}', url.host),
},
},
props: {
text: t['_welcome_message_']
.replace('${sender}', body.invitedBy)
.replace('${project_name}', body.projectName)
.replace('${host}', url.host),
},
},
},
'block-1709571302968': {
type: 'Button',
data: {
style: {
fontSize: 14,
padding: {
top: 16,
bottom: 24,
right: 24,
left: 24,
'block-1709571302968': {
type: 'Button',
data: {
style: {
fontSize: 14,
padding: {
top: 16,
bottom: 24,
right: 24,
left: 24,
},
},
props: {
buttonBackgroundColor: '#07529a',
buttonStyle: 'rectangle',
text: 'Open dashboard',
url: `${url.protocol}//${url.host}/${lang}/projects/${body.projectId}/accept-invite`,
},
},
props: {
buttonBackgroundColor: '#07529a',
buttonStyle: 'rectangle',
text: 'Open dashboard',
url: `${url.protocol}//${url.host}/${lang}/projects/${body.projectId}/accept-invite`,
},
},
},
};

console.log(
`${url.host}/${lang}/projects/${body.projectId}?accept-invite=true`
);

const html = renderToStaticMarkup(config, { rootBlockId: 'root' });

const transporter = nodemailer.createTransport({
// @ts-ignore
host: import.meta.env.MAIL_HOST,
port: import.meta.env.MAIL_PORT,
tls: true,
auth: {
user: import.meta.env.MAIL_USERNAME,
pass: import.meta.env.MAIL_PASSWORD,
},
});
};

const mailOptions = {
from: import.meta.env.MAIL_FROM_ADDRESS,
to: body.email.toLowerCase(),
subject: t['_you_have_been_invited_'],
html: html,
};
console.log(
`${url.host}/${lang}/projects/${body.projectId}?accept-invite=true`
);

const sendResp = await transporter.sendMail(mailOptions);
const html = renderToStaticMarkup(config, { rootBlockId: 'root' });

console.log('Message sent: ', sendResp.messageId);
const transporter = nodemailer.createTransport({
// @ts-ignore
host: import.meta.env.MAIL_HOST,
port: import.meta.env.MAIL_PORT,
tls: true,
auth: {
user: import.meta.env.MAIL_USERNAME,
pass: import.meta.env.MAIL_PASSWORD,
},
});

return new Response(
JSON.stringify({ error: null, data: inviteResponse.data }),
{ status: 200 }
);
const mailOptions = {
from: import.meta.env.MAIL_FROM_ADDRESS,
to: user.email.toLowerCase(),
subject: t['_you_have_been_invited_'],
html: html,
};

const sendResp = await transporter.sendMail(mailOptions);

console.log('Message sent: ', sendResp.messageId);
}

return new Response(JSON.stringify({ error: null, data: respData }), {
status: 200,
});
};

0 comments on commit 8e39490

Please sign in to comment.