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 { ifNoUser401, createValidatorErrorHandler } from '../utils/middleware';
import { themes } from '../../common/utils/themes.js';
import { alertTypes } from '../../common/utils/flash.js';
const log = debug('fcc:boot:settings');
export default function settingsController(app) {
const api = app.loopback.Router();
@ -47,6 +51,7 @@ export default function settingsController(app) {
createValidatorErrorHandler(alertTypes.danger),
updateMyTheme
);
api.put('/update-my-about', ifNoUser401, updateMyAbout);
api.put('/update-my-username', ifNoUser401, updateMyUsername);
app.use('/internal', api);
@ -59,6 +64,11 @@ const standardErrorMessage = {
'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 { user } = req;
const currentValue = user[flag];
@ -180,6 +190,21 @@ function updateMyProjects(req, res, 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) {
const { User } = app.models;
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 { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import {
Nav,
NavItem,
FormGroup,
ControlLabel,
FormControl
} from '@freecodecamp/react-bootstrap';
import { submitNewAbout } from '../../redux/settings';
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 mapDispatchToProps = dispatch =>
bindActionCreators({ submitNewAbout }, dispatch);
const propTypes = {
about: PropTypes.string,
@ -28,6 +27,7 @@ const propTypes = {
name: PropTypes.string,
picture: PropTypes.string,
points: PropTypes.number,
submitNewAbout: PropTypes.func.isRequired,
username: PropTypes.string
};
@ -36,33 +36,58 @@ class AboutSettings extends Component {
super(props);
const { name = '', location = '', picture = '', about = '' } = props;
const values = {
name,
location,
picture,
about
};
this.state = {
view: 'edit',
formValues: {
formValues: { ...values },
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,
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
};
formClicked: false
});
}
return null;
}
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();
const { formValues } = this.state;
console.log(formValues)
}
const { submitNewAbout } = this.props;
return this.setState({ formClicked: true }, () =>
submitNewAbout(formValues)
);
};
handleNameChange = e => {
const value = e.target.value.slice(0);
@ -104,19 +129,18 @@ class AboutSettings extends Component {
}));
};
handleTabSelect(key) {
return this.setState(state => ({
...state,
view: key
}));
}
renderEdit() {
render() {
const {
formValues: { name, location, picture, about }
} = this.state;
const { currentTheme, username } = this.props;
const toggleTheme = () => {};
return (
<Fragment>
<div className='about-settings'>
<UsernameSettings username={username} />
<br />
<FullWidthRow>
<form id='camper-identity' onSubmit={this.handleSubmit}>
<FormGroup controlId='about-name'>
<ControlLabel>
<strong>Name</strong>
@ -158,52 +182,7 @@ class AboutSettings extends Component {
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} />
<BlockSaveButton disabled={this.isFormPristine()} />
</form>
</FullWidthRow>
<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
},
initialState

View File

@ -22,6 +22,7 @@ const initialState = {
export const types = createTypes(
[
...createAsyncTypes('validateUsername'),
...createAsyncTypes('submitNewAbout'),
...createAsyncTypes('submitNewUsername')
],
ns
@ -29,6 +30,13 @@ export const types = createTypes(
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 submitNewUsernameComplete = createAction(
types.submitNewUsernameComplete,

View File

@ -4,13 +4,38 @@ import { call, put, takeLatest } from 'redux-saga/effects';
import {
validateUsernameComplete,
validateUsernameError,
submitNewAboutComplete,
submitNewAboutError,
submitNewUsernameComplete,
submitNewUsernameError
} from './';
import { getUsernameExists, putUpdateMyUsername } from '../../utils/ajax';
import {
getUsernameExists,
putUpdateMyAbout,
putUpdateMyUsername
} from '../../utils/ajax';
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 }) {
try {
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) {
return [
takeLatest(types.validateUsername, validateUsernameSaga),
takeLatest(types.submitNewUsername, submitNEwUswernameSaga)
takeLatest(types.submitNewAbout, submitNewAboutSaga),
takeLatest(types.submitNewUsername, submitNewUsernameSaga),
takeLatest(types.validateUsername, validateUsernameSaga)
];
}

View File

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