add a confirm-email landing page

This commit is contained in:
Riley Davis
2017-01-27 15:04:42 -07:00
parent 0b73f18eb3
commit 5e521b832c
9 changed files with 224 additions and 98 deletions
@@ -32,6 +32,10 @@ const updateModeration = (updateSettings, mod) => () => {
updateSettings({moderation});
};
const updateEmailConfirmation = (updateSettings, verify) => () => {
updateSettings({requireEmailConfirmation: !verify});
};
const updateInfoBoxEnable = (updateSettings, infoBox) => () => {
const infoBoxEnable = !infoBox;
updateSettings({infoBoxEnable});
@@ -67,106 +71,123 @@ const CommentSettings = ({fetchingSettings, title, updateSettings, settingsError
return <Card shadow="4"><Spinner/>Loading settings...</Card>;
}
// just putting this here for shorthand below
const on = styles.enabledSetting;
const off = styles.disabledSetting;
return (
<div className={styles.commentSettingsSection}>
<h3>{title}</h3>
<Card className={`${styles.configSetting} ${settings.moderation === 'PRE' ? styles.enabledSetting : styles.disabledSetting}`}>
<div className={styles.action}>
<Checkbox
onChange={updateModeration(updateSettings, settings.moderation)}
checked={settings.moderation === 'PRE'} />
</div>
<div className={styles.content}>
<Card className={`${styles.configSetting} ${settings.moderation === 'PRE' ? on : off}`}>
<div className={styles.action}>
<Checkbox
onChange={updateModeration(updateSettings, settings.moderation)}
checked={settings.moderation === 'PRE'} />
</div>
<div className={styles.content}>
<div className={styles.settingsHeader}>{lang.t('configure.enable-pre-moderation')}</div>
<p className={settings.moderation === 'PRE' ? '' : styles.disabledSettingText}>
{lang.t('configure.enable-pre-moderation-text')}
</p>
</div>
</Card>
<Card className={`${styles.configSetting} ${settings.charCountEnable ? styles.enabledSetting : styles.disabledSetting}`}>
<div className={styles.action}>
<Checkbox
onChange={updateCharCountEnable(updateSettings, settings.charCountEnable)}
checked={settings.charCountEnable} />
</div>
<div className={styles.content}>
<div className={styles.settingsHeader}>{lang.t('configure.comment-count-header')}</div>
<p className={settings.charCountEnable ? '' : styles.disabledSettingText}>
<span>{lang.t('configure.comment-count-text-pre')}</span>
<input type='text'
className={`${styles.charCountTexfield} ${settings.charCountEnable && styles.charCountTexfieldEnabled}`}
htmlFor='charCount'
onChange={updateCharCount(updateSettings, settingsError)}
value={settings.charCount}/>
<span>{lang.t('configure.comment-count-text-post')}</span>
{
errors.charCount &&
<span className={styles.settingsError}>
<br/>
<Icon name="error_outline"/>
{lang.t('configure.comment-count-error')}
</span>
}
</p>
</div>
</Card>
<Card className={`${styles.configSettingInfoBox} ${settings.infoBoxEnable ? styles.enabledSetting : styles.disabledSetting}`}>
<div className={styles.action}>
<Checkbox
onChange={updateInfoBoxEnable(updateSettings, settings.infoBoxEnable)}
checked={settings.infoBoxEnable} />
</div>
<div className={styles.content}>
{lang.t('configure.include-comment-stream')}
<p>
{lang.t('configure.include-comment-stream-desc')}
</p>
<div className={`${styles.configSettingInfoBox} ${settings.infoBoxEnable ? null : styles.hidden}`} >
<div className={styles.content}>
<Textfield
onChange={updateInfoBoxContent(updateSettings)}
value={settings.infoBoxContent}
label={lang.t('configure.include-text')}
rows={3}/>
</div>
</Card>
<Card className={`${styles.configSetting} ${settings.requireEmailConfirmation ? on : off}`}>
<div className={styles.action}>
<Checkbox
onChange={updateEmailConfirmation(updateSettings, settings.requireEmailConfirmation)}
checked={settings.requireEmailConfirmation} />
</div>
<div className={styles.content}>
<div className={styles.settingsHeader}>{lang.t('configure.require-email-verification')}</div>
<p className={settings.requireEmailConfirmation ? '' : styles.disabledSettingText}>
{lang.t('configure.require-email-verification-text')}
</p>
</div>
</Card>
<Card className={`${styles.configSetting} ${settings.charCountEnable ? on : off}`}>
<div className={styles.action}>
<Checkbox
onChange={updateCharCountEnable(updateSettings, settings.charCountEnable)}
checked={settings.charCountEnable} />
</div>
<div className={styles.content}>
<div className={styles.settingsHeader}>{lang.t('configure.comment-count-header')}</div>
<p className={settings.charCountEnable ? '' : styles.disabledSettingText}>
<span>{lang.t('configure.comment-count-text-pre')}</span>
<input type='text'
className={`${styles.charCountTexfield} ${settings.charCountEnable && styles.charCountTexfieldEnabled}`}
htmlFor='charCount'
onChange={updateCharCount(updateSettings, settingsError)}
value={settings.charCount}/>
<span>{lang.t('configure.comment-count-text-post')}</span>
{
errors.charCount &&
<span className={styles.settingsError}>
<br/>
<Icon name="error_outline"/>
{lang.t('configure.comment-count-error')}
</span>
}
</p>
</div>
</Card>
<Card className={`${styles.configSettingInfoBox} ${settings.infoBoxEnable ? on : off}`}>
<div className={styles.action}>
<Checkbox
onChange={updateInfoBoxEnable(updateSettings, settings.infoBoxEnable)}
checked={settings.infoBoxEnable} />
</div>
<div className={styles.content}>
{lang.t('configure.include-comment-stream')}
<p>
{lang.t('configure.include-comment-stream-desc')}
</p>
<div className={`${styles.configSettingInfoBox} ${settings.infoBoxEnable ? null : styles.hidden}`} >
<div className={styles.content}>
<Textfield
onChange={updateInfoBoxContent(updateSettings)}
value={settings.infoBoxContent}
label={lang.t('configure.include-text')}
rows={3}/>
</div>
</div>
</Card>
<Card className={styles.configSettingInfoBox}>
<div className={styles.content}>
{lang.t('configure.closed-comments-desc')}
<div>
<Textfield
onChange={updateClosedMessage(updateSettings)}
value={settings.closedMessage}
label={lang.t('configure.closed-comments-label')}
rows={3}/>
</div>
</div>
</Card>
<Card className={styles.configSettingInfoBox}>
<div className={styles.content}>
{lang.t('configure.closed-comments-desc')}
<div>
<Textfield
onChange={updateClosedMessage(updateSettings)}
value={settings.closedMessage}
label={lang.t('configure.closed-comments-label')}
rows={3}/>
</div>
</Card>
<Card className={`${styles.configSettingInfoBox}`}>
<div className={styles.content}>
{lang.t('configure.close-after')}
<br />
<Textfield
type='number'
pattern='[0-9]+'
style={{width: 50}}
onChange={updateClosedTimeout(updateSettings, settings.closedTimeout)}
value={getTimeoutAmount(settings.closedTimeout)}
label={lang.t('configure.closed-comments-label')} />
<div className={styles.configTimeoutSelect}>
<SelectField
label="comments closed time window"
value={getTimeoutMeasure(settings.closedTimeout)}
onChange={updateClosedTimeout(updateSettings, settings.closedTimeout, true)}>
<Option value={'hours'}>{lang.t('configure.hours')}</Option>
<Option value={'days'}>{lang.t('configure.days')}</Option>
<Option value={'weeks'}>{lang.t('configure.weeks')}</Option>
</SelectField>
</div>
</div>
</Card>
<Card className={`${styles.configSettingInfoBox}`}>
<div className={styles.content}>
{lang.t('configure.close-after')}
<br />
<Textfield
type='number'
pattern='[0-9]+'
style={{width: 50}}
onChange={updateClosedTimeout(updateSettings, settings.closedTimeout)}
value={getTimeoutAmount(settings.closedTimeout)}
label={lang.t('configure.closed-comments-label')} />
<div className={styles.configTimeoutSelect}>
<SelectField
label="comments closed time window"
value={getTimeoutMeasure(settings.closedTimeout)}
onChange={updateClosedTimeout(updateSettings, settings.closedTimeout, true)}>
<Option value={'hours'}>{lang.t('configure.hours')}</Option>
<Option value={'days'}>{lang.t('configure.days')}</Option>
<Option value={'weeks'}>{lang.t('configure.weeks')}</Option>
</SelectField>
</div>
</Card>
</div>
</Card>
</div>
);
};
+4
View File
@@ -43,6 +43,8 @@
"configure": {
"enable-pre-moderation": "Enable pre-moderation",
"enable-pre-moderation-text": "Moderators must approve any comment before it is published.",
"require-email-verification": "Require Email Confirmation",
"require-email-verification-text": "New Users must verify their email before commenting",
"include-comment-stream": "Include Comment Stream Description for Readers.",
"include-comment-stream-desc": "Write a message to be added to the top of your comment stream. Pose a topic, include community guidelines, etc.",
"include-text": "Include your text here.",
@@ -129,6 +131,8 @@
"configure": {
"enable-pre-moderation": "Habilitar pre-moderación",
"enable-pre-moderation-text": "Los moderadores deben aprobar cada comentario antes de que sea publicado.",
"require-email-verification": "Necesita confirmación de correo",
"require-email-verification-text": "Nuevos usuarios deben verificar sus correos antes de comentar",
"include-comment-stream": "Incluir la Descripción a un Hilo de Comentario para los y las Lectoras.",
"include-comment-stream-desc": "Escribir un mensaje que será agregado a la parte de arriba del tu hilo de comentarios. Por ejemplo, un tema, guias de comunidad, etc.",
"include-text": "Incluir tu texto aqui.",
+5
View File
@@ -1,6 +1,11 @@
const express = require('express');
const router = express.Router();
// Get /email-confirmation expects a signed JWT in the hash
router.get('/confirm-email', (req, res) => {
res.render('admin/confirm-email');
});
// Get /password-reset expects a signed token (JWT) in the hash.
// Links to this endpoint are generated by /views/password-reset-email.ejs.
router.get('/password-reset', (req, res) => {
+2 -2
View File
@@ -28,8 +28,8 @@ router.post('/email/confirm', (req, res, next) => {
UsersService
.verifyEmailConfirmation(token)
.then(() => {
res.status(204).end();
.then(([, referer]) => {
res.json({redirectUri: referer});
})
.catch((err) => {
next(err);
+3 -3
View File
@@ -69,8 +69,8 @@ router.post('/:user_id/status', authorization.needed('ADMIN'), (req, res, next)
* @param {String} userID the id for the user to send the email to
* @param {String} email the email for the user to send the email to
*/
const SendEmailConfirmation = (app, userID, email) => UsersService
.createEmailConfirmToken(userID, email)
const SendEmailConfirmation = (app, userID, email, referer) => UsersService
.createEmailConfirmToken(userID, email, referer)
.then((token) => {
return mailer.sendSimple({
app, // needed to render the templates.
@@ -103,7 +103,7 @@ router.post('/', (req, res, next) => {
if (requireEmailConfirmation) {
SendEmailConfirmation(req.app, user.id, email)
SendEmailConfirmation(req.app, user.id, email, req.header('Referer'))
.then(() => {
// Then send back the user.
+7 -3
View File
@@ -531,7 +531,7 @@ module.exports = class UsersService {
* @param {String} email The email that we are needing to get confirmed.
* @return {Promise}
*/
static createEmailConfirmToken(userID, email) {
static createEmailConfirmToken(userID, email, referer) {
if (!email || typeof email !== 'string') {
return Promise.reject('email is required when creating a JWT for resetting passord');
}
@@ -555,6 +555,7 @@ module.exports = class UsersService {
const payload = {
email,
referer,
userID
};
@@ -579,9 +580,9 @@ module.exports = class UsersService {
.verifyToken(token, {
subject: EMAIL_CONFIRM_JWT_SUBJECT
})
.then(({userID, email}) => {
.then(({userID, email, referer}) => {
return UserModel
const userUpdate = UserModel
.update({
id: userID,
profiles: {
@@ -595,7 +596,10 @@ module.exports = class UsersService {
'profiles.$.metadata.confirmed_at': new Date()
}
});
return Promise.all([userUpdate, referer]);
});
}
};
+92
View File
@@ -0,0 +1,92 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1">
<title>Confirm Email</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://code.getmdl.io/1.2.1/material.indigo-pink.min.css">
<style media="screen">
#root {
max-width: 400px;
padding-top: 100px;
margin: 0 auto;
background: #fff;
}
.coral-card-wide > .mdl-card__title {
color: #fff;
height: 176px;
background: #F47E6B url('/path/to/logo.jpg') center / cover;
}
.coral-card-wide > .mdl-card__menu {
color: #fff;
}
.error-console {
display: none;
margin-top: 10px;
border-radius: 4px;
background-color: pink;
color: red;
border: 1px solid red;
padding: 10px;
}
.error-console.active {
display: block;
}
</style>
</head>
<body>
<div id="root">
<div class="coral-card-wide mdl-card mdl-shadow--2dp">
<div class="mdl-card__title">
<h2 class="mdl-card__title-text">Confirm Email Address</h2>
</div>
<div class="mdl-card__supporting-text">
Click the button below to confirm your new user account.
</div>
<div class="mdl-card__actions mdl-card--border">
<a class="mdl-button mdl-button--colored mdl-js-button mdl-js-ripple-effect" id="confirm-email">
Confirm
</a>
<div style="display: none" id="p2" class="mdl-progress mdl-js-progress mdl-progress__indeterminate"></div>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script defer src="https://code.getmdl.io/1.3.0/material.min.js"></script>
<script>
$(function () {
function showError(message) {
$('.error-console').text(message).addClass('active');
}
function handleClick (e) {
e.preventDefault();
$('#p2').css('display', 'block');
$('.error-console').removeClass('active');
$.ajax({
url: '/api/v1/account/email/confirm',
contentType: 'application/json',
method: 'POST',
headers: {
'X-CSRF-Token': '<%= csrfToken %>'
},
data: JSON.stringify({token: location.hash.replace('#', '')})
}).then(function (success) {
location.href = success.redirectUri;
}).catch(function (error) {
showError(error.responseText);
});
}
$('#confirm-email').on('click', handleClick);
});
</script>
</body>
</html>
+1 -1
View File
@@ -1,3 +1,3 @@
<p>A email confirmation has been requested for the following account: <b><%= email %></b>.</p>
<p>To confirm the account, please visit the following link: <a href="http://example.com/email/confirm/endpoint#<%= token %>">http://example.com/email/confirm/endpoint#<%= token %></a></p>
<p>To confirm the account, please visit the following link: <a href="<%= rootURL %>/admin/confirm-email#<%= token %>">http://example.com/email/confirm/endpoint#<%= token %></a></p>
<p>If you did not request this, you can safely ignore this email.</p>
+1 -1
View File
@@ -4,6 +4,6 @@ A email confirmation has been requested for the following account:
To confirm the account, please visit the following link:
http://example.com/email/confirm/endpoint#<%= token %>
<%= rootURL %>/confirm/endpoint#<%= token %>
If you did not request this, you can safely ignore this email.