From c5c4f3aa414d30bc3a4265c27c6018923bc7b656 Mon Sep 17 00:00:00 2001 From: Bouncey Date: Fri, 7 Sep 2018 13:32:38 +0100 Subject: [PATCH] feat(report-user) Gatsby /user/:username/report-user --- api-server/server/boot/user.js | 63 +++----- client/src/client-only-routes/ShowUser.js | 166 ++++++++++++++++++++-- client/src/components/global.css | 4 + client/src/redux/index.js | 11 +- client/src/redux/report-user-saga.js | 29 ++++ client/src/utils/ajax.js | 10 +- 6 files changed, 222 insertions(+), 61 deletions(-) create mode 100644 client/src/redux/report-user-saga.js diff --git a/api-server/server/boot/user.js b/api-server/server/boot/user.js index 93cbaf29b8..d46ad6c470 100644 --- a/api-server/server/boot/user.js +++ b/api-server/server/boot/user.js @@ -3,24 +3,19 @@ import debugFactory from 'debug'; import { curry, pick } from 'lodash'; import { Observable } from 'rx'; +import { homeLocation } from '../../../config/env'; import { getProgress, normaliseUserFields, userPropsForSession } from '../utils/publicUserProps'; import { fixCompletedChallengeItem } from '../../common/utils'; -import { - ifNoUser401, - ifNoUserRedirectTo, - ifNotVerifiedRedirectToUpdateEmail -} from '../utils/middleware'; +import { ifNoUser401, ifNoUserRedirectTo } from '../utils/middleware'; const log = debugFactory('fcc:boot:user'); -const sendNonUserToHome = ifNoUserRedirectTo('/'); -const sendNonUserToHomeWithMessage = curry(ifNoUserRedirectTo, 2)('/'); +const sendNonUserToHome = ifNoUserRedirectTo(homeLocation); module.exports = function bootUser(app) { - const router = app.loopback.Router(); const api = app.loopback.Router(); api.get('/account', sendNonUserToHome, getAccount); @@ -29,21 +24,8 @@ module.exports = function bootUser(app) { api.post('/account/delete', ifNoUser401, createPostDeleteAccount(app)); api.post('/account/reset-progress', ifNoUser401, postResetProgress); - api.post( - '/user/:username/report-user/', - ifNoUser401, - createPostReportUserProfile(app) - ); + api.post('/user/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); }; @@ -86,14 +68,6 @@ function readSessionUser(req, res, 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) { const { username } = req.user; return res.redirect('/' + username); @@ -217,17 +191,19 @@ function createPostReportUserProfile(app) { const { Email } = app.models; return function postReportUserProfile(req, res, next) { const { user } = req; - const { username } = req.params; + const { username } = req.body; const report = req.sanitize('reportDescription').trimTags(); - if (!username || !report || report === '') { - req.flash( - 'danger', - 'Oops, something is not right please re-check your submission.' - ); - return next(); - } + log(username); + log(report); + if (!username || !report || report === '') { + return res.json({ + type: 'danger', + message: + 'Oops, something is not right please re-check your submission.' + }); + } return Email.send$( { type: 'email', @@ -250,15 +226,14 @@ function createPostReportUserProfile(app) { }, err => { if (err) { - err.redirectTo = '/' + username; + err.redirectTo = `${homeLocation}/${username}`; return next(err); } - req.flash( - 'info', - `A report was sent to the team with ${user.email} in copy.` - ); - return res.redirect('/'); + return res.json({ + typer: 'info', + message: `A report was sent to the team with ${user.email} in copy.` + }); } ); }; diff --git a/client/src/client-only-routes/ShowUser.js b/client/src/client-only-routes/ShowUser.js index 77770b55d3..da3dbde9d2 100644 --- a/client/src/client-only-routes/ShowUser.js +++ b/client/src/client-only-routes/ShowUser.js @@ -1,13 +1,32 @@ -import React from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import { bindActionCreators } from 'redux'; +import { Link, navigate } from 'gatsby'; import { connect } from 'react-redux'; 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 { Spacer } from '../components/helpers'; +import { Spacer, Loader, FullWidthRow } from '../components/helpers'; +import { runInThisContext } from 'vm'; const propTypes = { + email: PropTypes.string, isSignedIn: PropTypes.bool, + reportUser: PropTypes.func.isRequired, userFetchState: PropTypes.shape({ pending: PropTypes.bool, comnplete: PropTypes.bool, @@ -19,19 +38,142 @@ const propTypes = { const mapStateToProps = createSelector( isSignedInSelector, userFetchStateSelector, - (isSignedIn, userFetchState) => ({ isSignedIn, userFetchState }) + userSelector, + (isSignedIn, userFetchState, { email }) => ({ + isSignedIn, + userFetchState, + email + }) ); -function ShowUser() { - return ( - - -

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 ( + +
+ +
+
+ ); + } + + if ((complete || errored) && !isSignedIn) { + this.setNavigationTimer(); + return ( + + + + + + + + You need to be signed in to report a user + + + + +

+ You will be redirected to sign in to freeCodeCamp.org + automatically in 5 seconds +

+

+ + Or you can here if you do not want to wait + +

+ +
+
+
+
+ ); + } + + const { textarea } = this.state; + + return ( + + + + + +

+ Do you want to report {username} + 's profile for abuse? +

+

+ We will notify the community moderators' team, and a send copy of + this report to your email:{' '} + {email}. +

+

We may get back to you for more information, if required.

+
+ + Additional Information + + + +
+ +
+
+ ); + } } ShowUser.displayName = 'ShowUser'; ShowUser.propTypes = propTypes; -export default connect(mapStateToProps)(ShowUser); +export default connect( + mapStateToProps, + mapDispatchToProps +)(ShowUser); diff --git a/client/src/components/global.css b/client/src/components/global.css index 674c8546fc..21caf596cf 100644 --- a/client/src/components/global.css +++ b/client/src/components/global.css @@ -18,4 +18,8 @@ h6 { .text-center { text-align: center !important; +} + +.green-text { + color: #006400; } \ No newline at end of file diff --git a/client/src/redux/index.js b/client/src/redux/index.js index 675503b781..7f7b99a2b7 100644 --- a/client/src/redux/index.js +++ b/client/src/redux/index.js @@ -4,6 +4,7 @@ import { createTypes, createAsyncTypes } from '../utils/createTypes'; import { createFetchUserSaga } from './fetch-user-saga'; import { createAcceptTermsSaga } from './accept-terms-saga'; import { createAppMountSaga } from './app-mount-saga'; +import { createReportUserSaga } from './report-user-saga'; import { createShowCertSaga } from './show-cert-saga'; import { createUpdateMyEmailSaga } from './update-email-saga'; @@ -34,7 +35,8 @@ const types = createTypes( ...createAsyncTypes('fetchUser'), ...createAsyncTypes('acceptTerms'), ...createAsyncTypes('showCert'), - ...createAsyncTypes('updateMyEmail') + ...createAsyncTypes('updateMyEmail'), + ...createAsyncTypes('reportUser') ], ns ); @@ -44,7 +46,8 @@ export const sagas = [ ...createAppMountSaga(types), ...createFetchUserSaga(types), ...createUpdateMyEmailSaga(types), - ...createShowCertSaga(types) + ...createShowCertSaga(types), + ...createReportUserSaga(types) ]; export const appMount = createAction(types.appMount); @@ -57,6 +60,10 @@ export const fetchUser = createAction(types.fetchUser); export const fetchUserComplete = createAction(types.fetchUserComplete); 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 showCertComplete = createAction(types.showCertComplete); export const showCertError = createAction(types.showCertError); diff --git a/client/src/redux/report-user-saga.js b/client/src/redux/report-user-saga.js new file mode 100644 index 0000000000..407ac1488f --- /dev/null +++ b/client/src/redux/report-user-saga.js @@ -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) + ]; +} diff --git a/client/src/utils/ajax.js b/client/src/utils/ajax.js index 927df75cd9..3f9249b374 100644 --- a/client/src/utils/ajax.js +++ b/client/src/utils/ajax.js @@ -6,9 +6,9 @@ function get(path) { return axios.get(`${base}${path}`); } -// function post(path, body) { -// return axios.post(`${base}${path}`, body); -// } +function post(path, body) { + return axios.post(`${base}${path}`, body); +} function put(path, body) { return axios.put(`${base}${path}`, body); @@ -30,6 +30,10 @@ export function getShowCert(username, cert) { /** POST **/ +export function postReportUser(body) { + return post('/user/report-user', body); +} + /** PUT **/ export function putUserAcceptsTerms(quincyEmails) {