feat(welcome): Gatsby the Welcome page
This commit is contained in:
committed by
mrugesh mohapatra
parent
9f6a2e35f7
commit
3ea38ee31a
@ -8,6 +8,6 @@ const spawnOpts = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// spawns loopback
|
// spawns loopback
|
||||||
spawn('babel-node', ['./server/server.js'], spawnOpts);
|
spawn('DEBUG=fcc* babel-node', ['./server/server.js'], spawnOpts);
|
||||||
// spawns gatsby in development mode
|
// spawns gatsby in development mode
|
||||||
spawn('npm', ['run', 'develop'], spawnOpts);
|
spawn('npm', ['run', 'develop'], spawnOpts);
|
||||||
|
@ -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
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
@ -15,7 +15,7 @@ import {
|
|||||||
ifNotVerifiedRedirectToUpdateEmail
|
ifNotVerifiedRedirectToUpdateEmail
|
||||||
} from '../utils/middleware';
|
} from '../utils/middleware';
|
||||||
|
|
||||||
const debug = debugFactory('fcc:boot:user');
|
const log = debugFactory('fcc:boot:user');
|
||||||
const sendNonUserToHome = ifNoUserRedirectTo('/');
|
const sendNonUserToHome = ifNoUserRedirectTo('/');
|
||||||
const sendNonUserToHomeWithMessage = curry(ifNoUserRedirectTo, 2)('/');
|
const sendNonUserToHomeWithMessage = curry(ifNoUserRedirectTo, 2)('/');
|
||||||
|
|
||||||
@ -49,6 +49,7 @@ module.exports = function bootUser(app) {
|
|||||||
|
|
||||||
function readSessionUser(req, res, next) {
|
function readSessionUser(req, res, next) {
|
||||||
const queryUser = req.user;
|
const queryUser = req.user;
|
||||||
|
|
||||||
const source =
|
const source =
|
||||||
queryUser &&
|
queryUser &&
|
||||||
Observable.forkJoin(
|
Observable.forkJoin(
|
||||||
@ -146,7 +147,7 @@ function getUnlinkSocial(req, res, next) {
|
|||||||
const updateData = { [social]: null };
|
const updateData = { [social]: null };
|
||||||
|
|
||||||
return user.update$(updateData).subscribe(() => {
|
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}.`);
|
req.flash('info', `You've successfully unlinked your ${social}.`);
|
||||||
return res.redirect('/' + username);
|
return res.redirect('/' + username);
|
||||||
|
@ -25,6 +25,7 @@ export default function userServices() {
|
|||||||
config,
|
config,
|
||||||
cb) {
|
cb) {
|
||||||
const queryUser = req.user;
|
const queryUser = req.user;
|
||||||
|
console.log(queryUser.completedChallengeCount)
|
||||||
const source = queryUser && Observable.forkJoin(
|
const source = queryUser && Observable.forkJoin(
|
||||||
queryUser.getCompletedChallenges$(),
|
queryUser.getCompletedChallenges$(),
|
||||||
queryUser.getPoints$(),
|
queryUser.getPoints$(),
|
||||||
|
@ -46,7 +46,11 @@ export const userPropsForSession = [
|
|||||||
'emailVerified',
|
'emailVerified',
|
||||||
'id',
|
'id',
|
||||||
'sendQuincyEmail',
|
'sendQuincyEmail',
|
||||||
'theme'
|
'theme',
|
||||||
|
'completedChallengeCount',
|
||||||
|
'completedProjectCount',
|
||||||
|
'completedCertCount',
|
||||||
|
'completedLegacyCertCount'
|
||||||
];
|
];
|
||||||
|
|
||||||
export function normaliseUserFields(user) {
|
export function normaliseUserFields(user) {
|
||||||
|
@ -4,4 +4,4 @@
|
|||||||
span
|
span
|
||||||
q=quote
|
q=quote
|
||||||
.spacer
|
.spacer
|
||||||
footer.quote-author.blockquote-footer=author
|
footer.quote-author=author
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import Spinner from 'react-spinkit';
|
import Spinner from 'react-spinkit';
|
||||||
|
|
||||||
import {
|
import { isSignedInSelector, userFetchStateSelector } from '../../../redux';
|
||||||
isSignedInSelector,
|
|
||||||
userFetchStateSelector
|
|
||||||
} from '../../../redux';
|
|
||||||
import Login from './Login';
|
import Login from './Login';
|
||||||
import SignedIn from './SignedIn';
|
import SignedIn from './SignedIn';
|
||||||
|
|
||||||
@ -28,10 +25,8 @@ const propTypes = {
|
|||||||
showLoading: PropTypes.bool
|
showLoading: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
class UserState extends Component {
|
function UserState(props) {
|
||||||
render() {
|
const { isSignedIn, showLoading, disableSettings } = props;
|
||||||
console.log(this.props);
|
|
||||||
const { isSignedIn, showLoading, disableSettings } = this.props;
|
|
||||||
if (disableSettings) {
|
if (disableSettings) {
|
||||||
return <Login />;
|
return <Login />;
|
||||||
}
|
}
|
||||||
@ -48,7 +43,6 @@ class UserState extends Component {
|
|||||||
}
|
}
|
||||||
return isSignedIn ? <SignedIn /> : <Login />;
|
return isSignedIn ? <SignedIn /> : <Login />;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
UserState.displayName = 'UserState';
|
UserState.displayName = 'UserState';
|
||||||
UserState.propTypes = propTypes;
|
UserState.propTypes = propTypes;
|
||||||
|
@ -8,24 +8,19 @@
|
|||||||
padding: 4px 12px;
|
padding: 4px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-cta-big {
|
|
||||||
max-height: 100%;
|
|
||||||
height: 70px;
|
|
||||||
font-size: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-btn.btn {
|
.signup-btn.btn {
|
||||||
background-color: #ffac33;
|
background-color: #ffac33;
|
||||||
background-image: linear-gradient(#ffcc4d, #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;
|
border-color: #f1a02a;
|
||||||
color: #292f33 !important;
|
color: #292f33 !important;
|
||||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
|
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-color: #e99110;
|
||||||
background-image: linear-gradient(#ffcc4d, #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;
|
border-color: #ec8b11;
|
||||||
color: #292f33 !important;
|
color: #292f33 !important;
|
||||||
}
|
}
|
||||||
|
17
src/components/global.css
Normal file
17
src/components/global.css
Normal 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;
|
||||||
|
}
|
@ -622,12 +622,3 @@ pre tt:after {
|
|||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6 {
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
|
@ -2,14 +2,19 @@ import React, { Fragment, Component } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
import { StaticQuery, graphql } from 'gatsby';
|
import { StaticQuery, graphql } from 'gatsby';
|
||||||
|
|
||||||
import { fetchUser } from '../redux';
|
import { fetchUser, isSignedInSelector } from '../redux';
|
||||||
import Header from './Header';
|
import Header from './Header';
|
||||||
import './layout.css';
|
|
||||||
|
|
||||||
const mapStateToProps = () => ({});
|
import './layout.css';
|
||||||
|
import './global.css';
|
||||||
|
|
||||||
|
const mapStateToProps = createSelector(isSignedInSelector, isSignedIn => ({
|
||||||
|
isSignedIn
|
||||||
|
}));
|
||||||
const mapDispatchToProps = dispatch =>
|
const mapDispatchToProps = dispatch =>
|
||||||
bindActionCreators({ fetchUser }, dispatch);
|
bindActionCreators({ fetchUser }, dispatch);
|
||||||
|
|
||||||
@ -19,8 +24,10 @@ class Layout extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
if (!this.props.isSignedIn) {
|
||||||
this.props.fetchUser();
|
this.props.fetchUser();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { children, disableSettings } = this.props;
|
const { children, disableSettings } = this.props;
|
||||||
@ -56,7 +63,8 @@ class Layout extends Component {
|
|||||||
Layout.propTypes = {
|
Layout.propTypes = {
|
||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.node.isRequired,
|
||||||
disableSettings: PropTypes.bool,
|
disableSettings: PropTypes.bool,
|
||||||
fetchUser: PropTypes.func.isRequired
|
fetchUser: PropTypes.func.isRequired,
|
||||||
|
isSignedIn: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
|
@ -3,10 +3,6 @@
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 40px;
|
font-size: 40px;
|
||||||
}
|
}
|
||||||
.landing-heading {
|
|
||||||
font-size: 50px !important;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
.large-p {
|
.large-p {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
|
@ -29,7 +29,7 @@ const IndexPage = () => (
|
|||||||
<Layout disableSettings={true}>
|
<Layout disableSettings={true}>
|
||||||
<Grid className='text-center'>
|
<Grid className='text-center'>
|
||||||
<Row>
|
<Row>
|
||||||
<h1 className='landing-heading'>Learn to code for free.</h1>
|
<h1 className='big-heading'>Learn to code for free.</h1>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<Col md={4} sm={12}>
|
<Col md={4} sm={12}>
|
||||||
<Image
|
<Image
|
||||||
|
22
src/pages/welcome.css
Normal file
22
src/pages/welcome.css
Normal 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
140
src/pages/welcome.js
Normal 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);
|
Reference in New Issue
Block a user