feat(about): Submit new 'about' settings
This commit is contained in:
@ -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) {
|
||||||
|
@ -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 = {
|
||||||
this.state = {
|
name,
|
||||||
view: 'edit',
|
location,
|
||||||
formValues: {
|
picture,
|
||||||
name,
|
about
|
||||||
location,
|
|
||||||
picture,
|
|
||||||
about
|
|
||||||
},
|
|
||||||
isFormPristine: true
|
|
||||||
};
|
};
|
||||||
|
this.state = {
|
||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
formValues: { ...values },
|
||||||
this.handleTabSelect = this.handleTabSelect.bind(this);
|
originalValues: { ...values },
|
||||||
this.renderEdit = this.renderEdit.bind(this);
|
formClicked: false
|
||||||
this.renderPreview = this.renderPreview.bind(this);
|
|
||||||
this.show = {
|
|
||||||
edit: this.renderEdit,
|
|
||||||
preview: this.renderPreview
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
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,106 +129,60 @@ 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;
|
||||||
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 { currentTheme, username } = this.props;
|
||||||
const { view, isFormPristine } = this.state;
|
|
||||||
|
|
||||||
const toggleTheme = () => {};
|
const toggleTheme = () => {};
|
||||||
return (
|
return (
|
||||||
<div className='about-settings'>
|
<div className='about-settings'>
|
||||||
<UsernameSettings username={username} />
|
<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 />
|
<br />
|
||||||
<FullWidthRow>
|
<FullWidthRow>
|
||||||
<form id='camper-identity' onSubmit={this.handleSubmit}>
|
<form id='camper-identity' onSubmit={this.handleSubmit}>
|
||||||
{this.show[view]()}
|
<FormGroup controlId='about-name'>
|
||||||
<BlockSaveButton disabled={isFormPristine} />
|
<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>
|
||||||
|
<BlockSaveButton disabled={this.isFormPristine()} />
|
||||||
</form>
|
</form>
|
||||||
</FullWidthRow>
|
</FullWidthRow>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
function CamperSettings() {
|
|
||||||
return (<h1>CamperSettings</h1>);
|
|
||||||
}
|
|
||||||
|
|
||||||
CamperSettings.displayName = 'CamperSettings';
|
|
||||||
|
|
||||||
export default CamperSettings;
|
|
@ -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
|
initialState
|
||||||
);
|
);
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -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 });
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user