feat(username): Add Username updating

This commit is contained in:
Bouncey
2018-09-13 18:28:23 +01:00
committed by Stuart Taylor
parent 99e025699a
commit 4f54803674
14 changed files with 807 additions and 171 deletions

View File

@ -1,4 +1,6 @@
module.exports = function mountRestApi(app) { module.exports = function mountRestApi(app) {
var restApiRoot = app.get('restApiRoot'); const restApi = app.loopback.rest();
app.use(restApiRoot, app.loopback.rest()); const restApiRoot = app.get('restApiRoot');
app.use(restApiRoot, restApi);
app.use(`/internal${restApiRoot}`, restApi);
}; };

View File

@ -5,167 +5,8 @@ import { alertTypes } from '../../common/utils/flash.js';
export default function settingsController(app) { export default function settingsController(app) {
const api = app.loopback.Router(); const api = app.loopback.Router();
const toggleUserFlag = (flag, req, res, next) => {
const { user } = req;
const currentValue = user[flag];
return user.update$({ [flag]: !currentValue }).subscribe(
() =>
res.status(200).json({
flag,
value: !currentValue
}),
next
);
};
function refetchCompletedChallenges(req, res, next) { const updateMyUsername = createUpdateMyUsername(app);
const { user } = req;
return user
.requestCompletedChallenges()
.subscribe(completedChallenges => res.json({ completedChallenges }), next);
}
const updateMyEmailValidators = [
check('email')
.isEmail()
.withMessage('Email format is invalid.')
];
function updateMyEmail(req, res, next) {
const {
user,
body: { email }
} = req;
return user
.requestUpdateEmail(email)
.subscribe(message => res.json({ message }), next);
}
const updateMyCurrentChallengeValidators = [
check('currentChallengeId')
.isMongoId()
.withMessage('currentChallengeId is not a valid challenge ID')
];
function updateMyCurrentChallenge(req, res, next) {
const {
user,
body: { currentChallengeId }
} = req;
return user.update$({ currentChallengeId }).subscribe(
() =>
res.json({
message: `your current challenge has been updated to ${currentChallengeId}`
}),
next
);
}
const updateMyThemeValidators = [
check('theme')
.isIn(Object.keys(themes))
.withMessage('Theme is invalid.')
];
function updateMyTheme(req, res, next) {
const {
body: { theme }
} = req;
if (req.user.theme === theme) {
return res.sendFlash(alertTypes.info, 'Theme already set');
}
return req.user
.updateTheme(theme)
.then(
() => res.sendFlash(alertTypes.info, 'Your theme has been updated'),
next
);
}
function updateFlags(req, res, next) {
const {
user,
body: { values }
} = req;
const keys = Object.keys(values);
if (keys.length === 1 && typeof keys[0] === 'boolean') {
return toggleUserFlag(keys[0], req, res, next);
}
return user
.requestUpdateFlags(values)
.subscribe(message => res.json({ message }), next);
}
function updateMyPortfolio(req, res, next) {
const {
user,
body: { portfolio }
} = req;
// if we only have one key, it should be the id
// user cannot send only one key to this route
// other than to remove a portfolio item
const requestDelete = Object.keys(portfolio).length === 1;
return user
.updateMyPortfolio(portfolio, requestDelete)
.subscribe(message => res.json({ message }), next);
}
function updateMyProfileUI(req, res, next) {
const {
user,
body: { profileUI }
} = req;
return user
.updateMyProfileUI(profileUI)
.subscribe(message => res.json({ message }), next);
}
function updateMyProjects(req, res, next) {
const {
user,
body: { projects: project }
} = req;
return user
.updateMyProjects(project)
.subscribe(message => res.json({ message }), next);
}
function updateMyUsername(req, res, next) {
const {
user,
body: { username }
} = req;
return user
.updateMyUsername(username)
.subscribe(message => res.json({ message }), next);
}
const updatePrivacyTerms = (req, res) => {
const {
user,
body: { quincyEmails }
} = req;
const update = {
acceptedPrivacyTerms: true,
sendQuincyEmail: !!quincyEmails
};
return user.updateAttributes(update, err => {
if (err) {
return res.status(500).json({
type: 'warning',
message:
'Something went wrong updating your preferences. ' +
'Please try again.'
});
}
return res.status(200).json({
type: 'success',
message:
'We have updated your preferences. ' +
'You can now continue using freeCodeCamp.'
});
});
};
api.put('/update-privacy-terms', ifNoUser401, updatePrivacyTerms); api.put('/update-privacy-terms', ifNoUser401, updatePrivacyTerms);
@ -206,9 +47,192 @@ export default function settingsController(app) {
createValidatorErrorHandler(alertTypes.danger), createValidatorErrorHandler(alertTypes.danger),
updateMyTheme updateMyTheme
); );
api.post('/update-my-username', ifNoUser401, updateMyUsername); api.put('/update-my-username', ifNoUser401, updateMyUsername);
app.use('/external', api);
app.use('/internal', api); app.use('/internal', api);
app.use(api); app.use(api);
} }
const standardErrorMessage = {
type: 'danger',
message:
'Something went wrong updating your account. Please check and try again'
};
const toggleUserFlag = (flag, req, res, next) => {
const { user } = req;
const currentValue = user[flag];
return user.update$({ [flag]: !currentValue }).subscribe(
() =>
res.status(200).json({
flag,
value: !currentValue
}),
next
);
};
function refetchCompletedChallenges(req, res, next) {
const { user } = req;
return user
.requestCompletedChallenges()
.subscribe(completedChallenges => res.json({ completedChallenges }), next);
}
const updateMyEmailValidators = [
check('email')
.isEmail()
.withMessage('Email format is invalid.')
];
function updateMyEmail(req, res, next) {
const {
user,
body: { email }
} = req;
return user
.requestUpdateEmail(email)
.subscribe(message => res.json({ message }), next);
}
const updateMyCurrentChallengeValidators = [
check('currentChallengeId')
.isMongoId()
.withMessage('currentChallengeId is not a valid challenge ID')
];
function updateMyCurrentChallenge(req, res, next) {
const {
user,
body: { currentChallengeId }
} = req;
return user
.update$({ currentChallengeId })
.subscribe(() => res.status(200), next);
}
const updateMyThemeValidators = [
check('theme')
.isIn(Object.keys(themes))
.withMessage('Theme is invalid.')
];
function updateMyTheme(req, res, next) {
const {
body: { theme }
} = req;
if (req.user.theme === theme) {
return res.sendFlash(alertTypes.info, 'Theme already set');
}
return req.user
.updateTheme(theme)
.then(
() => res.sendFlash(alertTypes.info, 'Your theme has been updated'),
next
);
}
function updateFlags(req, res, next) {
const {
user,
body: { values }
} = req;
const keys = Object.keys(values);
if (keys.length === 1 && typeof keys[0] === 'boolean') {
return toggleUserFlag(keys[0], req, res, next);
}
return user
.requestUpdateFlags(values)
.subscribe(message => res.json({ message }), next);
}
function updateMyPortfolio(req, res, next) {
const {
user,
body: { portfolio }
} = req;
// if we only have one key, it should be the id
// user cannot send only one key to this route
// other than to remove a portfolio item
const requestDelete = Object.keys(portfolio).length === 1;
return user
.updateMyPortfolio(portfolio, requestDelete)
.subscribe(message => res.json({ message }), next);
}
function updateMyProfileUI(req, res, next) {
const {
user,
body: { profileUI }
} = req;
return user
.updateMyProfileUI(profileUI)
.subscribe(message => res.json({ message }), next);
}
function updateMyProjects(req, res, next) {
const {
user,
body: { projects: project }
} = req;
return user
.updateMyProjects(project)
.subscribe(message => res.json({ message }), next);
}
function createUpdateMyUsername(app) {
const { User } = app.models;
return async function updateMyUsername(req, res, next) {
const {
user,
body: { username }
} = req;
if (username === user.username) {
return res.json({
type: 'info',
message: 'Username is already associated with this account'
});
}
const exists = await User.doesExist(username);
if (exists) {
return res.json({
type: 'info',
message: 'Username is already associated with a different account'
});
}
return user.updateAttribute('username', username, err => {
if (err) {
res.status(500).json(standardErrorMessage);
return next(err);
}
return res.status(200).json({
type: 'success',
message: `We have updated your username to ${username}`
});
});
};
}
const updatePrivacyTerms = (req, res) => {
const {
user,
body: { quincyEmails }
} = req;
const update = {
acceptedPrivacyTerms: true,
sendQuincyEmail: !!quincyEmails
};
return user.updateAttributes(update, err => {
if (err) {
return res.status(500).json(standardErrorMessage);
}
return res.status(200).json({
type: 'success',
message:
'We have updated your preferences. ' +
'You can now continue using freeCodeCamp.'
});
});
};

