diff --git a/api-server/server/boot/settings.js b/api-server/server/boot/settings.js index 3f0c25a431..b2911bedef 100644 --- a/api-server/server/boot/settings.js +++ b/api-server/server/boot/settings.js @@ -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) { diff --git a/client/src/components/settings/About.js b/client/src/components/settings/About.js index 21d3e4564b..ad7d6c07ab 100644 --- a/client/src/components/settings/About.js +++ b/client/src/components/settings/About.js @@ -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; - - this.state = { - view: 'edit', - formValues: { - name, - location, - picture, - about - }, - isFormPristine: true + const values = { + name, + location, + picture, + about }; - - 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 + this.state = { + formValues: { ...values }, + originalValues: { ...values }, + formClicked: false }; } - handleSubmit(e) { + 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 + }, + formClicked: false + }); + } + return null; + } + + 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,106 +129,60 @@ class AboutSettings extends Component { })); }; - handleTabSelect(key) { - return this.setState(state => ({ - ...state, - view: key - })); - } - - renderEdit() { + render() { const { formValues: { name, location, picture, about } } = this.state; - return ( - - - - Name - - - - - - Location - - - - - - Picture - - - - - - About - - - - - ); - } - - renderPreview() { - const { about, picture, points, username, name, location } = this.props; - return ( - - ); - } - - render() { const { currentTheme, username } = this.props; - const { view, isFormPristine } = this.state; - const toggleTheme = () => {}; return (
- - -
- {this.show[view]()} - + + + Name + + + + + + Location + + + + + + Picture + + + + + + About + + + +
diff --git a/client/src/components/settings/Camper.js b/client/src/components/settings/Camper.js deleted file mode 100644 index 536e4166c3..0000000000 --- a/client/src/components/settings/Camper.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; - -function CamperSettings() { - return (

CamperSettings

); -} - -CamperSettings.displayName = 'CamperSettings'; - -export default CamperSettings; diff --git a/client/src/redux/index.js b/client/src/redux/index.js index 3fc20bd74e..4e26ca7895 100644 --- a/client/src/redux/index.js +++ b/client/src/redux/index.js @@ -160,7 +160,18 @@ export const reducer = handleActions( } } } - : state + : state, + [settingsTypes.submitNewAboutComplete]: (state, {payload}) => payload ? { + ...state, + user: { + ...state.user, + [state.appUsername]: { + ...state.user[state.appUsername], + ...payload + } + } + } + : state }, initialState ); diff --git a/client/src/redux/settings/index.js b/client/src/redux/settings/index.js index be03c2bd85..69b50f8e1c 100644 --- a/client/src/redux/settings/index.js +++ b/client/src/redux/settings/index.js @@ -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, diff --git a/client/src/redux/settings/settings-sagas.js b/client/src/redux/settings/settings-sagas.js index d61d83560b..6488a92ce7 100644 --- a/client/src/redux/settings/settings-sagas.js +++ b/client/src/redux/settings/settings-sagas.js @@ -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) ]; } diff --git a/client/src/utils/ajax.js b/client/src/utils/ajax.js index b7091a1427..b12a3121d5 100644 --- a/client/src/utils/ajax.js +++ b/client/src/utils/ajax.js @@ -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 }); }