feat(report-user) Gatsby /user/:username/report-user

This commit is contained in:
Bouncey
2018-09-07 13:32:38 +01:00
committed by Stuart Taylor
parent a9c948679e
commit c5c4f3aa41
6 changed files with 222 additions and 61 deletions

View File

@ -3,24 +3,19 @@ import debugFactory from 'debug';
import { curry, pick } from 'lodash'; import { curry, pick } from 'lodash';
import { Observable } from 'rx'; import { Observable } from 'rx';
import { homeLocation } from '../../../config/env';
import { import {
getProgress, getProgress,
normaliseUserFields, normaliseUserFields,
userPropsForSession userPropsForSession
} from '../utils/publicUserProps'; } from '../utils/publicUserProps';
import { fixCompletedChallengeItem } from '../../common/utils'; import { fixCompletedChallengeItem } from '../../common/utils';
import { import { ifNoUser401, ifNoUserRedirectTo } from '../utils/middleware';
ifNoUser401,
ifNoUserRedirectTo,
ifNotVerifiedRedirectToUpdateEmail
} from '../utils/middleware';
const log = debugFactory('fcc:boot:user'); const log = debugFactory('fcc:boot:user');
const sendNonUserToHome = ifNoUserRedirectTo('/'); const sendNonUserToHome = ifNoUserRedirectTo(homeLocation);
const sendNonUserToHomeWithMessage = curry(ifNoUserRedirectTo, 2)('/');
module.exports = function bootUser(app) { module.exports = function bootUser(app) {
const router = app.loopback.Router();
const api = app.loopback.Router(); const api = app.loopback.Router();
api.get('/account', sendNonUserToHome, getAccount); api.get('/account', sendNonUserToHome, getAccount);
@ -29,21 +24,8 @@ module.exports = function bootUser(app) {
api.post('/account/delete', ifNoUser401, createPostDeleteAccount(app)); api.post('/account/delete', ifNoUser401, createPostDeleteAccount(app));
api.post('/account/reset-progress', ifNoUser401, postResetProgress); api.post('/account/reset-progress', ifNoUser401, postResetProgress);
api.post( api.post('/user/report-user/', ifNoUser401, createPostReportUserProfile(app));
'/user/:username/report-user/',
ifNoUser401,
createPostReportUserProfile(app)
);
router.get(
'/user/:username/report-user/',
sendNonUserToHomeWithMessage('You must be signed in to report a user'),
ifNotVerifiedRedirectToUpdateEmail,
getReportUserProfile
);
app.use(router);
app.use('/external', api);
app.use('/internal', api); app.use('/internal', api);
}; };
@ -86,14 +68,6 @@ function readSessionUser(req, res, next) {
).subscribe(user => res.json(user), next); ).subscribe(user => res.json(user), next);
} }
function getReportUserProfile(req, res) {
const username = req.params.username.toLowerCase();
return res.render('account/report-profile', {
title: 'Report User',
username
});
}
function getAccount(req, res) { function getAccount(req, res) {
const { username } = req.user; const { username } = req.user;
return res.redirect('/' + username); return res.redirect('/' + username);
@ -217,17 +191,19 @@ function createPostReportUserProfile(app) {
const { Email } = app.models; const { Email } = app.models;
return function postReportUserProfile(req, res, next) { return function postReportUserProfile(req, res, next) {
const { user } = req; const { user } = req;
const { username } = req.params; const { username } = req.body;
const report = req.sanitize('reportDescription').trimTags(); const report = req.sanitize('reportDescription').trimTags();
if (!username || !report || report === '') { log(username);
req.flash( log(report);
'danger',
'Oops, something is not right please re-check your submission.'
);
return next();
}
if (!username || !report || report === '') {
return res.json({
type: 'danger',
message:
'Oops, something is not right please re-check your submission.'
});
}
return Email.send$( return Email.send$(
{ {
type: 'email', type: 'email',
@ -250,15 +226,14 @@ function createPostReportUserProfile(app) {
}, },
err => { err => {
if (err) { if (err) {
err.redirectTo = '/' + username; err.redirectTo = `${homeLocation}/${username}`;
return next(err); return next(err);
} }
req.flash( return res.json({
'info', typer: 'info',
`A report was sent to the team with ${user.email} in copy.` message: `A report was sent to the team with ${user.email} in copy.`
); });
return res.redirect('/');
} }
); );
}; };

View File

@ -1,13 +1,32 @@
import React from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { Link, navigate } from 'gatsby';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { isSignedInSelector, userFetchStateSelector } from '../redux'; import {
Panel,
FormControl,
FormGroup,
ControlLabel,
Button,
Col
} from '@freecodecamp/react-bootstrap';
import {
isSignedInSelector,
userFetchStateSelector,
userSelector,
reportUser
} from '../redux';
import Layout from '../components/Layout'; import Layout from '../components/Layout';
import { Spacer } from '../components/helpers'; import { Spacer, Loader, FullWidthRow } from '../components/helpers';
import { runInThisContext } from 'vm';
const propTypes = { const propTypes = {
email: PropTypes.string,
isSignedIn: PropTypes.bool, isSignedIn: PropTypes.bool,
reportUser: PropTypes.func.isRequired,
userFetchState: PropTypes.shape({ userFetchState: PropTypes.shape({
pending: PropTypes.bool, pending: PropTypes.bool,
comnplete: PropTypes.bool, comnplete: PropTypes.bool,
@ -19,19 +38,142 @@ const propTypes = {
const mapStateToProps = createSelector( const mapStateToProps = createSelector(
isSignedInSelector, isSignedInSelector,
userFetchStateSelector, userFetchStateSelector,
(isSignedIn, userFetchState) => ({ isSignedIn, userFetchState }) userSelector,
(isSignedIn, userFetchState, { email }) => ({
isSignedIn,
userFetchState,
email
})
); );
function ShowUser() { const mapDispatchToProps = dispatch =>
bindActionCreators({ reportUser }, dispatch);
class ShowUser extends Component {
constructor(props) {
super(props);
this.timer = null;
this.state = {
textarea: ''
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
componentWillUnmount() {
if (this.timer) {
clearTimeout(this.timer);
}
}
handleChange(e) {
const textarea = e.target.value.slice();
return this.setState({
textarea
});
}
handleSubmit(e) {
e.preventDefault();
const { textarea: reportDescription } = this.state;
const { username, reportUser } = this.props;
return reportUser({ username, reportDescription });
}
setNavigationTimer() {
if (!this.timer) {
this.timer = setTimeout(() => navigate('/signin'), 5000);
}
}
render() {
const { username, isSignedIn, userFetchState, email } = this.props;
const { pending, complete, errored } = userFetchState;
if (pending && !complete) {
return ( return (
<Layout> <Layout>
<Spacer /> <div className='loader-wrapper'>
<h1>ShowUser</h1> <Loader />
</div>
</Layout> </Layout>
); );
} }
if ((complete || errored) && !isSignedIn) {
this.setNavigationTimer();
return (
<Layout>
<FullWidthRow>
<Spacer />
<Spacer />
<Panel bsStyle='info'>
<Panel.Heading>
<Panel.Title componentClass='h3'>
You need to be signed in to report a user
</Panel.Title>
</Panel.Heading>
<Panel.Body className='text-center'>
<Spacer />
<p>
You will be redirected to sign in to freeCodeCamp.org
automatically in 5 seconds
</p>
<p>
<Link to='/signin'>
Or you can here if you do not want to wait
</Link>
</p>
<Spacer />
</Panel.Body>
</Panel>
</FullWidthRow>
</Layout>
);
}
const { textarea } = this.state;
return (
<Layout>
<FullWidthRow>
<Spacer />
<Spacer />
<Col md={8} mdOffset={2}>
<h2>
Do you want to report {username}
's profile for abuse?
</h2>
<p>
We will notify the community moderators' team, and a send copy of
this report to your email:{' '}
<span className='green-text'>{email}</span>.
</p>
<p>We may get back to you for more information, if required.</p>
<form onSubmit={this.handleSubmit}>
<FormGroup controlId='report-user-textarea'>
<ControlLabel>Additional Information</ControlLabel>
<FormControl
componentClass='textarea'
onChange={this.handleChange}
placeholder=''
value={textarea}
/>
</FormGroup>
<Button block={true} bsStyle='primary' type='submit'>
Sumbit the report
</Button>
</form>
</Col>
</FullWidthRow>
</Layout>
);
}
}
ShowUser.displayName = 'ShowUser'; ShowUser.displayName = 'ShowUser';
ShowUser.propTypes = propTypes; ShowUser.propTypes = propTypes;
export default connect(mapStateToProps)(ShowUser); export default connect(
mapStateToProps,
mapDispatchToProps
)(ShowUser);

View File

@ -19,3 +19,7 @@ h6 {
.text-center { .text-center {
text-align: center !important; text-align: center !important;
} }
.green-text {
color: #006400;
}

View File

@ -4,6 +4,7 @@ import { createTypes, createAsyncTypes } from '../utils/createTypes';
import { createFetchUserSaga } from './fetch-user-saga'; import { createFetchUserSaga } from './fetch-user-saga';
import { createAcceptTermsSaga } from './accept-terms-saga'; import { createAcceptTermsSaga } from './accept-terms-saga';
import { createAppMountSaga } from './app-mount-saga'; import { createAppMountSaga } from './app-mount-saga';
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';
@ -34,7 +35,8 @@ const types = createTypes(
...createAsyncTypes('fetchUser'), ...createAsyncTypes('fetchUser'),
...createAsyncTypes('acceptTerms'), ...createAsyncTypes('acceptTerms'),
...createAsyncTypes('showCert'), ...createAsyncTypes('showCert'),
...createAsyncTypes('updateMyEmail') ...createAsyncTypes('updateMyEmail'),
...createAsyncTypes('reportUser')
], ],
ns ns
); );
@ -44,7 +46,8 @@ export const sagas = [
...createAppMountSaga(types), ...createAppMountSaga(types),
...createFetchUserSaga(types), ...createFetchUserSaga(types),
...createUpdateMyEmailSaga(types), ...createUpdateMyEmailSaga(types),
...createShowCertSaga(types) ...createShowCertSaga(types),
...createReportUserSaga(types)
]; ];
export const appMount = createAction(types.appMount); export const appMount = createAction(types.appMount);
@ -57,6 +60,10 @@ export const fetchUser = createAction(types.fetchUser);
export const fetchUserComplete = createAction(types.fetchUserComplete); export const fetchUserComplete = createAction(types.fetchUserComplete);
export const fetchUserError = createAction(types.fetchUserError); export const fetchUserError = createAction(types.fetchUserError);
export const reportUser = createAction(types.reportUser);
export const reportUserComplete = createAction(types.reportUserComplete);
export const reportUserError = createAction(types.reportUserError);
export const showCert = createAction(types.showCert); export const showCert = createAction(types.showCert);
export const showCertComplete = createAction(types.showCertComplete); export const showCertComplete = createAction(types.showCertComplete);
export const showCertError = createAction(types.showCertError); export const showCertError = createAction(types.showCertError);

View File

@ -0,0 +1,29 @@
import { call, put, takeEvery } from 'redux-saga/effects';
import { navigate } from 'gatsby';
import { reportUserComplete, reportUserError } from './';
import { createFlashMessage } from '../components/Flash/redux';
import { postReportUser } from '../utils/ajax';
function* reportUserSaga({ payload }) {
try {
const { data: response } = yield call(postReportUser, payload);
yield put(reportUserComplete());
yield put(createFlashMessage(response));
} catch (e) {
yield put(reportUserError(e));
}
}
function* acceptCompleteSaga() {
yield call(navigate, '/');
}
export function createReportUserSaga(types) {
return [
takeEvery(types.reportUser, reportUserSaga),
takeEvery(types.reportUserComplete, acceptCompleteSaga)
];
}

View File

@ -6,9 +6,9 @@ function get(path) {
return axios.get(`${base}${path}`); return axios.get(`${base}${path}`);
} }
// function post(path, body) { function post(path, body) {
// return axios.post(`${base}${path}`, body); return axios.post(`${base}${path}`, body);
// } }
function put(path, body) { function put(path, body) {
return axios.put(`${base}${path}`, body); return axios.put(`${base}${path}`, body);
@ -30,6 +30,10 @@ export function getShowCert(username, cert) {
/** POST **/ /** POST **/
export function postReportUser(body) {
return post('/user/report-user', body);
}
/** PUT **/ /** PUT **/
export function putUserAcceptsTerms(quincyEmails) { export function putUserAcceptsTerms(quincyEmails) {