Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions app/components/web-components/candidate-lookup/formInput.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react'

export const FormInput = props => {
const { labelName, handleChange, handleBlur, name, type, value, width, placeHolder } = props
return (
<label style={{ color: '#18397D', textAlign: 'left', fontWeight: '900', width }}>
{labelName}
<input
name={name}
style={{
boxShadow: '0px 4px 4px rgba(0, 0, 0, 0.25)',
border: 'none',
fontSize: '1.4rem',
height: '2rem',
padding: '0.5rem',
}}
onChange={e => handleChange(e)}
onBlur={() => (handleBlur ? handleBlur() : null)}
type={type}
required
value={value}
placeholder={placeHolder}
/>
</label>
)
}

FormInput.defaultProps = {
type: 'text',
}
122 changes: 122 additions & 0 deletions app/components/web-components/candidate-lookup/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import React, { useState, useEffect } from 'react'
import injectSheet from 'react-jss'
import superagent from 'superagent'
import { LookupForm } from './lookupForm'

const Preinject = props => {
const [isDiabled, setIsDisabled] = useState(true)
const [formValues, setFormValues] = useState({
email: '',
name: '',
message: '',
})
const [infoMessage, setInfoMessage] = useState(null)
const { classes } = props
const handleChange = e => setFormValues({ ...formValues, [e.target.name]: e.target.value })
const handleSendLink = e => {
e.preventDefault()
setInfoMessage('Sending you your recording url')
logger.info('on the browser', formValues)
const { email, name } = formValues
const userInfo = Object.assign({}, props.userInfo, { email, name })
superagent
.post('/send/recorder-link')
.send(userInfo)
.end((err, res) => {
if (err) {
logger.error('Send recorder link error', err)
}
setInfoMessage(null)
switch (res.statusCode) {
case 200:
logger.info('successfully posted')
setFormValues({ ...formValues, message: res.body.message })
break
case 401:
logger.info('invalid email')
setFormValues({ ...formValues, message: res.body.message })
break
case 429:
setFormValues({
...formValues,
message: 'Too many attempts logging in, try again in 24 hrs',
})
break
default:
setFormValues({
...formValues,
message: 'Make sure the information you entered is correct.',
})
break
}
})
}

return (
<div className={props.classes.Container}>
<div className={props.classes.Form}>
<h1>Email Recording Link</h1>
<form onSubmit={handleSendLink} className={props.classes.LookupForm}>
<LookupForm
classes={classes}
formValues={formValues}
handleChange={handleChange}
handleSendLink={handleSendLink}
/>
</form>
</div>
</div>
)
}
const styles = {
Container: {
display: 'flex',
justifyContent: 'center',
fontFamily: 'Montserrat , sans-serif',
fontSize: '13px',
marginTop: '3rem',
},
Form: {
display: 'flex',
flexDirection: 'column',
marginBottom: '1rem',
},
LookupForm: {
border: '0.5px solid black',
padding: '3rem',
display: 'flex',
flexDirection: 'column',
width: '30rem',
maxWidth: '30rem',
minHeight: '42rem',
fontSize: 'inherit',
'& form': {},
'& label': {
display: 'flex',
flexDirection: 'column',
marginTop: '3rem',
fontSize: 'inherit',
},
},
disable: {
backgroundColor: '#D3D3D3',
float: 'right',
width: '9rem',
height: '3rem',
fontSize: '1rem',
fontWeight: '600',
fontSize: 'inherit',
},
activeBtn: {
backgroundColor: '#E5A650',
cursor: 'pointer',
float: 'right',
width: '9rem',
height: '3rem',
color: 'white',
fontSize: '1rem',
fontWeight: '600',
},
}

export default injectSheet(styles)(Preinject)
5 changes: 5 additions & 0 deletions app/components/web-components/candidate-lookup/lookupBtn.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react'

export const LookupBtn = ({ classes, btnName }) => {
return <button className={classes}>{btnName}</button>
}
35 changes: 35 additions & 0 deletions app/components/web-components/candidate-lookup/lookupForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react'
import { FormInput } from './formInput'
import { LookupBtn } from './lookupBtn'

export const LookupForm = ({ isDisabled, classes, formValues, handleChange, handleSendLink }) => {
const { email, name, message } = formValues
return (
<>
<FormInput
labelName="Name"
name="name"
placeholder="Full Name"
handleChange={handleChange}
value={name}
type="name"
/>
<FormInput
labelName="Email Address"
name="email"
placeholder="email@address.com"
handleChange={handleChange}
value={email}
type="email"
/>
<div style={{ marginTop: '2rem' }}>
<LookupBtn
btnName="Send me Link"
classes={isDisabled ? classes.disable : classes.activeBtn}
handleClick={handleSendLink}
/>
</div>
<h3>{message}</h3>
</>
)
}
1 change: 1 addition & 0 deletions app/components/web-components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const WebComponents = {
HartfordVotes: require('./HartfordVotes'),
FAQ: require('./FAQ'),
About: require('./About'),
SendRecordingLink: require('./candidate-lookup'),
}

