From 7fd0b5b84bad198b22363a08e0a058c254bcbf8a Mon Sep 17 00:00:00 2001 From: Bouncey Date: Sat, 25 Aug 2018 00:24:19 +0100 Subject: [PATCH] feat(accept-pp-tos): Add Privacy and ToS accept page --- server/boot/authentication.js | 14 --- server/utils/publicUserProps.js | 3 +- src/pages/accept-privacy-terms.js | 172 ++++++++++++++++++++++++++++++ src/pages/signin.js | 0 src/pages/welcome.js | 18 +++- src/redux/accept-terms-saga.js | 24 +++++ src/redux/index.js | 15 ++- src/utils/ajax.js | 4 + static/_redirects | 9 ++ 9 files changed, 237 insertions(+), 22 deletions(-) create mode 100644 src/pages/accept-privacy-terms.js create mode 100644 src/pages/signin.js create mode 100644 src/redux/accept-terms-saga.js create mode 100644 static/_redirects diff --git a/server/boot/authentication.js b/server/boot/authentication.js index 2c43ce027b..9a354adb4a 100644 --- a/server/boot/authentication.js +++ b/server/boot/authentication.js @@ -74,20 +74,6 @@ module.exports = function enableAuthentication(app) { }); }); - router.get( - '/accept-privacy-terms', - ifNoUserRedirectHome, - (req, res) => { - const { user } = req; - if (user && !user.acceptedPrivacyTerms) { - return res.render('account/accept-privacy-terms', { - title: 'Privacy Policy and Terms of Service' - }); - } - return res.redirect('/settings'); - } - ); - const defaultErrorMsg = dedent` Oops, something is not right, please request a fresh link to sign in / sign up. diff --git a/server/utils/publicUserProps.js b/server/utils/publicUserProps.js index fdaca6b9e6..69ec276dab 100644 --- a/server/utils/publicUserProps.js +++ b/server/utils/publicUserProps.js @@ -50,7 +50,8 @@ export const userPropsForSession = [ 'completedChallengeCount', 'completedProjectCount', 'completedCertCount', - 'completedLegacyCertCount' + 'completedLegacyCertCount', + 'acceptedPrivacyTerms' ]; export function normaliseUserFields(user) { diff --git a/src/pages/accept-privacy-terms.js b/src/pages/accept-privacy-terms.js new file mode 100644 index 0000000000..738f77d528 --- /dev/null +++ b/src/pages/accept-privacy-terms.js @@ -0,0 +1,172 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import { + Grid, + Row, + Col, + Button, + FormGroup, + ControlLabel, + Checkbox +} from 'react-bootstrap'; +import Helmet from 'react-helmet'; + +import Layout from '../components/layout'; +import { ButtonSpacer, Spacer } from '../components/helpers'; +import { acceptTerms, isSignedInSelector, userSelector } from '../redux'; +import { createSelector } from 'reselect'; +import { navigateTo } from 'gatsby'; + +const propTypes = { + acceptTerms: PropTypes.func.isRequired, + acceptedPrivacyTerms: PropTypes.bool, + isSignedIn: PropTypes.bool +}; + +const mapStateToProps = createSelector( + isSignedInSelector, + userSelector, + (isSignedIn, { acceptedPrivacyTerms }) => ({ + isSignedIn, + acceptedPrivacyTerms + }) +); +const mapDispatchToProps = dispatch => + bindActionCreators({ acceptTerms }, dispatch); + +class AcceptPrivacyTerms extends Component { + constructor(props) { + super(props); + + this.state = { + privacyPolicy: false, + termsOfService: false, + quincyEmail: true + }; + this.createHandleChange = this.createHandleChange.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + } + + createHandleChange(prop) { + return () => + this.setState(prevState => ({ + [prop]: !prevState[prop] + })); + } + + handleSubmit(e) { + e.preventDefault(); + const { privacyPolicy, termsOfService, quincyEmail } = this.state; + if (!privacyPolicy || !termsOfService) { + return null; + } + return this.props.acceptTerms(quincyEmail); + } + + render() { + const { isSignedIn, acceptedPrivacyTerms } = this.props; + if (!isSignedIn || acceptedPrivacyTerms) { + navigateTo(isSignedIn ? '/welcome' : '/'); + return null; + } + const { privacyPolicy, termsOfService, quincyEmail } = this.state; + return ( + + + Privacy Policy and Terms of Service + + + + +
+ + +

+ Please review our updated privacy policy and the terms of + service. +

+ +
+ +
+ + +
+ + + Terms of Service + + + + I accept the{' '} + + terms of service + {' '} + (required) + + + + + Privacy Policy + + + + I accept the{' '} + + privacy policy + {' '} + (required) + + + + + Quincy's Emails + + + + I want weekly emails from Quincy, freeCodeCamp.org's + founder. + + + + + + +
+
+
+ ); + } +} + +AcceptPrivacyTerms.propTypes = propTypes; + +export default connect( + mapStateToProps, + mapDispatchToProps +)(AcceptPrivacyTerms); diff --git a/src/pages/signin.js b/src/pages/signin.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/pages/welcome.js b/src/pages/welcome.js index 9a1592f2b5..3744364b75 100644 --- a/src/pages/welcome.js +++ b/src/pages/welcome.js @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { navigateTo } from 'gatsby'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; @@ -19,6 +20,7 @@ const propTypes = { errored: PropTypes.bool }), user: PropTypes.shape({ + acceptedPrivacyTerms: PropTypes.bool, username: PropTypes.string, completedChallengeCount: PropTypes.number, completedProjectCount: PropTypes.number, @@ -37,11 +39,12 @@ const mapDispatchToProps = dispatch => bindActionCreators({}, dispatch); function Welcome({ fetchState: { pending, complete }, user: { - name, - completedChallengeCount, - completedProjectCount, - completedCertCount, - completedLegacyCertCount + acceptedPrivacyTerms, + name = '', + completedChallengeCount = 0, + completedProjectCount = 0, + completedCertCount = 0, + completedLegacyCertCount = 0 } }) { if (pending && !complete) { @@ -52,6 +55,11 @@ function Welcome({ ); } + if (!acceptedPrivacyTerms) { + navigateTo('/accept-privacy-terms'); + return null; + } + const { quote, author } = randomQuote(); return ( diff --git a/src/redux/accept-terms-saga.js b/src/redux/accept-terms-saga.js new file mode 100644 index 0000000000..30ae490e0d --- /dev/null +++ b/src/redux/accept-terms-saga.js @@ -0,0 +1,24 @@ +import { call, put, takeEvery } from 'redux-saga/effects'; + +import { acceptTermsComplete, acceptTermsError } from './'; + +import { putUserAcceptsTerms } from '../utils/ajax'; +import { createFlashMessage } from '../components/Flash/redux'; + +function* acceptTermsSaga({ payload: quincyEmails }) { + console.log('hello?'); + try { + const { + data: response + } = yield call(putUserAcceptsTerms, quincyEmails); + + yield put(acceptTermsComplete()); + yield put(createFlashMessage(response)); + } catch (e) { + yield put(acceptTermsError(e)); + } +} + +export function createAcceptTermsSaga(types) { + return [takeEvery(types.acceptTerms, acceptTermsSaga)]; +} diff --git a/src/redux/index.js b/src/redux/index.js index 7683562911..4d5c50fa99 100644 --- a/src/redux/index.js +++ b/src/redux/index.js @@ -2,6 +2,7 @@ import { createAction, handleActions } from 'redux-actions'; import { createTypes, createAsyncTypes } from '../utils/createTypes'; import { createFetchUserSaga } from './fetch-user-saga'; +import { createAcceptTermsSaga } from './accept-terms-saga'; const ns = 'app'; @@ -16,14 +17,24 @@ const initialState = { user: {} }; -const types = createTypes([...createAsyncTypes('fetchUser')], ns); +const types = createTypes( + [...createAsyncTypes('fetchUser'), ...createAsyncTypes('acceptTerms')], + ns +); -export const sagas = [...createFetchUserSaga(types)]; +export const sagas = [ + ...createFetchUserSaga(types), + ...createAcceptTermsSaga(types) +]; export const fetchUser = createAction(types.fetchUser); export const fetchUserComplete = createAction(types.fetchUserComplete); export const fetchUserError = createAction(types.fetchUserError); +export const acceptTerms = createAction(types.acceptTerms); +export const acceptTermsComplete = createAction(types.acceptTermsComplete); +export const acceptTermsError = createAction(types.acceptTermsError); + export const isSignedInSelector = state => !!Object.keys(state[ns].user).length; export const userFetchStateSelector = state => state[ns].fetchState; export const usernameSelector = state => state[ns].appUsername; diff --git a/src/utils/ajax.js b/src/utils/ajax.js index f461a4ae35..fc4592ee12 100644 --- a/src/utils/ajax.js +++ b/src/utils/ajax.js @@ -20,3 +20,7 @@ function sniff(things) { export function getSessionUser() { return get('/user/get-session-user').then(sniff); } + +export function putUserAcceptsTerms(quincyEmails) { + return put('/update-privacy-terms', {quincyEmails}) +} diff --git a/static/_redirects b/static/_redirects new file mode 100644 index 0000000000..0508902bf9 --- /dev/null +++ b/static/_redirects @@ -0,0 +1,9 @@ + +# signin redirects +/signup /signin 301 +/email-signin /signin 301 +/login /signin 301 +/deprecated-signin /signin 301 + +# signout redirects +/logout /signout 301 \ No newline at end of file