feat(about): Submit new 'about' settings

This commit is contained in:
Bouncey
2018-09-14 13:18:31 +01:00
committed by Stuart Taylor
parent 68cdeea6ae
commit a41ef09932
7 changed files with 174 additions and 140 deletions

View File

@ -1,8 +1,12 @@
import debug from 'debug';
import { check } from 'express-validator/check'; import { check } from 'express-validator/check';
import { ifNoUser401, createValidatorErrorHandler } from '../utils/middleware'; import { ifNoUser401, createValidatorErrorHandler } from '../utils/middleware';
import { themes } from '../../common/utils/themes.js'; import { themes } from '../../common/utils/themes.js';
import { alertTypes } from '../../common/utils/flash.js'; import { alertTypes } from '../../common/utils/flash.js';
const log = debug('fcc:boot:settings');
export default function settingsController(app) { export default function settingsController(app) {
const api = app.loopback.Router(); const api = app.loopback.Router();
@ -47,6 +51,7 @@ export default function settingsController(app) {
createValidatorErrorHandler(alertTypes.danger), createValidatorErrorHandler(alertTypes.danger),
updateMyTheme updateMyTheme
); );
api.put('/update-my-about', ifNoUser401, updateMyAbout);
api.put('/update-my-username', ifNoUser401, updateMyUsername); api.put('/update-my-username', ifNoUser401, updateMyUsername);
app.use('/internal', api); app.use('/internal', api);
@ -59,6 +64,11 @@ const standardErrorMessage = {
'Something went wrong updating your account. Please check and try again' 'Something went wrong updating your account. Please check and try again'
}; };
const standardSuccessMessage = {
type: 'success',
message: 'We have updated your preferences'
};
const toggleUserFlag = (flag, req, res, next) => { const toggleUserFlag = (flag, req, res, next) => {
const { user } = req; const { user } = req;
const currentValue = user[flag]; const currentValue = user[flag];
@ -180,6 +190,21 @@ function updateMyProjects(req, res, next) {
.subscribe(message => res.json({ message }), next); .subscribe(message => res.json({ message }), next);
} }
function updateMyAbout(req, res, next) {
const {
user,
body: { name, location, about, picture }
} = req;
log(name, location, picture, about)
return user.updateAttributes({ name, location, about, picture }, err => {
if (err) {
res.status(500).json(standardErrorMessage);
return next(err);
}
return res.status(200).json(standardSuccessMessage);
});
}
function createUpdateMyUsername(app) { function createUpdateMyUsername(app) {
const { User } = app.models; const { User } = app.models;
return async function updateMyUsername(req, res, next) { return async function updateMyUsername(req, res, next) {

View File

@ -1,25 +1,24 @@
import React, { Component, Fragment } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import {
Nav,
NavItem,
FormGroup, FormGroup,
ControlLabel, ControlLabel,
FormControl FormControl
} from '@freecodecamp/react-bootstrap'; } from '@freecodecamp/react-bootstrap';
import { submitNewAbout } from '../../redux/settings';
import { FullWidthRow, Spacer } from '../helpers'; import { FullWidthRow, Spacer } from '../helpers';
import ThemeSettings from './Theme'; import ThemeSettings from './Theme';
import Camper from './Camper';
import UsernameSettings from './Username'; import UsernameSettings from './Username';
import BlockSaveButton from '../helpers/form/BlockSaveButton'; import BlockSaveButton from '../helpers/form/BlockSaveButton';
import BlockSaveWrapper from '../helpers/form/BlockSaveWrapper';
const mapStateToProps = () => ({}); const mapStateToProps = () => ({});
const mapDispatchToProps = dispatch => bindActionCreators({}, dispatch); const mapDispatchToProps = dispatch =>
bindActionCreators({ submitNewAbout }, dispatch);
const propTypes = { const propTypes = {
about: PropTypes.string, about: PropTypes.string,
@ -28,6 +27,7 @@ const propTypes = {
name: PropTypes.string, name: PropTypes.string,
picture: PropTypes.string, picture: PropTypes.string,
points: PropTypes.number, points: PropTypes.number,
submitNewAbout: PropTypes.func.isRequired,
username: PropTypes.string username: PropTypes.string
}; };
@ -36,33 +36,58 @@ class AboutSettings extends Component {
super(props); super(props);
const { name = '', location = '', picture = '', about = '' } = props; const { name = '', location = '', picture = '', about = '' } = props;
const values = {
name,
location,
picture,
about
};
this.state = { this.state = {
view: 'edit', formValues: { ...values },
formValues: { originalValues: { ...values },
formClicked: false
};
}
componentDidUpdate() {
const { name, location, picture, about } = this.props;
const { formValues, formClicked } = this.state;
if (
formClicked &&
name === formValues.name &&
location === formValues.location &&
picture === formValues.picture &&
about === formValues.about
) {
/* eslint-disable-next-line react/no-did-update-set-state */
return this.setState({
originalValues: {
name, name,
location, location,
picture, picture,
about about
}, },
isFormPristine: true formClicked: false
}; });
}
this.handleSubmit = this.handleSubmit.bind(this); return null;
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) { isFormPristine = () => {
const { formValues, originalValues } = this.state;
return Object.keys(originalValues)
.map(key => originalValues[key] === formValues[key])
.every(bool => bool);
};
handleSubmit = e => {
e.preventDefault(); e.preventDefault();
const { formValues } = this.state; const { formValues } = this.state;
console.log(formValues) const { submitNewAbout } = this.props;
} return this.setState({ formClicked: true }, () =>
submitNewAbout(formValues)
);
};
handleNameChange = e => { handleNameChange = e => {
const value = e.target.value.slice(0); const value = e.target.value.slice(0);
@ -104,19 +129,18 @@ class AboutSettings extends Component {
})); }));
}; };
handleTabSelect(key) { render() {
return this.setState(state => ({
...state,
view: key
}));
}
renderEdit() {
const { const {
formValues: { name, location, picture, about } formValues: { name, location, picture, about }
} = this.state; } = this.state;
const { currentTheme, username } = this.props;
const toggleTheme = () => {};
return ( return (
<Fragment> <div className='about-settings'>
<UsernameSettings username={username} />
<br />
<FullWidthRow>
<form id='camper-identity' onSubmit={this.handleSubmit}>
<FormGroup controlId='about-name'> <FormGroup controlId='about-name'>
<ControlLabel> <ControlLabel>
<strong>Name</strong> <strong>Name</strong>
@ -158,52 +182,7 @@ class AboutSettings extends Component {
value={about} value={about}
/> />
</FormGroup> </FormGroup>
</Fragment> <BlockSaveButton disabled={this.isFormPristine()} />
);
}
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> </form>
</FullWidthRow> </FullWidthRow>
<Spacer /> <Spacer />

View File

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

View File

@ -160,6 +160,17 @@ export const reducer = handleActions(
} }
} }
} }
: state,
[settingsTypes.submitNewAboutComplete]: (state, {payload}) => payload ? {
...state,
user: {
...state.user,
[state.appUsername]: {
...state.user[state.appUsername],
...payload
}
}
}
: state : state
}, },
initialState initialState