View File

@ -92,7 +92,7 @@ function ShowSettings(props) {
</FullWidthRow> </FullWidthRow>
<Spacer /> <Spacer />
<h1 className='text-center'>{`Account Settings for ${username}`}</h1> <h1 className='text-center'>{`Account Settings for ${username}`}</h1>
{/* <About <About
about={about} about={about}
currentTheme={theme} currentTheme={theme}
location={location} location={location}
@ -102,7 +102,7 @@ function ShowSettings(props) {
username={username} username={username}
/> />
<Spacer /> <Spacer />
<PrivacySettings /> {/* <PrivacySettings />
<Spacer /> <Spacer />
<EmailSettings /> <EmailSettings />
<Spacer /> <Spacer />

View File

@ -22,4 +22,10 @@ h6 {
.green-text { .green-text {
color: #006400; color: #006400;
}
.btn-invert {
background-color: #fff;
color: #006400;
} }

View File

@ -0,0 +1,227 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import {
Nav,
NavItem,
FormGroup,
ControlLabel,
FormControl
} from '@freecodecamp/react-bootstrap';
import { FullWidthRow, Spacer } from '../helpers';
import ThemeSettings from './Theme';
import Camper from './Camper';
import UsernameSettings from './Username';
import BlockSaveButton from '../helpers/form/BlockSaveButton';
import BlockSaveWrapper from '../helpers/form/BlockSaveWrapper';
const mapStateToProps = () => ({});
const mapDispatchToProps = dispatch => bindActionCreators({}, dispatch);
const propTypes = {
about: PropTypes.string,
currentTheme: PropTypes.string,
location: PropTypes.string,
name: PropTypes.string,
picture: PropTypes.string,
points: PropTypes.number,
username: PropTypes.string
};
class AboutSettings extends Component {
constructor(props) {
super(props);
const { name = '', location = '', picture = '', about = '' } = props;
this.state = {
view: 'edit',
formValues: {
name,
location,
picture,
about
},
isFormPristine: true
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleTabSelect = this.handleTabSelect.bind(this);
this.renderEdit = this.renderEdit.bind(this);
this.renderPreview = this.renderPreview.bind(this);
this.show = {
edit: this.renderEdit,
preview: this.renderPreview
};
}
handleSubmit(e) {
e.preventDefault();
const { formValues } = this.state;
console.log(formValues)
}
handleNameChange = e => {
const value = e.target.value.slice(0);
return this.setState(state => ({
formValues: {
...state.formValues,
name: value
}
}));
};
handleLocationChange = e => {
const value = e.target.value.slice(0);
return this.setState(state => ({
formValues: {
...state.formValues,
location: value
}
}));
};
handlePictureChange = e => {
const value = e.target.value.slice(0);
return this.setState(state => ({
formValues: {
...state.formValues,
picture: value
}
}));
};
handleAboutChange = e => {
const value = e.target.value.slice(0);
return this.setState(state => ({
formValues: {
...state.formValues,
about: value
}
}));
};
handleTabSelect(key) {
return this.setState(state => ({
...state,
view: key
}));
}
renderEdit() {
const {
formValues: { name, location, picture, about }
} = this.state;
return (
<Fragment>
<FormGroup controlId='about-name'>
<ControlLabel>
<strong>Name</strong>
</ControlLabel>
<FormControl
onChange={this.handleNameChange}
type='text'
value={name}
/>
</FormGroup>
<FormGroup controlId='about-location'>
<ControlLabel>
<strong>Location</strong>
</ControlLabel>
<FormControl
onChange={this.handleLocationChange}
type='text'
value={location}
/>
</FormGroup>
<FormGroup controlId='about-picture'>
<ControlLabel>
<strong>Picture</strong>
</ControlLabel>
<FormControl
onChange={this.handlePictureChange}
required={true}
type='url'
value={picture}
/>
</FormGroup>
<FormGroup controlId='about-about'>
<ControlLabel>
<strong>About</strong>
</ControlLabel>
<FormControl
componentClass='textarea'
onChange={this.handleAboutChange}
value={about}
/>
</FormGroup>
</Fragment>
);
}
renderPreview() {
const { about, picture, points, username, name, location } = this.props;
return (
<Camper
about={about}
location={location}
name={name}
picture={picture}
points={points}
username={username}
/>
);
}
render() {
const { currentTheme, username } = this.props;
const { view, isFormPristine } = this.state;
const toggleTheme = () => {};
return (
<div className='about-settings'>
<UsernameSettings username={username} />
<FullWidthRow>
<Nav
activeKey={view}
bsStyle='tabs'
className='edit-preview-tabs'
onSelect={k => this.handleTabSelect(k)}
>
<NavItem eventKey='edit' title='Edit Bio'>
Edit Bio
</NavItem>
<NavItem eventKey='preview' title='Preview Bio'>
Preview Bio
</NavItem>
</Nav>
</FullWidthRow>
<br />
<FullWidthRow>
<form id='camper-identity' onSubmit={this.handleSubmit}>
{this.show[view]()}
<BlockSaveButton disabled={isFormPristine} />
</form>
</FullWidthRow>
<Spacer />
<FullWidthRow>
<ThemeSettings
currentTheme={currentTheme}
toggleNightMode={toggleTheme}
/>
</FullWidthRow>
</div>
);
}
}
AboutSettings.displayName = 'AboutSettings';
AboutSettings.propTypes = propTypes;
export default connect(
mapStateToProps,
mapDispatchToProps
)(AboutSettings);

View File

@ -0,0 +1,9 @@
import React from 'react';
function CamperSettings() {
return (<h1>CamperSettings</h1>);
}
CamperSettings.displayName = 'CamperSettings';
export default CamperSettings;

View File

@ -0,0 +1,9 @@
import React from 'react';
function ThemeSettings() {
return (<h1>ThemeSettings</h1>);
}
ThemeSettings.displayName = 'ThemeSettings';
export default ThemeSettings;

View File

@ -0,0 +1,209 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import {
ControlLabel,
FormControl,
Alert,
FormGroup
} from '@freecodecamp/react-bootstrap';
import isAscii from 'validator/lib/isAscii';
import {
validateUsername,
usernameValidationSelector,
submitNewUsername
} from '../../redux/settings';
import BlockSaveButton from '../helpers/form/BlockSaveButton';
import FullWidthRow from '../helpers/FullWidthRow';
const propTypes = {
isValidUsername: PropTypes.bool,
submitNewUsername: PropTypes.func.isRequired,
username: PropTypes.string,
validateUsername: PropTypes.func.isRequired,
validating: PropTypes.bool
};
const mapStateToProps = createSelector(
usernameValidationSelector,
({ isValidUsername, fetchState }) => ({
isValidUsername,
validating: fetchState.pending
})
);
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
submitNewUsername,
validateUsername
},
dispatch
);
const invalidCharsRE = /[/\s?:@=&"'<>#%{}|\\^~[\]`,.;!*()$]/;
const invlaidCharError = {
valid: false,
error: 'Username contains invalid characters'
};
const valididationSuccess = { valid: true, error: null };
const usernameTooShort = { valid: false, error: 'Username is too short' };
class UsernameSettings extends Component {
constructor(props) {
super(props);
this.state = {
isFormPristine: true,
formValue: props.username,
characterValidation: { valid: false, error: null },
sumbitClicked: false
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.validateFormInput = this.validateFormInput.bind(this);
}
componentDidUpdate(prevProps, prevState) {
const { username: prevUsername } = prevProps;
const { formValue: prevFormValue } = prevState;
const { username } = this.props;
const { formValue } = this.state;
if (prevUsername !== username && prevFormValue === formValue) {
/* eslint-disable-next-line react/no-did-update-set-state */
return this.setState({
isFormPristine: username === formValue,
sumbitClicked: false
});
}
return null;
}
handleSubmit(e) {
e.preventDefault();
const { submitNewUsername } = this.props;
const {
formValue,
characterValidation: { valid }
} = this.state;
return this.setState(
{ sumbitClicked: true },
() => (valid ? submitNewUsername(formValue) : null)
);
}
handleChange(e) {
e.preventDefault();
const { username, validateUsername } = this.props;
const newValue = e.target.value.toLowerCase();
return this.setState(
{
formValue: newValue,
isFormPristine: username === newValue,
characterValidation: this.validateFormInput(newValue)
},
() =>
this.state.isFormPristine || this.state.characterValidation.error
? null
: validateUsername(this.state.formValue)
);
}
validateFormInput(formValue) {
if (formValue.length < 3) {
return usernameTooShort;
}
if (!isAscii(formValue)) {
return invlaidCharError;
}
if (invalidCharsRE.test(formValue)) {
return invlaidCharError;
}
return valididationSuccess;
}
renderAlerts(validating, error, isValidUsername) {
if (!validating && error) {
return (
<FullWidthRow>
<Alert bsStyle='danger'>{error}</Alert>
</FullWidthRow>
);
}
if (!validating && !isValidUsername) {
console.log(this.props, this.state);
return (
<FullWidthRow>
<Alert bsStyle='warning'>Username not available</Alert>
</FullWidthRow>
);
}
if (validating) {
return (
<FullWidthRow>
<Alert bsStyle='info'>Validating username</Alert>
</FullWidthRow>
);
}
if (!validating && isValidUsername) {
return (
<FullWidthRow>
<Alert bsStyle='success'>Username is available</Alert>
</FullWidthRow>
);
}
return null;
}
render() {
const {
isFormPristine,
formValue,
characterValidation: { valid, error },
sumbitClicked
} = this.state;
const { isValidUsername, validating } = this.props;
return (
<Fragment>
<form id='usernameSettings' onSubmit={this.handleSubmit}>
<FullWidthRow>
<FormGroup>
<ControlLabel htmlFor='username-settings'>
<strong>Username</strong>
</ControlLabel>
<FormControl
name='username-settings'
onChange={this.handleChange}
value={formValue}
/>
</FormGroup>
</FullWidthRow>
{!isFormPristine &&
this.renderAlerts(validating, error, isValidUsername)}
<FullWidthRow>
<BlockSaveButton
disabled={
!(isValidUsername && valid && !isFormPristine) || sumbitClicked
}
/>
</FullWidthRow>
</form>
</Fragment>
);
}
}
UsernameSettings.displayName = 'UsernameSettings';
UsernameSettings.propTypes = propTypes;
export default connect(
mapStateToProps,
mapDispatchToProps
)(UsernameSettings);