import React from 'react'
Expand Down
30 changes: 16 additions & 14 deletions app/components/web-components/undebate.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3119,20 +3119,22 @@ class Undebate extends React.Component {
!bot &&
beginOverlay()}
{ifShowPreamble && (
<CandidatePreamble
subject={subject}
bp_info={bp_info}
agreed={preambleAgreed}
classes={classes}
onClick={() => {
logger.info('Undebate preambleAgreed true')
this.setState({ preambleAgreed: true })
noOverlay && this.beginButton()
}}
candidate_questions={participants.moderator.agenda}
instructionLink={instructionLink}
timeLimits={participants.moderator.timeLimits}
/>
<>
<CandidatePreamble
subject={subject}
bp_info={bp_info}
agreed={preambleAgreed}
classes={classes}
onClick={() => {
logger.info('Undebate preambleAgreed true')
this.setState({ preambleAgreed: true })
noOverlay && this.beginButton()
}}
candidate_questions={participants.moderator.agenda}
instructionLink={instructionLink}
timeLimits={participants.moderator.timeLimits}
/>
</>
)}
{ending()}
{((this.participants.human && (preambleAgreed || opening.noPreamble)) || !this.participants.human) &&
Expand Down
108 changes: 108 additions & 0 deletions app/server/routes/send-recorder-link-route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
'use strict'
/**
* There are 3 new enviroment variables that need to be created:
* export SENDINBLUE_TEMPLATE_ID_CANDIDATE="1" template that will be sent when once recording link is found
* export SENDINBLUE_TEMPLATE_ID_MANAGER="2" template that will be sent to email manager(David) in case there is more than one link is found
* export SENDINBLUE_TEMPLATE_ID_SEND_BP_INFO="3" template in case we cannot find the email in the database
* export EMAIL_MANAGER="epg323@gmail.com"
*
* for dev we dont send the link to the candidate, we send it to ourselves
*/
import Iota from '../../models/iota'

var SibApiV3Sdk = require('sib-api-v3-sdk')

SibApiV3Sdk.ApiClient.instance.authentications['api-key'].apiKey = process.env.SENDINBLUE_API_KEY
var SibSMTPApi = new SibApiV3Sdk.SMTPApi()

async function sendLinkRoute(req, res, next) {
try {
logger.info('####this is the link route#####', req.body)
let FetchStatus = await FetchLink(req.body)
logger.info('this is the fetch status ', FetchStatus)
if (FetchStatus === 'success') {
res.statusCode = 200
res.json({ message: 'successful, email is being sent' })
} else {
res.statusCode = 401
res.json({ message: 'The email you entered is invalid' })
}
} catch (error) {
next(error)
}
}

async function FetchLink({ name, email }) {
let sendto
if (process.env.NODE_ENV === 'development') {
sendto = process.env.EMAIL_MANAGER
} else {
sendto = email
}

try {
const participant = await Iota.find({
$or: [
{
'bp_info.candidate_emails': {
$elemMatch: {
$eq: email,
},
},
},
{
'bp_info.person_emails': {
$elemMatch: {
$eq: email,
},
},
},
],
})
logger.info('Fetched the following link(s)', participant[0].path)
let uri = Array.isArray(participant[0].path) ? participant[0].path : [participant[0].path]
let viewer_url = `${process.env.HOSTNAME === 'localhost:3011' ? 'http' : 'https'}://${process.env.HOSTNAME}${uri}`
let firstname = name.split(' ')[0]
let sendLink = {
to: [
{
email: sendto,
name: name,
},
],
templateId: parseInt(process.env.SENDINBLUE_TEMPLATE_ID_CANDIDATE),
params: {
recorder_link: viewer_url,
firstname: firstname,
},
}
let ManagerPick = {
to: [
{
email: sendto,
name: name,
},
],
templateId: parseInt(process.env.SENDINBLUE_TEMPLATE_ID_MANAGER),
params: {
recorder_link: viewer_url,
name: name,
},
}
let EmailPicker = uri.length === 1 ? sendLink : ManagerPick
SibSMTPApi.sendTransacEmail(EmailPicker).then(
function(data) {
logger.trace('Sendinblue API called successfully. Returned data: ', data)
},
function(error) {
logger.error('Sendingblue API caught error:', error)
}
)
return 'success'
} catch (err) {
logger.error('could not find the email in the database', err)
return 'fail'
}
}

export default sendLinkRoute
17 changes: 13 additions & 4 deletions app/server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import tempIdRoute from './routes/temp-id'
import signOutRoute from './routes/sign-out'
import setUserCookie from './routes/set-user-cookie'
import serverReactRender from './routes/server-react-render'
import sendLinkRoute from './routes/send-recorder-link-route'

import User from '../models/user'
import Iota from '../models/iota'
Expand Down Expand Up @@ -124,21 +125,29 @@ class HttpServer extends EventEmitter {
})
}

const apiLimiter = expressRateLimit({
const loginLimiter = expressRateLimit({
windowMs: 60 * 1000,
max: 2,
message: 'Too many attempts logging in, please try again after 24 hours.',
max: 5,
message: 'Too many attempts logging in',
})

const linkRequestLimiter = expressRateLimit({
windowMs: 60 * 1000,
max: 5,
message: 'Too many requests',
})
//will need trust proxy for production
this.app.set('trust proxy', 'loopback')

this.app.post('/sign/in', apiLimiter, signInRoute, this.setUserCookie, sendUserId)
this.app.post('/sign/in', loginLimiter, signInRoute, this.setUserCookie, sendUserId)

this.app.all('/sign/up', signUpRoute, this.setUserCookie, sendUserId)

this.app.all('/tempid', tempIdRoute, this.setUserCookie, sendUserId)

this.app.all('/sign/out', signOutRoute)

this.app.post('/send/recorder-link', linkRequestLimiter, sendLinkRoute)
}

router() {
Expand Down
Loading