feat(welcome): Gatsby the Welcome page

This commit is contained in:
Bouncey
2018-08-24 14:32:54 +01:00
committed by mrugesh mohapatra
parent 9f6a2e35f7
commit 3ea38ee31a
17 changed files with 228 additions and 133 deletions

View File

@ -8,6 +8,6 @@ const spawnOpts = {
};
// spawns loopback
spawn('babel-node', ['./server/server.js'], spawnOpts);
spawn('DEBUG=fcc* babel-node', ['./server/server.js'], spawnOpts);
// spawns gatsby in development mode
spawn('npm', ['run', 'develop'], spawnOpts);

View File

@ -1,74 +0,0 @@
import { defaultProfileImage } from '../../common/utils/constantStrings.json';
import { randomQuote } from '../../common/app/utils/get-words';
import { cachedMap } from '../utils/map';
// import NewsFeed from '../rss';
// const news = new NewsFeed();
module.exports = function(app, done) {
const { About } = app.models;
const router = app.loopback.Router();
let challengeCount = 0;
if (!process.env.SEEDING) {
cachedMap(app.models)
.do(({ entities: { challenge } }) => {
challengeCount = Object.keys(challenge).length;
})
.subscribe(
() => {},
err => {throw new Error(err);},
() => {
router.get('/', addDefaultImage, index);
app.use(router);
done();
}
);
}
function addDefaultImage(req, res, next) {
if (!req.user || req.user.picture) {
return next();
}
return req.user.update$({ picture: defaultProfileImage })
.subscribe(
() => next(),
next
);
}
function index(req, res) {
const { user } = req;
const homePage = user ? 'userHome' : 'noUserHome';
const { quote, author} = randomQuote();
const title = user ?
`Welcome, ${user.name ? user.name : 'Camper'}!` :
'Learn to Code and Help Nonprofits';
const completedChallengeCount = user && user.completedChallengeCount || 0;
const completedProjectCount = user && user.completedProjectCount || 0;
const completedCertCount = user && user.completedCertCount || 0;
const completedLegacyCertCount = user && user.completedLegacyCertCount || 0;
Promise.all([
// news.getFeed(),
About.getActiveUsersForRendering()
])
.then(([
// feed,
activeUsers
]) => {
return res.render(
homePage, {
activeUsers,
author,
challengeCount,
completedChallengeCount,
completedProjectCount,
completedCertCount,
completedLegacyCertCount,
// feed,
quote,
title
});
});
}
};

View File