View File

@ -8,6 +8,8 @@ import { createReportUserSaga } from './report-user-saga';
import { createShowCertSaga } from './show-cert-saga'; import { createShowCertSaga } from './show-cert-saga';
import { createUpdateMyEmailSaga } from './update-email-saga'; import { createUpdateMyEmailSaga } from './update-email-saga';
import { types as settingsTypes } from './settings';
const ns = 'app'; const ns = 'app';
const defaultFetchState = { const defaultFetchState = {
@ -74,6 +76,9 @@ export const updateMyEmailError = createAction(types.updateMyEmailError);
export const isSignedInSelector = state => !!Object.keys(state[ns].user).length; export const isSignedInSelector = state => !!Object.keys(state[ns].user).length;
export const signInLoadingSelector = state =>
userFetchStateSelector(state).pending;
export const showCertSelector = state => state[ns].showCert; export const showCertSelector = state => state[ns].showCert;
export const showCertFetchStateSelector = state => state[ns].showCertFetchState; export const showCertFetchStateSelector = state => state[ns].showCertFetchState;
@ -83,7 +88,11 @@ export const userByNameSelector = username => state => {
}; };
export const userFetchStateSelector = state => state[ns].userFetchState; export const userFetchStateSelector = state => state[ns].userFetchState;
export const usernameSelector = state => state[ns].appUsername; export const usernameSelector = state => state[ns].appUsername;
export const userSelector = state => state[ns].user; export const userSelector = state => {
const username = usernameSelector(state);
return state[ns].user[username] || {};
};
export const reducer = handleActions( export const reducer = handleActions(
{ {
@ -93,7 +102,10 @@ export const reducer = handleActions(
}), }),
[types.fetchUserComplete]: (state, { payload: { user, username } }) => ({ [types.fetchUserComplete]: (state, { payload: { user, username } }) => ({
...state, ...state,
user, user: {
...state.user,
[username]: user
},
appUsername: username, appUsername: username,
userFetchState: { userFetchState: {
pending: false, pending: false,
@ -135,7 +147,20 @@ export const reducer = handleActions(
errored: true, errored: true,
error: payload error: payload
} }
}) }),
[settingsTypes.submitNewUsernameComplete]: (state, { payload }) =>
payload
? {
...state,
user: {
...state.user,
[state.appUsername]: {
...state.user[state.appUsername],
username: payload
}
}
}
: state
}, },
initialState initialState
); );

