feat(email-settings): Add email settings
This commit is contained in:
@ -9,7 +9,6 @@ import { wrapHandledError } from '../../server/utils/create-handled-error.js';
|
||||
// const log = debug('fcc:models:userIdent');
|
||||
|
||||
export default function(UserIdent) {
|
||||
|
||||
UserIdent.on('dataSourceAttached', () => {
|
||||
UserIdent.findOne$ = observeMethod(UserIdent, 'findOne');
|
||||
});
|
||||
@ -33,7 +32,7 @@ export default function(UserIdent) {
|
||||
// get the social provider data and the external id from auth0
|
||||
profile.id = profile.id || profile.openid;
|
||||
const auth0IdString = '' + profile.id;
|
||||
const [ provider, socialExtId ] = auth0IdString.split('|');
|
||||
const [provider, socialExtId] = auth0IdString.split('|');
|
||||
const query = {
|
||||
where: {
|
||||
provider: provider,
|
||||
@ -42,8 +41,10 @@ export default function(UserIdent) {
|
||||
include: 'user'
|
||||
};
|
||||
// get the email from the auth0 (its expected from social providers)
|
||||
const email = (profile && profile.emails && profile.emails[0]) ?
|
||||
profile.emails[0].value : '';
|
||||
const email =
|
||||
profile && profile.emails && profile.emails[0]
|
||||
? profile.emails[0].value
|
||||
: '';
|
||||
if (!isEmail('' + email)) {
|
||||
throw wrapHandledError(
|
||||
new Error('invalid or empty email recieved from auth0'),
|
||||
@ -60,12 +61,11 @@ export default function(UserIdent) {
|
||||
}
|
||||
|
||||
if (provider === 'email') {
|
||||
|
||||
return User.findOne$({ where: { email } })
|
||||
.flatMap(user => {
|
||||
return user ?
|
||||
Observable.of(user) :
|
||||
User.create$({ email }).toPromise();
|
||||
return user
|
||||
? Observable.of(user)
|
||||
: User.create$({ email }).toPromise();
|
||||
})
|
||||
.flatMap(user => {
|
||||
if (!user) {
|
||||
@ -81,56 +81,51 @@ export default function(UserIdent) {
|
||||
}
|
||||
);
|
||||
}
|
||||
const createToken = observeQuery(
|
||||
AccessToken,
|
||||
'create',
|
||||
{
|
||||
const createToken = observeQuery(AccessToken, 'create', {
|
||||
userId: user.id,
|
||||
created: new Date(),
|
||||
ttl: user.constructor.settings.ttl
|
||||
}
|
||||
);
|
||||
const updateUser = user.update$({
|
||||
});
|
||||
const updateUserPromise = new Promise((resolve, reject) =>
|
||||
user.updateAttributes(
|
||||
{
|
||||
emailVerified: true,
|
||||
emailAuthLinkTTL: null,
|
||||
emailVerifyTTL: null
|
||||
});
|
||||
},
|
||||
err => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
return resolve();
|
||||
}
|
||||
)
|
||||
);
|
||||
return Observable.combineLatest(
|
||||
Observable.of(user),
|
||||
createToken,
|
||||
updateUser,
|
||||
(user, token) => ({user, token})
|
||||
Observable.fromPromise(updateUserPromise),
|
||||
(user, token) => ({ user, token })
|
||||
);
|
||||
})
|
||||
.subscribe(
|
||||
({ user, token }) => cb(null, user, null, token),
|
||||
cb
|
||||
);
|
||||
|
||||
.subscribe(({ user, token }) => cb(null, user, null, token), cb);
|
||||
} else {
|
||||
|
||||
return UserIdent.findOne$(query)
|
||||
.flatMap(identity => {
|
||||
return identity ?
|
||||
Observable.of(identity.user()) :
|
||||
User.findOne$({ where: { email } })
|
||||
.flatMap(user => {
|
||||
return user ?
|
||||
Observable.of(user) :
|
||||
User.create$({ email }).toPromise();
|
||||
return identity
|
||||
? Observable.of(identity.user())
|
||||
: User.findOne$({ where: { email } }).flatMap(user => {
|
||||
return user
|
||||
? Observable.of(user)
|
||||
: User.create$({ email }).toPromise();
|
||||
});
|
||||
})
|
||||
.flatMap(user => {
|
||||
|
||||
const createToken = observeQuery(
|
||||
AccessToken,
|
||||
'create',
|
||||
{
|
||||
const createToken = observeQuery(AccessToken, 'create', {
|
||||
userId: user.id,
|
||||
created: new Date(),
|
||||
ttl: user.constructor.settings.ttl
|
||||
}
|
||||
);
|
||||
});
|
||||
const updateUser = user.update$({
|
||||
email: email,
|
||||
emailVerified: true,
|
||||
@ -144,11 +139,7 @@ export default function(UserIdent) {
|
||||
(user, token) => ({ user, token })
|
||||
);
|
||||
})
|
||||
.subscribe(
|
||||
({ user, token }) => cb(null, user, null, token),
|
||||
cb
|
||||
);
|
||||
|
||||
.subscribe(({ user, token }) => cb(null, user, null, token), cb);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -614,10 +614,8 @@ module.exports = function(User) {
|
||||
this.update$({ emailAuthLinkTTL })
|
||||
);
|
||||
})
|
||||
.map(() =>
|
||||
dedent`
|
||||
Check your email and click the link we sent you to confirm you email.
|
||||
`
|
||||
.map(() => 'Check your email and click the link we sent you to confirm' +
|
||||
' your new email address.'
|
||||
);
|
||||
}
|
||||
|
||||
@ -691,12 +689,18 @@ module.exports = function(User) {
|
||||
}
|
||||
})
|
||||
.flatMap(()=>{
|
||||
const updatePromise = new Promise((resolve, reject) =>
|
||||
this.updateAttributes(updateConfig, err => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
return resolve();
|
||||
}));
|
||||
return Observable.forkJoin(
|
||||
this.update$(updateConfig),
|
||||
Observable.fromPromise(updatePromise),
|
||||
this.requestAuthEmail(false, newEmail),
|
||||
(_, message) => message
|
||||
)
|
||||
.doOnNext(() => this.manualReload());
|
||||
);
|
||||
});
|
||||
|
||||
} else {
|
||||
|
@ -2,14 +2,12 @@ import dedent from 'dedent';
|
||||
|
||||
const ALLOWED_METHODS = ['GET'];
|
||||
const EXCLUDED_PATHS = [
|
||||
'/api/flyers/findOne',
|
||||
'/signout',
|
||||
'/accept-privacy-terms',
|
||||
'/update-email',
|
||||
'/confirm-email',
|
||||
'/passwordless-change',
|
||||
'/external/services/user'
|
||||
];
|
||||
'/passwordless-change'
|
||||
].reduce((list, item) => [...list, item, `/internal${item}`], []);
|
||||
|
||||
export default function emailNotVerifiedNotice() {
|
||||
return function(req, res, next) {
|
||||
|
@ -12,20 +12,25 @@ import { submitNewAbout, updateUserFlag } from '../redux/settings';
|
||||
import Layout from '../components/Layout';
|
||||
import Spacer from '../components/helpers/Spacer';
|
||||
import Loader from '../components/helpers/Loader';
|
||||
import { FullWidthRow } from '../components/helpers';
|
||||
import FullWidthRow from '../components/helpers/FullWidthRow';
|
||||
import About from '../components/settings/About';
|
||||
import Privacy from '../components/settings/Privacy';
|
||||
import Email from '../components/settings/Email';
|
||||
|
||||
const propTypes = {
|
||||
about: PropTypes.string,
|
||||
email: PropTypes.string,
|
||||
isEmailVerified: PropTypes.bool,
|
||||
location: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
picture: PropTypes.string,
|
||||
points: PropTypes.number,
|
||||
sendQuincyEmail: PropTypes.bool,
|
||||
showLoading: PropTypes.bool,
|
||||
submitNewAbout: PropTypes.func.isRequired,
|
||||
theme: PropTypes.string,
|
||||
toggleNightMode: PropTypes.func.isRequired,
|
||||
updateQuincyEmail: PropTypes.func.isRequired,
|
||||
username: PropTypes.string
|
||||
};
|
||||
|
||||
@ -34,8 +39,22 @@ const mapStateToProps = createSelector(
|
||||
userSelector,
|
||||
(
|
||||
showLoading,
|
||||
{ username = '', about, picture, points, name, location, theme }
|
||||
{
|
||||
username = '',
|
||||
about,
|
||||
email,
|
||||
sendQuincyEmail,
|
||||
isEmailVerified,
|
||||
picture,
|
||||
points,
|
||||
name,
|
||||
location,
|
||||
theme
|
||||
}
|
||||
) => ({
|
||||
email,
|
||||
sendQuincyEmail,
|
||||
isEmailVerified,
|
||||
showLoading,
|
||||
username,
|
||||
about,
|
||||
@ -49,12 +68,19 @@ const mapStateToProps = createSelector(
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{ submitNewAbout, toggleNightMode: theme => updateUserFlag({theme}) },
|
||||
{
|
||||
submitNewAbout,
|
||||
toggleNightMode: theme => updateUserFlag({ theme }),
|
||||
updateQuincyEmail: sendQuincyEmail => updateUserFlag({ sendQuincyEmail })
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
function ShowSettings(props) {
|
||||
const {
|
||||
email,
|
||||
isEmailVerified,
|
||||
sendQuincyEmail,
|
||||
showLoading,
|
||||
username,
|
||||
about,
|
||||
@ -64,7 +90,8 @@ function ShowSettings(props) {
|
||||
location,
|
||||
name,
|
||||
submitNewAbout,
|
||||
toggleNightMode
|
||||
toggleNightMode,
|
||||
updateQuincyEmail
|
||||
} = props;
|
||||
|
||||
if (showLoading) {
|
||||
@ -120,9 +147,14 @@ function ShowSettings(props) {
|
||||
<Spacer />
|
||||
<Privacy />
|
||||
<Spacer />
|
||||
{/* <EmailSettings />
|
||||
<Email
|
||||
email={email}
|
||||
isEmailVerified={isEmailVerified}
|
||||
sendQuincyEmail={sendQuincyEmail}
|
||||
updateQuincyEmail={updateQuincyEmail}
|
||||
/>
|
||||
<Spacer />
|
||||
<InternetSettings />
|
||||
{/* <InternetSettings />
|
||||
<Spacer />
|
||||
<PortfolioSettings />
|
||||
<Spacer />
|
||||
|
250
client/src/components/settings/Email.js
Normal file
250
client/src/components/settings/Email.js
Normal file
@ -0,0 +1,250 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { Link } from 'gatsby';
|
||||
import {
|
||||
HelpBlock,
|
||||
Alert,
|
||||
FormGroup,
|
||||
ControlLabel,
|
||||
FormControl,
|
||||
Button
|
||||
} from '@freecodecamp/react-bootstrap';
|
||||
import isEmail from 'validator/lib/isEmail';
|
||||
|
||||
import { updateMyEmail } from '../../redux/settings';
|
||||
import { maybeEmailRE } from '../../utils';
|
||||
|
||||
import FullWidthRow from '../helpers/FullWidthRow';
|
||||
import Spacer from '../helpers/Spacer';
|
||||
import SectionHeader from './SectionHeader';
|
||||
import BlockSaveButton from '../helpers/form/BlockSaveButton';
|
||||
import ToggleSetting from './ToggleSetting';
|
||||
|
||||
const mapStateToProps = () => ({});
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators({ updateMyEmail }, dispatch);
|
||||
|
||||
const propTypes = {
|
||||
email: PropTypes.string,
|
||||
isEmailVerified: PropTypes.bool,
|
||||
sendQuincyEmail: PropTypes.bool,
|
||||
updateMyEmail: PropTypes.func.isRequired,
|
||||
updateQuincyEmail: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export function UpdateEmailButton() {
|
||||
return (
|
||||
<Link style={{ textDecoration: 'none' }} to='/update-email'>
|
||||
<Button block={true} bsSize='lg' bsStyle='primary'>
|
||||
Edit
|
||||
</Button>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
class EmailSettings extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
emailForm: {
|
||||
currentEmail: props.email,
|
||||
newEmail: '',
|
||||
confirmNewEmail: '',
|
||||
isPristine: true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
handleSubmit = e => {
|
||||
e.preventDefault();
|
||||
const {
|
||||
emailForm: { newEmail }
|
||||
} = this.state;
|
||||
const { updateMyEmail } = this.props;
|
||||
return updateMyEmail(newEmail);
|
||||
};
|
||||
|
||||
createHandleEmailFormChange = key => e => {
|
||||
e.preventDefault();
|
||||
const userInput = e.target.value.slice();
|
||||
return this.setState(state => ({
|
||||
emailForm: {
|
||||
...state.emailForm,
|
||||
[key]: userInput,
|
||||
isPristine: userInput === state.emailForm.currentEmail
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
getValidationForNewEmail = () => {
|
||||
const {
|
||||
emailForm: { newEmail, currentEmail }
|
||||
} = this.state;
|
||||
|
||||
if (!maybeEmailRE.test(newEmail)) {
|
||||
return {
|
||||
state: null,
|
||||
message: ''
|
||||
};
|
||||
}
|
||||
if (newEmail === currentEmail) {
|
||||
return {
|
||||
state: 'error',
|
||||
message: 'This email is the same as your current email'
|
||||
};
|
||||
}
|
||||
if (isEmail(newEmail)) {
|
||||
return { state: 'success', message: '' };
|
||||
} else {
|
||||
return {
|
||||
state: 'warning',
|
||||
message:
|
||||
'We could not validate your email correctly, ' +
|
||||
'please ensure it is correct'
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
getValidationForConfirmEmail = () => {
|
||||
const {
|
||||
emailForm: { confirmNewEmail, newEmail }
|
||||
} = this.state;
|
||||
|
||||
if (!maybeEmailRE.test(newEmail)) {
|
||||
return {
|
||||
state: null,
|
||||
message: ''
|
||||
};
|
||||
}
|
||||
const isMatch = newEmail === confirmNewEmail;
|
||||
if (maybeEmailRE.test(confirmNewEmail)) {
|
||||
return {
|
||||
state: isMatch ? 'success' : 'error',
|
||||
message: isMatch ? '' : 'Both new email addresses must be the same'
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
state: null,
|
||||
message: ''
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
emailForm: { newEmail, confirmNewEmail, currentEmail, isPristine }
|
||||
} = this.state;
|
||||
const { isEmailVerified, updateQuincyEmail, sendQuincyEmail } = this.props;
|
||||
|
||||
const {
|
||||
state: newEmailValidation,
|
||||
message: newEmailValidationMessage
|
||||
} = this.getValidationForNewEmail();
|
||||
|
||||
const {
|
||||
state: confirmEmailValidation,
|
||||
message: confirmEmailValidationMessage
|
||||
} = this.getValidationForConfirmEmail();
|
||||
if (!currentEmail) {
|
||||
return (
|
||||
<div>
|
||||
<FullWidthRow>
|
||||
<p className='large-p text-center'>
|
||||
You do not have an email associated with this account.
|
||||
</p>
|
||||
</FullWidthRow>
|
||||
<FullWidthRow>
|
||||
<UpdateEmailButton />
|
||||
</FullWidthRow>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className='email-settings'>
|
||||
<SectionHeader>Email Settings</SectionHeader>
|
||||
{isEmailVerified ? null : (
|
||||
<FullWidthRow>
|
||||
<HelpBlock>
|
||||
<Alert bsStyle='info' className='text-center'>
|
||||
Your email has not been verified.
|
||||
<br />
|
||||
Please check your email, or{' '}
|
||||
<Link to='/update-email'>
|
||||
request a new verification email here
|
||||
</Link>
|
||||
.
|
||||
</Alert>
|
||||
</HelpBlock>
|
||||
</FullWidthRow>
|
||||
)}
|
||||
<FullWidthRow>
|
||||
<form id='form-update-email' onSubmit={this.handleSubmit}>
|
||||
<FormGroup controlId='current-email'>
|
||||
<ControlLabel>Current Email</ControlLabel>
|
||||
<FormControl.Static>{currentEmail}</FormControl.Static>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
controlId='new-email'
|
||||
validationState={newEmailValidation}
|
||||
>
|
||||
<ControlLabel>New Email</ControlLabel>
|
||||
<FormControl
|
||||
onChange={this.createHandleEmailFormChange('newEmail')}
|
||||
type='email'
|
||||
value={newEmail}
|
||||
/>
|
||||
{newEmailValidationMessage ? (
|
||||
<HelpBlock>{newEmailValidationMessage}</HelpBlock>
|
||||
) : null}
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
controlId='confirm-email'
|
||||
validationState={confirmEmailValidation}
|
||||
>
|
||||
<ControlLabel>Confirm New Email</ControlLabel>
|
||||
<FormControl
|
||||
onChange={this.createHandleEmailFormChange('confirmNewEmail')}
|
||||
type='email'
|
||||
value={confirmNewEmail}
|
||||
/>
|
||||
{confirmEmailValidationMessage ? (
|
||||
<HelpBlock>{confirmEmailValidationMessage}</HelpBlock>
|
||||
) : null}
|
||||
</FormGroup>
|
||||
<BlockSaveButton
|
||||
disabled={
|
||||
newEmailValidation !== 'success' ||
|
||||
confirmEmailValidation !== 'success' ||
|
||||
isPristine
|
||||
}
|
||||
/>
|
||||
</form>
|
||||
</FullWidthRow>
|
||||
<Spacer />
|
||||
<FullWidthRow>
|
||||
<form id='form-quincy-email' onSubmit={this.handleSubmit}>
|
||||
<ToggleSetting
|
||||
action="Send me Quincy's weekly email"
|
||||
flag={sendQuincyEmail}
|
||||
flagName='sendQuincyEmail'
|
||||
offLabel='No thanks'
|
||||
onLabel='Yes please'
|
||||
toggleFlag={() => updateQuincyEmail(!sendQuincyEmail)}
|
||||
/>
|
||||
</form>
|
||||
</FullWidthRow>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
EmailSettings.displayName = 'EmailSettings';
|
||||
EmailSettings.propTypes = propTypes;
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(EmailSettings);
|
@ -15,13 +15,15 @@ import {
|
||||
Button
|
||||
} from '@freecodecamp/react-bootstrap';
|
||||
import Helmet from 'react-helmet';
|
||||
import isEmail from 'validator/lib/isEmail';
|
||||
import { isString } from 'lodash';
|
||||
|
||||
import Layout from '../components/Layout';
|
||||
import { Spacer } from '../components/helpers';
|
||||
import './update-email.css';
|
||||
import { userSelector, updateMyEmail } from '../redux';
|
||||
import { isString } from 'lodash';
|
||||
import isEmail from 'validator/lib/isEmail';
|
||||
import { userSelector } from '../redux';
|
||||
import { updateMyEmail } from '../redux/settings';
|
||||
import { maybeEmailRE } from '../utils';
|
||||
|
||||
const propTypes = {
|
||||
isNewEmail: PropTypes.bool,
|
||||
@ -38,8 +40,6 @@ const mapStateToProps = createSelector(
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators({ updateMyEmail }, dispatch);
|
||||
|
||||
const maybeEmailRE = /[\w.+]*?@\w*?\.\w+?/;
|
||||
|
||||
class UpdateEmail extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -48,16 +48,15 @@ class UpdateEmail extends Component {
|
||||
emailValue: ''
|
||||
};
|
||||
|
||||
// this.createSubmitHandler = this.createSubmitHandler.bind(this);
|
||||
this.onChange = this.onChange.bind(this);
|
||||
}
|
||||
|
||||
createSubmitHandler(fn) {
|
||||
return e => {
|
||||
handleSubmit = e => {
|
||||
e.preventDefault();
|
||||
return fn(this.state.emailValue);
|
||||
const { emailValue } = this.state;
|
||||
const { updateMyEmail } = this.props;
|
||||
return updateMyEmail(emailValue);
|
||||
};
|
||||
}
|
||||
|
||||
onChange(e) {
|
||||
const change = e.target.value;
|
||||
@ -78,7 +77,7 @@ class UpdateEmail extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isNewEmail, updateMyEmail } = this.props;
|
||||
const { isNewEmail } = this.props;
|
||||
return (
|
||||
<Layout>
|
||||
<Helmet>
|
||||
@ -90,10 +89,7 @@ class UpdateEmail extends Component {
|
||||
<Row>
|
||||
<Col sm={6} smOffset={3}>
|
||||
<Row>
|
||||
<Form
|
||||
horizontal={true}
|
||||
onSubmit={this.createSubmitHandler(updateMyEmail)}
|
||||
>
|
||||
<Form horizontal={true} onSubmit={this.handleSubmit}>
|
||||
<FormGroup
|
||||
controlId='emailInput'
|
||||
validationState={this.getEmailValidationState()}
|
||||
|
@ -6,7 +6,6 @@ import { createAcceptTermsSaga } from './accept-terms-saga';
|
||||
import { createAppMountSaga } from './app-mount-saga';
|
||||
import { createReportUserSaga } from './report-user-saga';
|
||||
import { createShowCertSaga } from './show-cert-saga';
|
||||
import { createUpdateMyEmailSaga } from './update-email-saga';
|
||||
import { createNightModeSaga } from './night-mode-saga';
|
||||
|
||||
import { types as settingsTypes } from './settings';
|
||||
@ -38,7 +37,6 @@ const types = createTypes(
|
||||
...createAsyncTypes('fetchUser'),
|
||||
...createAsyncTypes('acceptTerms'),
|
||||
...createAsyncTypes('showCert'),
|
||||
...createAsyncTypes('updateMyEmail'),
|
||||
...createAsyncTypes('reportUser')
|
||||
],
|
||||
ns
|
||||
@ -48,7 +46,6 @@ export const sagas = [
|
||||
...createAcceptTermsSaga(types),
|
||||
...createAppMountSaga(types),
|
||||
...createFetchUserSaga(types),
|
||||
...createUpdateMyEmailSaga(types),
|
||||
...createShowCertSaga(types),
|
||||
...createReportUserSaga(types),
|
||||
...createNightModeSaga({ ...types, ...settingsTypes })
|
||||
@ -72,10 +69,6 @@ export const showCert = createAction(types.showCert);
|
||||
export const showCertComplete = createAction(types.showCertComplete);
|
||||
export const showCertError = createAction(types.showCertError);
|
||||
|
||||
export const updateMyEmail = createAction(types.updateMyEmail);
|
||||
export const updateMyEmailComplete = createAction(types.updateMyEmailComplete);
|
||||
export const updateMyEmailError = createAction(types.updateMyEmailError);
|
||||
|
||||
export const isSignedInSelector = state => !!Object.keys(state[ns].user).length;
|
||||
|
||||
export const signInLoadingSelector = state =>
|
||||
@ -96,6 +89,19 @@ export const userSelector = state => {
|
||||
return state[ns].user[username] || {};
|
||||
};
|
||||
|
||||
function spreadThePayloadOnUser(state, payload) {
|
||||
return {
|
||||
...state,
|
||||
user: {
|
||||
...state.user,
|
||||
[state.appUsername]: {
|
||||
...state.user[state.appUsername],
|
||||
...payload
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const reducer = handleActions(
|
||||
{
|
||||
[types.fetchUser]: state => ({
|
||||
@ -164,31 +170,11 @@ export const reducer = handleActions(
|
||||
}
|
||||
: state,
|
||||
[settingsTypes.submitNewAboutComplete]: (state, { payload }) =>
|
||||
payload
|
||||
? {
|
||||
...state,
|
||||
user: {
|
||||
...state.user,
|
||||
[state.appUsername]: {
|
||||
...state.user[state.appUsername],
|
||||
...payload
|
||||
}
|
||||
}
|
||||
}
|
||||
: state,
|
||||
payload ? spreadThePayloadOnUser(state, payload) : state,
|
||||
[settingsTypes.updateMyEmailComplete]: (state, { payload }) =>
|
||||
payload ? spreadThePayloadOnUser(state, payload) : state,
|
||||
[settingsTypes.updateUserFlagComplete]: (state, { payload }) =>
|
||||
payload
|
||||
? {
|
||||
...state,
|
||||
user: {
|
||||
...state.user,
|
||||
[state.appUsername]: {
|
||||
...state.user[state.appUsername],
|
||||
...payload
|
||||
}
|
||||
}
|
||||
}
|
||||
: state
|
||||
payload ? spreadThePayloadOnUser(state, payload) : state
|
||||
},
|
||||
initialState
|
||||
);
|
||||
|
@ -2,6 +2,7 @@ import { createAction, handleActions } from 'redux-actions';
|
||||
|
||||
import { createTypes, createAsyncTypes } from '../../utils/createTypes';
|
||||
import { createSettingsSagas } from './settings-sagas';
|
||||
import { createUpdateMyEmailSaga } from './update-email-saga';
|
||||
|
||||
const ns = 'settings';
|
||||
|
||||
@ -24,16 +25,21 @@ export const types = createTypes(
|
||||
...createAsyncTypes('validateUsername'),
|
||||
...createAsyncTypes('submitNewAbout'),
|
||||
...createAsyncTypes('submitNewUsername'),
|
||||
...createAsyncTypes('updateMyEmail'),
|
||||
...createAsyncTypes('updateUserFlag'),
|
||||
...createAsyncTypes('submitProfileUI')
|
||||
],
|
||||
ns
|
||||
);
|
||||
|
||||
export const sagas = [
|
||||
...createSettingsSagas(types),
|
||||
...createUpdateMyEmailSaga(types)
|
||||
];
|
||||
|
||||
const checkForSuccessPayload = ({ type, payload }) =>
|
||||
type === 'success' ? payload : null;
|
||||
|
||||
export const sagas = [...createSettingsSagas(types)];
|
||||
|
||||
export const submitNewAbout = createAction(types.submitNewAbout);
|
||||
export const submitNewAboutComplete = createAction(
|
||||
types.submitNewAboutComplete,
|
||||
@ -57,6 +63,10 @@ export const submitProfileUIComplete = createAction(
|
||||
);
|
||||
export const submitProfileUIError = createAction(types.submitProfileUIError);
|
||||
|
||||
export const updateMyEmail = createAction(types.updateMyEmail);
|
||||
export const updateMyEmailComplete = createAction(types.updateMyEmailComplete);
|
||||
export const updateMyEmailError = createAction(types.updateMyEmailError);
|
||||
|
||||
export const updateUserFlag = createAction(types.updateUserFlag);
|
||||
export const updateUserFlagComplete = createAction(
|
||||
types.updateUserFlagComplete,
|
||||
|
32
client/src/redux/settings/update-email-saga.js
Normal file
32
client/src/redux/settings/update-email-saga.js
Normal file
@ -0,0 +1,32 @@
|
||||
import { call, put, takeEvery } from 'redux-saga/effects';
|
||||
import isEmail from 'validator/lib/isEmail';
|
||||
|
||||
import { updateMyEmailComplete, updateMyEmailError } from './';
|
||||
import { createFlashMessage } from '../../components/Flash/redux';
|
||||
|
||||
import { putUserUpdateEmail } from '../../utils/ajax';
|
||||
import reallyWeirdErrorMessage from '../../utils/reallyWeirdErrorMessage';
|
||||
|
||||
function* updateMyEmailSaga({ payload: email = '' }) {
|
||||
console.log('saga', email);
|
||||
if (!email || !isEmail(email)) {
|
||||
yield put(createFlashMessage(reallyWeirdErrorMessage));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const { data: response } = yield call(putUserUpdateEmail, email);
|
||||
yield put(
|
||||
updateMyEmailComplete({
|
||||
...response,
|
||||
payload: { email, isEmailVerified: false }
|
||||
})
|
||||
);
|
||||
yield put(createFlashMessage(response));
|
||||
} catch (e) {
|
||||
yield put(updateMyEmailError(e));
|
||||
}
|
||||
}
|
||||
|
||||
export function createUpdateMyEmailSaga(types) {
|
||||
return [takeEvery(types.updateMyEmail, updateMyEmailSaga)];
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
import { call, put, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
import { updateMyEmailComplete, updateMyEmailError } from './';
|
||||
import { createFlashMessage } from '../components/Flash/redux';
|
||||
|
||||
import { putUserUpdateEmail } from '../utils/ajax';
|
||||
|
||||
function* updateMyEmailSaga({ payload: newEmail }) {
|
||||
try {
|
||||
const { data: response } = yield call(putUserUpdateEmail, newEmail);
|
||||
|
||||
yield put(updateMyEmailComplete());
|
||||
yield put(createFlashMessage(response));
|
||||
} catch (e) {
|
||||
yield put(updateMyEmailError(e));
|
||||
}
|
||||
}
|
||||
|
||||
export function createUpdateMyEmailSaga(types) {
|
||||
return [takeEvery(types.updateMyEmail, updateMyEmailSaga)];
|
||||
}
|
3
client/src/utils/index.js
Normal file
3
client/src/utils/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
// This regex is not for validation, it is purely to see
|
||||
// if we are looking at something like an email before we try to validate
|
||||
export const maybeEmailRE = /.*@.*\.\w\w/;
|
Reference in New Issue
Block a user