feat(accept-pp-tos): Add Privacy and ToS accept page
This commit is contained in:
committed by
mrugesh mohapatra
parent
4f2241d39f
commit
7fd0b5b84b
@ -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.
|
||||
|
@ -50,7 +50,8 @@ export const userPropsForSession = [
|
||||
'completedChallengeCount',
|
||||
'completedProjectCount',
|
||||
'completedCertCount',
|
||||
'completedLegacyCertCount'
|
||||
'completedLegacyCertCount',
|
||||
'acceptedPrivacyTerms'
|
||||
];
|
||||
|
||||
export function normaliseUserFields(user) {
|
||||
|
172
src/pages/accept-privacy-terms.js
Normal file
172
src/pages/accept-privacy-terms.js
Normal file
@ -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 (
|
||||
<Layout>
|
||||
<Helmet>
|
||||
<title>Privacy Policy and Terms of Service</title>
|
||||
</Helmet>
|
||||
<Grid>
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<div className='text-center'>
|
||||
<Spacer />
|
||||
<Spacer />
|
||||
<h3>
|
||||
Please review our updated privacy policy and the terms of
|
||||
service.
|
||||
</h3>
|
||||
<Spacer />
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col sm={6} smOffset={3}>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<FormGroup>
|
||||
<ControlLabel htmlFor='terms-of-service'>
|
||||
Terms of Service
|
||||
</ControlLabel>
|
||||
<Spacer />
|
||||
<Checkbox
|
||||
checked={termsOfService}
|
||||
id='terms-of-service'
|
||||
inline={true}
|
||||
onChange={this.createHandleChange('termsOfService')}
|
||||
>
|
||||
I accept the{' '}
|
||||
<a href='https://www.freecodecamp/terms' target='_blank'>
|
||||
terms of service
|
||||
</a>{' '}
|
||||
(required)
|
||||
</Checkbox>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<ControlLabel htmlFor='privacy-policy'>
|
||||
Privacy Policy
|
||||
</ControlLabel>
|
||||
<Spacer />
|
||||
<Checkbox
|
||||
checked={privacyPolicy}
|
||||
id='privacy-policy'
|
||||
inline={true}
|
||||
onChange={this.createHandleChange('privacyPolicy')}
|
||||
>
|
||||
I accept the{' '}
|
||||
<a href='https://www.freecodecamp/privacy' target='_blank'>
|
||||
privacy policy
|
||||
</a>{' '}
|
||||
(required)
|
||||
</Checkbox>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<ControlLabel htmlFor='quincy-email'>
|
||||
Quincy's Emails
|
||||
</ControlLabel>
|
||||
<Spacer />
|
||||
<Checkbox
|
||||
checked={quincyEmail}
|
||||
id='quincy-email'
|
||||
inline={true}
|
||||
onChange={this.createHandleChange('quincyEmail')}
|
||||
>
|
||||
I want weekly emails from Quincy, freeCodeCamp.org's
|
||||
founder.
|
||||
</Checkbox>
|
||||
</FormGroup>
|
||||
<ButtonSpacer />
|
||||
<Button
|
||||
block={true}
|
||||
bsStyle='primary'
|
||||
className='big-cta-btn'
|
||||
disabled={!privacyPolicy || !termsOfService}
|
||||
type='submit'
|
||||
>
|
||||
Continue to freeCodeCamp
|
||||
</Button>
|
||||
</form>
|
||||
</Col>
|
||||
</Row>
|
||||
</Grid>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AcceptPrivacyTerms.propTypes = propTypes;
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(AcceptPrivacyTerms);
|
0
src/pages/signin.js
Normal file
0
src/pages/signin.js
Normal file
@ -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 (
|
||||
<Layout>
|
||||
|
24
src/redux/accept-terms-saga.js
Normal file
24
src/redux/accept-terms-saga.js
Normal file
@ -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)];
|
||||
}
|
@ -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;
|
||||
|
@ -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})
|
||||
}
|
||||
|
9
static/_redirects
Normal file
9
static/_redirects
Normal file
@ -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
|
Reference in New Issue
Block a user