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`
|
const defaultErrorMsg = dedent`
|
||||||
Oops, something is not right,
|
Oops, something is not right,
|
||||||
please request a fresh link to sign in / sign up.
|
please request a fresh link to sign in / sign up.
|
||||||
|
@ -50,7 +50,8 @@ export const userPropsForSession = [
|
|||||||
'completedChallengeCount',
|
'completedChallengeCount',
|
||||||
'completedProjectCount',
|
'completedProjectCount',
|
||||||
'completedCertCount',
|
'completedCertCount',
|
||||||
'completedLegacyCertCount'
|
'completedLegacyCertCount',
|
||||||
|
'acceptedPrivacyTerms'
|
||||||
];
|
];
|
||||||
|
|
||||||
export function normaliseUserFields(user) {
|
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 React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { navigateTo } from 'gatsby';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
@ -19,6 +20,7 @@ const propTypes = {
|
|||||||
errored: PropTypes.bool
|
errored: PropTypes.bool
|
||||||
}),
|
}),
|
||||||
user: PropTypes.shape({
|
user: PropTypes.shape({
|
||||||
|
acceptedPrivacyTerms: PropTypes.bool,
|
||||||
username: PropTypes.string,
|
username: PropTypes.string,
|
||||||
completedChallengeCount: PropTypes.number,
|
completedChallengeCount: PropTypes.number,
|
||||||
completedProjectCount: PropTypes.number,
|
completedProjectCount: PropTypes.number,
|
||||||
@ -37,11 +39,12 @@ const mapDispatchToProps = dispatch => bindActionCreators({}, dispatch);
|
|||||||
function Welcome({
|
function Welcome({
|
||||||
fetchState: { pending, complete },
|
fetchState: { pending, complete },
|
||||||
user: {
|
user: {
|
||||||
name,
|
acceptedPrivacyTerms,
|
||||||
completedChallengeCount,
|
name = '',
|
||||||
completedProjectCount,
|
completedChallengeCount = 0,
|
||||||
completedCertCount,
|
completedProjectCount = 0,
|
||||||
completedLegacyCertCount
|
completedCertCount = 0,
|
||||||
|
completedLegacyCertCount = 0
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
if (pending && !complete) {
|
if (pending && !complete) {
|
||||||
@ -52,6 +55,11 @@ function Welcome({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!acceptedPrivacyTerms) {
|
||||||
|
navigateTo('/accept-privacy-terms');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { quote, author } = randomQuote();
|
const { quote, author } = randomQuote();
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<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 { createTypes, createAsyncTypes } from '../utils/createTypes';
|
||||||
import { createFetchUserSaga } from './fetch-user-saga';
|
import { createFetchUserSaga } from './fetch-user-saga';
|
||||||
|
import { createAcceptTermsSaga } from './accept-terms-saga';
|
||||||
|
|
||||||
const ns = 'app';
|
const ns = 'app';
|
||||||
|
|
||||||
@ -16,14 +17,24 @@ const initialState = {
|
|||||||
user: {}
|
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 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 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 isSignedInSelector = state => !!Object.keys(state[ns].user).length;
|
||||||
export const userFetchStateSelector = state => state[ns].fetchState;
|
export const userFetchStateSelector = state => state[ns].fetchState;
|
||||||
export const usernameSelector = state => state[ns].appUsername;
|
export const usernameSelector = state => state[ns].appUsername;
|
||||||
|
@ -20,3 +20,7 @@ function sniff(things) {
|
|||||||
export function getSessionUser() {
|
export function getSessionUser() {
|
||||||
return get('/user/get-session-user').then(sniff);
|
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