View File

@ -22,6 +22,7 @@ const initialState = {
export const types = createTypes( export const types = createTypes(
[ [
...createAsyncTypes('validateUsername'), ...createAsyncTypes('validateUsername'),
...createAsyncTypes('submitNewAbout'),
...createAsyncTypes('submitNewUsername') ...createAsyncTypes('submitNewUsername')
], ],
ns ns
@ -29,6 +30,13 @@ export const types = createTypes(
export const sagas = [...createSettingsSagas(types)]; export const sagas = [...createSettingsSagas(types)];
export const submitNewAbout = createAction(types.submitNewAbout);
export const submitNewAboutComplete = createAction(
types.submitNewAboutComplete,
({ type, payload }) => (type === 'success' ? payload : null)
);
export const submitNewAboutError = createAction(types.submitNewAboutError);
export const submitNewUsername = createAction(types.submitNewUsername); export const submitNewUsername = createAction(types.submitNewUsername);
export const submitNewUsernameComplete = createAction( export const submitNewUsernameComplete = createAction(
types.submitNewUsernameComplete, types.submitNewUsernameComplete,

View File

@ -4,13 +4,38 @@ import { call, put, takeLatest } from 'redux-saga/effects';
import { import {
validateUsernameComplete, validateUsernameComplete,
validateUsernameError, validateUsernameError,
submitNewAboutComplete,
submitNewAboutError,
submitNewUsernameComplete, submitNewUsernameComplete,
submitNewUsernameError submitNewUsernameError
} from './'; } from './';
import {
import { getUsernameExists, putUpdateMyUsername } from '../../utils/ajax'; getUsernameExists,
putUpdateMyAbout,
putUpdateMyUsername
} from '../../utils/ajax';
import { createFlashMessage } from '../../components/Flash/redux'; import { createFlashMessage } from '../../components/Flash/redux';
function* submitNewAboutSaga({ payload }) {
try {
const { data: response } = yield call(putUpdateMyAbout, payload);
yield put(submitNewAboutComplete({ ...response, payload }));
yield put(createFlashMessage(response));
} catch (e) {
yield put(submitNewAboutError(e));
}
}
function* submitNewUsernameSaga({ 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));
}
}
function* validateUsernameSaga({ payload }) { function* validateUsernameSaga({ payload }) {
try { try {
yield delay(500); yield delay(500);
@ -23,19 +48,10 @@ function* validateUsernameSaga({ payload }) {
} }
} }
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) { export function createSettingsSagas(types) {
return [ return [
takeLatest(types.validateUsername, validateUsernameSaga), takeLatest(types.submitNewAbout, submitNewAboutSaga),
takeLatest(types.submitNewUsername, submitNEwUswernameSaga) takeLatest(types.submitNewUsername, submitNewUsernameSaga),
takeLatest(types.validateUsername, validateUsernameSaga)
]; ];
} }

View File

@ -40,6 +40,10 @@ export function postReportUser(body) {
/** PUT **/ /** PUT **/
export function putUpdateMyAbout(values) {
return put('/update-my-about', { ...values });
}
export function putUpdateMyUsername(username) { export function putUpdateMyUsername(username) {
return put('/update-my-username', { username }); return put('/update-my-username', { username });
} }