-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
TEC email reminders feature #536
Changes from 2 commits
d03d25a
418fc93
98a957e
8a51092
f663cc9
958db63
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import axios from 'axios'; | ||
import axios, { AxiosResponse } from 'axios'; | ||
import { Request } from 'express'; | ||
import getEmailTransporter from '../nodemailer'; | ||
import { isProd } from '../api'; | ||
|
@@ -50,9 +50,31 @@ const emailAdmins = async (req: Request, subject: string, text: string) => { | |
}); | ||
}; | ||
|
||
const emailMember = async (req: Request, member: IdolMember, subject: string, text: string) => { | ||
const url = getSendMailURL(req); | ||
const idToken = req.headers['auth-token'] as string; | ||
const requestBody = { | ||
subject, | ||
text | ||
}; | ||
|
||
return axios.post( | ||
url, | ||
{ ...requestBody, to: member.email }, | ||
{ headers: { 'auth-token': idToken } } | ||
); | ||
}; | ||
|
||
export const sendMemberUpdateNotifications = async (req: Request): Promise<Promise<void>[]> => { | ||
const subject = 'IDOL Member Profile Change'; | ||
const text = | ||
'Hey! A DTI member has updated their profile on IDOL. Please visit https://idol.cornelldti.org/admin/member-review to review the changes.'; | ||
return emailAdmins(req, subject, text); | ||
}; | ||
|
||
export const sendTECReminder = async (req: Request, member: IdolMember): Promise<AxiosResponse> => { | ||
const subject = 'TEC Reminder'; | ||
const text = | ||
'Hey! You currently do not have enough team events credits this semester. This is a reminder to get at least 3 team events credits by the end of the semester.'; | ||
return emailMember(req, member, subject, text); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we make the credit amount custom based on the role type? Leads need 6 credits, all other members need only 3. |
||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ import PermissionsManager from '../utils/permissionsManager'; | |
import { BadRequestError, PermissionError } from '../utils/errors'; | ||
import { bucket } from '../firebase'; | ||
import { getNetIDFromEmail, computeMembersDiff } from '../utils/memberUtil'; | ||
import { sendTECReminder } from './mailAPI'; | ||
|
||
const membersDao = new MembersDao(); | ||
|
||
|
@@ -74,6 +75,25 @@ export const deleteMember = async (email: string, user: IdolMember): Promise<voi | |
); | ||
}; | ||
|
||
export const notifyMember = async ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems to be TeamEvent specific. Consider putting this in |
||
req: Request, | ||
body: IdolMember, | ||
user: IdolMember | ||
): Promise<unknown> => { | ||
const canNotify = await PermissionsManager.canNotifyMembers(user); | ||
if (!canNotify) { | ||
throw new PermissionError( | ||
`User with email: ${user.email} does not have permission to notify members!` | ||
); | ||
} | ||
if (!body.email || body.email === '') { | ||
throw new BadRequestError("Couldn't notify member with undefined email!"); | ||
} | ||
|
||
sendTECReminder(req, body); | ||
return body; | ||
}; | ||
|
||
export const deleteImage = async (email: string): Promise<void> => { | ||
// Create a reference to the file to delete | ||
const netId: string = getNetIDFromEmail(email); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ import { | |
setMember, | ||
deleteMember, | ||
updateMember, | ||
notifyMember, | ||
getUserInformationDifference, | ||
reviewUserInformationChange | ||
} from './API/memberAPI'; | ||
|
@@ -148,7 +149,7 @@ const loginCheckedHandler = | |
return; | ||
} | ||
if (env === 'staging' && !(await PermissionsManager.isAdmin(user))) { | ||
res.status(401).json({ error: 'Only admins users have permismsions to the staging API!' }); | ||
res.status(401).json({ error: 'Only admins users have permissions to the staging API!' }); | ||
} | ||
try { | ||
res.status(200).send(await handler(req, user)); | ||
|
@@ -219,6 +220,9 @@ loginCheckedDelete('/member/:email', async (req, user) => { | |
loginCheckedPut('/member', async (req, user) => ({ | ||
member: await updateMember(req, req.body, user) | ||
})); | ||
loginCheckedPost('/notifyMember', async (req, user) => ({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
member: await notifyMember(req, req.body, user) | ||
})); | ||
|
||
loginCheckedGet('/memberDiffs', async (_, user) => ({ | ||
diffs: await getUserInformationDifference(user) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,10 @@ export default class PermissionsManager { | |
return this.isLeadOrAdmin(mem); | ||
} | ||
|
||
static async canNotifyMembers(mem: IdolMember): Promise<boolean> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
return this.isLeadOrAdmin(mem); | ||
} | ||
|
||
static async canDeploySite(mem: IdolMember): Promise<boolean> { | ||
return this.isLeadOrAdmin(mem); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
.buttonsWrapper { | ||
display: flex; | ||
justify-content: flex-end; | ||
padding-top: 15px; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import React, { useState } from 'react'; | ||
import { Modal, Form } from 'semantic-ui-react'; | ||
import styles from './NotifyMemberModal.module.css'; | ||
import { Member, MembersAPI } from '../../API/MembersAPI'; | ||
import { Emitters } from '../../utils'; | ||
|
||
const NotifyMemberModal = (props: { | ||
all: boolean; | ||
member?: Member; | ||
members?: Member[]; | ||
trigger: JSX.Element; | ||
}): JSX.Element => { | ||
const { member, members, all, trigger } = props; | ||
const [open, setOpen] = useState(false); | ||
const subject = !all && member ? `${member.firstName} ${member.lastName}` : 'everyone'; | ||
|
||
const handleSubmit = () => { | ||
if (!all && member) { | ||
MembersAPI.notifyMember(member).then((val) => { | ||
Emitters.generalSuccess.emit({ | ||
headerMsg: 'Reminder sent!', | ||
contentMsg: `An email reminder was successfully sent to ${member.firstName} ${member.lastName}!` | ||
}); | ||
}); | ||
} | ||
|
||
if (all && members) { | ||
members.map((member) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could just do a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. make sure to also await the |
||
MembersAPI.notifyMember(member); | ||
}); | ||
Emitters.generalSuccess.emit({ | ||
headerMsg: 'Reminder sent!', | ||
contentMsg: `An email reminder was successfully sent to everyone!` | ||
}); | ||
} | ||
|
||
setOpen(false); | ||
}; | ||
|
||
return ( | ||
<Modal | ||
onClose={() => setOpen(false)} | ||
onOpen={() => setOpen(true)} | ||
open={open} | ||
trigger={trigger} | ||
> | ||
<Modal.Header> Are you sure you want to notify {subject}?</Modal.Header> | ||
<Modal.Content> | ||
This will send an email to {subject} reminding them that they do not have enough TEC Credits | ||
completed yet this semester. | ||
<Form> | ||
<div className={styles.buttonsWrapper}> | ||
<Form.Button onClick={() => setOpen(false)}>Cancel</Form.Button> | ||
<Form.Button | ||
content="Yes" | ||
labelPosition="right" | ||
icon="checkmark" | ||
onClick={() => { | ||
handleSubmit(); | ||
}} | ||
positive | ||
/> | ||
</div> | ||
</Form> | ||
</Modal.Content> | ||
</Modal> | ||
); | ||
}; | ||
export default NotifyMemberModal; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since you are creating variables with promises, I'd await them first instead of inlining the
await
, otherwise you might as well just inline the value and delete the variable entirely.So instead of
try
Same with
memberEvents
, although I'd personally rename this tomemberEventAttendance