View File

@ -2,8 +2,10 @@ import { combineReducers } from 'redux';
import { reducer as app } from './'; import { reducer as app } from './';
import { reducer as flash } from '../components/Flash/redux'; import { reducer as flash } from '../components/Flash/redux';
import { reducer as settings } from './settings';
export default combineReducers({ export default combineReducers({
app, app,
flash flash,
settings
}); });

View File

@ -1,7 +1,8 @@
import { all } from 'redux-saga/effects'; import { all } from 'redux-saga/effects';
import { sagas as appSagas } from './'; import { sagas as appSagas } from './';
import { sagas as settingsSagas } from './settings';
export default function* rootSaga() { export default function* rootSaga() {
yield all([...appSagas]); yield all([...appSagas, ...settingsSagas]);
} }

View File

@ -0,0 +1,73 @@
import { createAction, handleActions } from 'redux-actions';
import { createTypes, createAsyncTypes } from '../../utils/createTypes';
import { createSettingsSagas } from './settings-sagas';
const ns = 'settings';
const defaultFetchState = {
pending: false,
complete: false,
errored: false,
error: null
};
const initialState = {
usernameValidation: {
isValidUsername: false,
fetchState: { ...defaultFetchState }
}
};
export const types = createTypes(
[
...createAsyncTypes('validateUsername'),
...createAsyncTypes('submitNewUsername')
],
ns
);
export const sagas = [...createSettingsSagas(types)];
export const submitNewUsername = createAction(types.submitNewUsername);
export const submitNewUsernameComplete = createAction(
types.submitNewUsernameComplete,
({ type, username }) => (type === 'success' ? username : null)
);
export const submitNewUsernameError = createAction(
types.submitNewUsernameError
);
export const validateUsername = createAction(types.validateUsername);
export const validateUsernameComplete = createAction(
types.validateUsernameComplete
);
export const validateUsernameError = createAction(types.validateUsernameError);
export const usernameValidationSelector = state => state[ns].usernameValidation;
export const reducer = handleActions(
{
[types.submitNewUsernameComplete]: state => ({
...state,
usernameValidation: { ...initialState.usernameValidation }
}),
[types.validateUsername]: state => ({
...state,
usernameValidation: {
...state.usernameValidation,
isValidUsername: false,
fetchState: { ...defaultFetchState, pending: true }
}
}),
[types.validateUsernameComplete]: (state, { payload }) => ({
...state,
usernameValidation: {
...state.usernameValidation,
isValidUsername: !payload,
fetchState: { ...defaultFetchState, pending: false, complete: true }
}
})
},
initialState
);