@ -15,7 +15,7 @@ import {
ifNotVerifiedRedirectToUpdateEmail
} from '../utils/middleware';
const debug = debugFactory('fcc:boot:user');
const log = debugFactory('fcc:boot:user');
const sendNonUserToHome = ifNoUserRedirectTo('/');
const sendNonUserToHomeWithMessage = curry(ifNoUserRedirectTo, 2)('/');
@ -49,6 +49,7 @@ module.exports = function bootUser(app) {
function readSessionUser(req, res, next) {
const queryUser = req.user;
const source =
queryUser &&
Observable.forkJoin(
@ -146,7 +147,7 @@ function getUnlinkSocial(req, res, next) {
const updateData = { [social]: null };
return user.update$(updateData).subscribe(() => {
debug(`${social} has been unlinked successfully`);
log(`${social} has been unlinked successfully`);
req.flash('info', `You've successfully unlinked your ${social}.`);
return res.redirect('/' + username);

View File

@ -25,6 +25,7 @@ export default function userServices() {
config,
cb) {
const queryUser = req.user;
console.log(queryUser.completedChallengeCount)
const source = queryUser && Observable.forkJoin(
queryUser.getCompletedChallenges$(),
queryUser.getPoints$(),

View File

@ -46,7 +46,11 @@ export const userPropsForSession = [
'emailVerified',
'id',
'sendQuincyEmail',
'theme'
'theme',
'completedChallengeCount',
'completedProjectCount',
'completedCertCount',
'completedLegacyCertCount'
];
export function normaliseUserFields(user) {

View File

@ -1,7 +1,7 @@
.row.quote-partial
.col-xs-12.col-sm-10.col-sm-offset-1
blockquote.blockquote
span
span
q=quote
.spacer
footer.quote-author.blockquote-footer=author
footer.quote-author=author

View File

@ -1,13 +1,10 @@
import React, { Component } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import Spinner from 'react-spinkit';
import {
isSignedInSelector,
userFetchStateSelector
} from '../../../redux';
import { isSignedInSelector, userFetchStateSelector } from '../../../redux';
import Login from './Login';
import SignedIn from './SignedIn';
@ -28,26 +25,23 @@ const propTypes = {
showLoading: PropTypes.bool
};
class UserState extends Component {
render() {
console.log(this.props);
const { isSignedIn, showLoading, disableSettings } = this.props;
if (disableSettings) {
return <Login />;
}
if (showLoading) {
return (
<Spinner
className='user-state-spinner'
color='white'
fadeIn='none'
height='40px'
name='line-scale'
/>
);
}
return isSignedIn ? <SignedIn /> : <Login />;
function UserState(props) {
const { isSignedIn, showLoading, disableSettings } = props;
if (disableSettings) {
return <Login />;
}
if (showLoading) {
return (
<Spinner
className='user-state-spinner'
color='white'
fadeIn='none'
height='40px'
name='line-scale'
/>
);
}
return isSignedIn ? <SignedIn /> : <Login />;
}
UserState.displayName = 'UserState';

View File

@ -8,24 +8,19 @@
padding: 4px 12px;
}
.btn-cta-big {
max-height: 100%;
height: 70px;
font-size: 40px;
}
.signup-btn.btn {
background-color: #ffac33;
background-image: linear-gradient(#ffcc4d, #ffac33);
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#ffcc4d, endColorstr=#ffac33, GradientType=0)";
-ms-filter: 'progid:DXImageTransform.Microsoft.gradient(startColorstr=#ffcc4d, endColorstr=#ffac33, GradientType=0)';
border-color: #f1a02a;
color: #292f33 !important;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
}
.signup-btn:hover, .signup-btn:focus {
.signup-btn:hover,
.signup-btn:focus {
background-color: #e99110;
background-image: linear-gradient(#ffcc4d, #e99110);
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#ffcc4d, endColorstr=#e99110, GradientType=0)";
-ms-filter: 'progid:DXImageTransform.Microsoft.gradient(startColorstr=#ffcc4d, endColorstr=#e99110, GradientType=0)';
border-color: #ec8b11;
color: #292f33 !important;
}
@ -33,4 +28,4 @@
background-color: #f2a330;
background-image: none;
box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.3);
}
}

17
src/components/global.css Normal file
View File

@ -0,0 +1,17 @@
.btn-cta-big {
max-height: 100%;
height: 70px;
font-size: 40px;
}
.big-heading {
font-size: 50px !important;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 400;
}

View File

@ -622,12 +622,3 @@ pre tt:after {
font-size: 100%;
}
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 400;
}

View File

@ -2,14 +2,19 @@ import React, { Fragment, Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { createSelector } from 'reselect';
import Helmet from 'react-helmet';
import { StaticQuery, graphql } from 'gatsby';
import { fetchUser } from '../redux';
import { fetchUser, isSignedInSelector } from '../redux';
import Header from './Header';
import './layout.css';
const mapStateToProps = () => ({});
import './layout.css';
import './global.css';
const mapStateToProps = createSelector(isSignedInSelector, isSignedIn => ({
isSignedIn
}));
const mapDispatchToProps = dispatch =>
bindActionCreators({ fetchUser }, dispatch);
@ -19,7 +24,9 @@ class Layout extends Component {
}
componentDidMount() {
this.props.fetchUser();
if (!this.props.isSignedIn) {
this.props.fetchUser();
}
}
render() {
@ -56,7 +63,8 @@ class Layout extends Component {
Layout.propTypes = {
children: PropTypes.node.isRequired,
disableSettings: PropTypes.bool,
fetchUser: PropTypes.func.isRequired
fetchUser: PropTypes.func.isRequired,
isSignedIn: PropTypes.bool
};
export default connect(

View File

@ -3,10 +3,6 @@
font-weight: 400;
font-size: 40px;
}
.landing-heading {
font-size: 50px !important;
font-weight: 400;
}
.large-p {
font-size: 24px;

View File

@ -29,7 +29,7 @@ const IndexPage = () => (
<Layout disableSettings={true}>
<Grid className='text-center'>
<Row>
<h1 className='landing-heading'>Learn to code for free.</h1>
<h1 className='big-heading'>Learn to code for free.</h1>
<Spacer />
<Col md={4} sm={12}>
<Image

22
src/pages/welcome.css Normal file
View File

@ -0,0 +1,22 @@
.quote-partial .blockquote {
font-size: 2.5rem;
border: none;
}
.quote-author {
font-style: normal;
}
.update-link {
text-decoration: underline;
font-size: 1.4em;
color: #006400;
}
p.stats {
font-size: 1.45rem;
}
p.stats span {
color: #006400;
}

140
src/pages/welcome.js Normal file
View File

@ -0,0 +1,140 @@
import React from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { Grid, Row, Col, Button } from 'react-bootstrap';
import { Loader, Spacer } from '../components/helpers';
import Layout from '../components/layout';
import { userSelector, userFetchStateSelector } from '../redux';
import { randomQuote } from '../utils/get-words';
import './welcome.css';
const propTypes = {
fetchState: PropTypes.shape({
pending: PropTypes.bool,
complete: PropTypes.bool,
errored: PropTypes.bool
}),
user: PropTypes.shape({
username: PropTypes.string,
completedChallengeCount: PropTypes.number,
completedProjectCount: PropTypes.number,
completedCertCount: PropTypes.number,
completedLegacyCertCount: PropTypes.number
})
};
const mapStateToProps = createSelector(
userFetchStateSelector,
userSelector,
(fetchState, user) => ({ fetchState, user })
);
const mapDispatchToProps = dispatch => bindActionCreators({}, dispatch);
function Welcome({
fetchState: { pending, complete },
user: {
name,
completedChallengeCount,
completedProjectCount,
completedCertCount,
completedLegacyCertCount
}
}) {
if (pending && !complete) {
return (
<Layout>
<Loader />
</Layout>
);
}
const { quote, author } = randomQuote();
return (
<Layout>
<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 />
<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 ? (
<p className='stats'>
You have earned <span>{completedLegacyCertCount}</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 />
<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 />
<Spacer />
<Spacer />
<Spacer />
<Spacer />
</Grid>
</Layout>
);
}
Welcome.displayName = 'Welcome';
Welcome.propTypes = propTypes;
export default connect(
mapStateToProps,
mapDispatchToProps
)(Welcome);