fix(auth): Fix auth flow for the client app

This commit is contained in:
Bouncey
2018-10-24 00:24:48 +01:00
committed by mrugesh mohapatra
parent a656cbf98a
commit c08bb95ea8
19 changed files with 348 additions and 212 deletions

View File

@@ -6,10 +6,12 @@ import { createSelector } from 'reselect';
import { Grid, Button } from '@freecodecamp/react-bootstrap';
import Helmet from 'react-helmet';
import { apiLocation } from '../../config/env.json';
import {
signInLoadingSelector,
userSelector,
isSignedInSelector
isSignedInSelector,
hardGoTo
} from '../redux';
import { submitNewAbout, updateUserFlag, verifyCert } from '../redux/settings';
import { createFlashMessage } from '../components/Flash/redux';
@@ -29,6 +31,7 @@ import RedirectHome from '../components/RedirectHome';
const propTypes = {
createFlashMessage: PropTypes.func.isRequired,
hardGoTo: PropTypes.func.isRequired,
isSignedIn: PropTypes.bool,
showLoading: PropTypes.bool,
submitNewAbout: PropTypes.func.isRequired,
@@ -101,6 +104,7 @@ const mapDispatchToProps = dispatch =>
bindActionCreators(
{
createFlashMessage,
hardGoTo,
submitNewAbout,
toggleNightMode: theme => updateUserFlag({ theme }),
updateInternetSettings: updateUserFlag,
@@ -112,9 +116,15 @@ const mapDispatchToProps = dispatch =>
dispatch
);
const createHandleSignoutClick = hardGoTo => e => {
e.preventDefault();
return hardGoTo(`${apiLocation}/signout`);
};
function ShowSettings(props) {
const {
createFlashMessage,
hardGoTo,
isSignedIn,
submitNewAbout,
toggleNightMode,
@@ -193,6 +203,7 @@ function ShowSettings(props) {
bsStyle='primary'
className='btn-invert'
href={'/signout'}
onClick={createHandleSignoutClick(hardGoTo)}
>
Sign me out of freeCodeCamp
</Button>

View File

@@ -1,13 +1,27 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Button } from '@freecodecamp/react-bootstrap';
import { Link } from 'gatsby';
import { hardGoTo } from '../../../redux';
import { apiLocation } from '../../../../config/env.json';
import './login.css';
function Login({ children, ...restProps }) {
const mapStateToProps = () => ({});
const mapDispatchToProps = dispatch => ({
navigate: location => dispatch(hardGoTo(location))
});
const createOnClick = navigate => e => {
e.preventDefault();
return navigate(`${apiLocation}/signin`);
};
function Login(props) {
const { children, navigate, ...restProps } = props;
return (
<Link to='/signin'>
<a href='/signin' onClick={createOnClick(navigate)}>
<Button
{...restProps}
bsStyle='default'
@@ -17,13 +31,17 @@ function Login({ children, ...restProps }) {
>
{children || 'Sign In'}
</Button>
</Link>
</a>
);
}
Login.displayName = 'Login';
Login.propTypes = {
children: PropTypes.any
children: PropTypes.any,
navigate: PropTypes.func.isRequired
};
export default Login;
export default connect(
mapStateToProps,
mapDispatchToProps
)(Login);

View File

@@ -12,9 +12,9 @@ function Header({ disableSettings }) {
return (
<header>
<nav id='top-nav'>
<a className='home-link' href='https://www.freecodecamp.org'>
<Link className='home-link' to='/'>
<NavLogo />
</a>
</Link>
{disableSettings ? null : <FCCSearch />}
<ul id='top-right-nav'>
<li>

View File

@@ -149,7 +149,7 @@ class DefaultLayout extends Component {
]}
/>
<Header disableSettings={disableSettings} />
<div className={landingPage && 'landing-page'}>
<div className={`default-layout ${landingPage ? 'landing-page' : ''}`}>
<OfflineWarning isOnline={isOnline} isSignedIn={isSignedIn} />
{hasMessages ? (
<Flash messages={flashMessages} onClose={removeFlashMessage} />

View File

@@ -622,6 +622,6 @@ pre tt:after {
font-size: 100%;
}
}
main {
.default-layout {
margin-top: 38px;
}

View File

@@ -33,7 +33,7 @@ class NotFoundPage extends React.Component {
<Layout>
<div className='notfound-page-wrapper'>
<Helmet title='Page Not Found | freeCodeCamp' />
<img alt='learn to code at freeCodeCamp 404' src={notFoundLogo} />
<img alt='404 Not Found' src={notFoundLogo} />
<h1>NOT FOUND</h1>
{this.state.randomQuote ? (
<div>

View File

@@ -9,7 +9,11 @@ import Helmet from 'react-helmet';
import { Loader, Spacer } from '../components/helpers';
import Layout from '../components/layouts/Default';
import { userSelector, userFetchStateSelector } from '../redux';
import {
userSelector,
userFetchStateSelector,
isSignedInSelector
} from '../redux';
import { randomQuote } from '../utils/get-words';
import './welcome.css';
@@ -20,6 +24,7 @@ const propTypes = {
complete: PropTypes.bool,
errored: PropTypes.bool
}),
isSignedIn: PropTypes.bool,
user: PropTypes.shape({
acceptedPrivacyTerms: PropTypes.bool,
username: PropTypes.string,
@@ -32,20 +37,22 @@ const propTypes = {
const mapStateToProps = createSelector(
userFetchStateSelector,
isSignedInSelector,
userSelector,
(fetchState, user) => ({ fetchState, user })
(fetchState, isSignedIn, user) => ({ fetchState, isSignedIn, user })
);
const mapDispatchToProps = dispatch => bindActionCreators({}, dispatch);
function Welcome({
fetchState: { pending, complete },
isSignedIn,
user: {
acceptedPrivacyTerms,
name = '',
completedChallengeCount = 0,
completedChallengeCount: completedChallenges = 0,
completedProjectCount = 0,
completedCertCount = 0,
completedLegacyCertCount = 0
completedLegacyCertCount: completedLegacyCerts = 0
}
}) {
if (pending && !complete) {
@@ -58,7 +65,12 @@ function Welcome({
);
}
if (!acceptedPrivacyTerms) {
if (!isSignedIn) {
navigate('/');
return null;
}
if (isSignedIn && !acceptedPrivacyTerms) {
navigate('/accept-privacy-terms');
return null;
}
@@ -70,72 +82,72 @@ function Welcome({
<title>Welcome {name ? name : 'Camper'} | freeCodeCamp.org</title>
</Helmet>
<main>
<Grid className='text-center'>
<Row>
<Col xs={12}>
<Spacer />
<h1 className='big-heading'>Welcome {name ? name : 'Camper'}!</h1>
</Col>
</Row>
<Spacer />
<Row>
<Col sm={8} smOffset={2} xs={12}>
<a
className='update-link'
href='/n/7gR5pBM-K?refsource=userhome'
target='_blank'
>
We're building a massive open dataset about new coders. Take the
2018 New Coder Survey. It only takes 5 minutes.
</a>
</Col>
</Row>
<Spacer />
<Row className='quote-partial'>
<Col sm={10} smOffset={1} xs={12}>
<blockquote className='blockquote'>
<span>
<q>{quote}</q>
<footer className='quote-author blockquote-footer'>
<cite>{author}</cite>
</footer>
</span>
</blockquote>
</Col>
</Row>
<Spacer />
<Row>
<Col sm={8} smOffset={2} xs={12}>
<p className='stats'>
You have completed <span>{completedChallengeCount}</span> of{' '}
<span>1408</span> coding challenges.
</p>
<p className='stats'>
You have built <span>{completedProjectCount}</span> of{' '}
<span>30</span> projects.
</p>
{completedLegacyCertCount ? (
<Grid className='text-center'>
<Row>
<Col xs={12}>
<Spacer />
<h1 className='big-heading'>Welcome {name ? name : 'Camper'}!</h1>
</Col>
</Row>
<Spacer />
<Row>
<Col sm={8} smOffset={2} xs={12}>
<a
className='update-link'
href='/n/7gR5pBM-K?refsource=userhome'
target='_blank'
>
We're building a massive open dataset about new coders. Take the
2018 New Coder Survey. It only takes 5 minutes.
</a>
</Col>
</Row>
<Spacer />
<Row className='quote-partial'>
<Col sm={10} smOffset={1} xs={12}>
<blockquote className='blockquote'>
<span>
<q>{quote}</q>
<footer className='quote-author blockquote-footer'>
<cite>{author}</cite>
</footer>
</span>
</blockquote>
</Col>
</Row>
<Spacer />
<Row>
<Col sm={8} smOffset={2} xs={12}>
<p className='stats'>
You have earned <span>{completedLegacyCertCount}</span> of{' '}
<span>4</span> legacy certifications.
You have completed <span>{completedChallenges}</span> of{' '}
<span>1408</span> coding challenges.
</p>
) : null}
<p className='stats'>
You have earned <span>{completedCertCount}</span> of{' '}
<span>6</span> certifications.
</p>
</Col>
</Row>
<Spacer />
<Row>
<Col sm={8} smOffset={2} xs={12}>
<Button block={true} bsStyle='primary' className='btn-cta-big'>
Go to my next challenge
</Button>
</Col>
</Row>
<Spacer size={4} />
</Grid>
<p className='stats'>
You have built <span>{completedProjectCount}</span> of{' '}
<span>30</span> projects.
</p>
{completedLegacyCerts ? (
<p className='stats'>
You have earned <span>{completedLegacyCerts}</span> of{' '}
<span>4</span> legacy certifications.
</p>
) : null}
<p className='stats'>
You have earned <span>{completedCertCount}</span> of{' '}
<span>6</span> certifications.
</p>
</Col>
</Row>
<Spacer />
<Row>
<Col sm={8} smOffset={2} xs={12}>
<Button block={true} bsStyle='primary' className='btn-cta-big'>
Go to my next challenge
</Button>
</Col>
</Row>
<Spacer size={4} />
</Grid>
</main>
</Layout>
);

View File

@@ -11,7 +11,12 @@ import { ofType } from 'redux-observable';
import store from 'store';
import uuid from 'uuid/v4';
import { types, onlineStatusChange, isOnlineSelector } from './';
import {
types,
onlineStatusChange,
isOnlineSelector,
isSignedInSelector
} from './';
import postUpdate$ from '../templates/Challenges/utils/postUpdate$';
import { isGoodXHRStatus } from '../templates/Challenges/utils';
@@ -36,6 +41,7 @@ function failedUpdateEpic(action$, state$) {
const flushUpdates = action$.pipe(
ofType(types.fetchUserComplete, types.updateComplete),
filter(() => isSignedInSelector(state$.value)),
filter(() => store.get(key)),
filter(() => isOnlineSelector(state$.value)),
tap(() => {

View File

@@ -2,8 +2,13 @@ import { call, put, takeEvery } from 'redux-saga/effects';
import { fetchUserComplete, fetchUserError } from './';
import { getSessionUser } from '../utils/ajax';
import { jwt } from './cookieValues';
function* fetchSessionUser() {
if (!jwt) {
yield put(fetchUserComplete({ user: {}, username: '' }));
return;
}
try {
const {
data: { user = {}, result = '' }

View File

@@ -47,6 +47,7 @@ export const types = createTypes(
[
'appMount',
'closeDonationModal',
'hardGoTo',
'openDonationModal',
'onlineStatusChange',
'updateComplete',
@@ -81,6 +82,11 @@ export const openDonationModal = createAction(types.openDonationModal);
export const onlineStatusChange = createAction(types.onlineStatusChange);
// `hardGoTo` is used to hit the API server directly
// without going through /internal
// used for things like /signin and /signout
export const hardGoTo = createAction(types.hardGoTo);
export const updateComplete = createAction(types.updateComplete);
export const updateFailed = createAction(types.updateFailed);