View File

@ -0,0 +1,41 @@
import { delay } from 'redux-saga';
import { call, put, takeLatest } from 'redux-saga/effects';
import {
validateUsernameComplete,
validateUsernameError,
submitNewUsernameComplete,
submitNewUsernameError
} from './';
import { getUsernameExists, putUpdateMyUsername } from '../../utils/ajax';
import { createFlashMessage } from '../../components/Flash/redux';
function* validateUsernameSaga({ payload }) {
try {
yield delay(500);
const {
data: { exists }
} = yield call(getUsernameExists, payload);
yield put(validateUsernameComplete(exists));
} catch (e) {
yield put(validateUsernameError(e));
}
}
function* submitNEwUswernameSaga({ payload: username }) {
try {
const { data: response } = yield call(putUpdateMyUsername, username);
yield put(submitNewUsernameComplete({...response, username}));
yield put(createFlashMessage(response));
} catch (e) {
yield put(submitNewUsernameError(e));
}
}
export function createSettingsSagas(types) {
return [
takeLatest(types.validateUsername, validateUsernameSaga),
takeLatest(types.submitNewUsername, submitNEwUswernameSaga)
];
}

View File

@ -28,6 +28,10 @@ export function getShowCert(username, cert) {
return get(`/certificate/showCert/${username}/${cert}`); return get(`/certificate/showCert/${username}/${cert}`);
} }
export function getUsernameExists(username) {
return get(`/api/users/exists?username=${username}`);
}
/** POST **/ /** POST **/
export function postReportUser(body) { export function postReportUser(body) {
@ -36,6 +40,10 @@ export function postReportUser(body) {
/** PUT **/ /** PUT **/
export function putUpdateMyUsername(username) {
return put('/update-my-username', { username });
}
export function putUserAcceptsTerms(quincyEmails) { export function putUserAcceptsTerms(quincyEmails) {
return put('/update-privacy-terms', { quincyEmails }); return put('/update-privacy-terms', { quincyEmails });
} }