feat: simplify landing page

This commit is contained in:
Ahmad Abdolsaheb
2019-09-01 17:20:53 +03:00
committed by Mrugesh Mohapatra
parent 5dd8044035
commit 9ff7bf5801
24 changed files with 350 additions and 1061 deletions

View File

@ -83,9 +83,9 @@ export const loginRedirect = () => {
const successRedirect = req => { const successRedirect = req => {
if (!!req && req.session && req.session.returnTo) { if (!!req && req.session && req.session.returnTo) {
delete req.session.returnTo; delete req.session.returnTo;
return `${homeLocation}/welcome`; return `${homeLocation}/`;
} }
return `${homeLocation}/welcome`; return `${homeLocation}/`;
}; };
let redirect = url.parse(successRedirect(req), true); let redirect = url.parse(successRedirect(req), true);
@ -112,7 +112,7 @@ export const createPassportCallbackAuthenticator = (strategy, config) => (
if (!user || !userInfo) { if (!user || !userInfo) {
return res.redirect('/signin'); return res.redirect('/signin');
} }
const redirect = `${homeLocation}/welcome`; const redirect = `${homeLocation}/`;
const { accessToken } = userInfo; const { accessToken } = userInfo;
const { provider } = config; const { provider } = config;

View File

@ -3,7 +3,7 @@ import { homeLocation, apiLocation } from '../../config/env';
const { clientID, clientSecret, domain } = auth0; const { clientID, clientSecret, domain } = auth0;
const successRedirect = `${homeLocation}/welcome`; const successRedirect = `${homeLocation}/`;
const failureRedirect = `${homeLocation}/signin`; const failureRedirect = `${homeLocation}/signin`;
export default { export default {

View File

@ -55,7 +55,7 @@ export function ifNotVerifiedRedirectToUpdateEmail(req, res, next) {
return next(); return next();
} }
export function ifUserRedirectTo(path = `${homeLocation}/welcome`, status) { export function ifUserRedirectTo(path = `${homeLocation}/`, status) {
status = status === 302 ? 302 : 301; status = status === 302 ? 302 : 301;
return (req, res, next) => { return (req, res, next) => {
const { accessToken } = getAccessTokenFromRequest(req); const { accessToken } = getAccessTokenFromRequest(req);

View File

@ -54,7 +54,6 @@
} }
.donation-email-container input { .donation-email-container input {
color: var(--secondary-background);
font-weight: normal; font-weight: normal;
} }

View File

@ -26,7 +26,7 @@ const createOnClick = (navigate, isSignedIn) => e => {
e.preventDefault(); e.preventDefault();
gtagReportConversion(); gtagReportConversion();
if (isSignedIn) { if (isSignedIn) {
return gatsbyNavigate('/welcome'); return gatsbyNavigate('/');
} }
return navigate(`${apiLocation}/signin`); return navigate(`${apiLocation}/signin`);
}; };

View File

@ -20,7 +20,7 @@ function Header(props) {
<Link className='home-link' to='/'> <Link className='home-link' to='/'>
<NavLogo /> <NavLogo />
</Link> </Link>
{disableSettings ? null : <SearchBar />} <SearchBar />
<NavigationMenu disableSettings={disableSettings} /> <NavigationMenu disableSettings={disableSettings} />
</nav> </nav>
</header> </header>

View File

@ -0,0 +1,25 @@
/* global expect */
import React from 'react';
import ShallowRenderer from 'react-test-renderer/shallow';
import { IndexPage } from '../../pages';
describe('<Landing />', () => {
it('renders when visiting index page and logged out', () => {
const shallow = new ShallowRenderer();
shallow.render(<IndexPage {...loggedOutProps} />);
const result = shallow.getRenderOutput();
expect(result.type.displayName === 'Landing').toBeTruthy();
});
});
const loggedOutProps = {
fetchState: {
complete: true,
error: null,
errored: false,
pending: false
},
isSignedIn: false,
user: {}
};

View File

@ -0,0 +1,72 @@
import React, { Fragment } from 'react';
import { Grid, Row, Col, Image } from '@freecodecamp/react-bootstrap';
import Helmet from 'react-helmet';
import Login from '../Header/components/Login';
import { Spacer } from '../helpers';
import './landing.css';
const BigCallToAction = () => (
<Row>
<Col sm={10} smOffset={1} xs={12}>
<Login block={true}>Sign in and get started.</Login>
</Col>
</Row>
);
function Landing() {
return (
<Fragment>
<Helmet>
<title>Learn to code | freeCodeCamp.org</title>
</Helmet>
<main className='index-page'>
<Spacer size={2} />
<Grid>
<Row>
<Col sm={10} smOffset={1} xs={12}>
<h1 className='big-heading text-center'>
Welcome to freeCodeCamp.org
</h1>
<Spacer />
<h2 className='medium-heading'>Learn to code.</h2>
<h2 className='medium-heading'>
Build projects and earn certifications.
</h2>
<h2 className='medium-heading'>
Grow your portfolio and get a developer job.
</h2>
<h2 className='medium-heading'>
It's all 100% free thanks to our nonprofit's donors.
</h2>
</Col>
</Row>
<Spacer />
<BigCallToAction />
<Spacer size={2} />
<Image
alt='companies featuring freeCodeCamp.org'
className='img-center'
responsive={true}
src='https://cdn-media-1.freecodecamp.org/learn/as-seen-on.png'
/>
<Spacer />
<Row>
<Col sm={10} smOffset={1} xs={12}>
<h2 className='medium-heading'>
Since 2014, more than 40,000 freeCodeCamp.org graduates have
gotten jobs in tech.
</h2>
</Col>
</Row>
<Spacer />
</Grid>
</main>
</Fragment>
);
}
Landing.displayName = 'Landing';
export default Landing;

View File

@ -25,7 +25,6 @@ import Header from '../Header';
import Footer from '../Footer'; import Footer from '../Footer';
import './global.css'; import './global.css';
import './layout.css';
import './variables.css'; import './variables.css';
fontawesome.config = { fontawesome.config = {
@ -67,7 +66,6 @@ const propTypes = {
hasMessage: PropTypes.bool, hasMessage: PropTypes.bool,
isOnline: PropTypes.bool.isRequired, isOnline: PropTypes.bool.isRequired,
isSignedIn: PropTypes.bool, isSignedIn: PropTypes.bool,
landingPage: PropTypes.bool,
onlineStatusChange: PropTypes.func.isRequired, onlineStatusChange: PropTypes.func.isRequired,
pathname: PropTypes.string.isRequired, pathname: PropTypes.string.isRequired,
removeFlashMessage: PropTypes.func.isRequired, removeFlashMessage: PropTypes.func.isRequired,
@ -133,7 +131,6 @@ class DefaultLayout extends Component {
flashMessage, flashMessage,
isOnline, isOnline,
isSignedIn, isSignedIn,
landingPage,
removeFlashMessage, removeFlashMessage,
showFooter = true, showFooter = true,
theme theme
@ -157,11 +154,8 @@ class DefaultLayout extends Component {
<style>{fontawesome.dom.css()}</style> <style>{fontawesome.dom.css()}</style>
</Helmet> </Helmet>
<WithInstantSearch> <WithInstantSearch>
<Header disableSettings={landingPage} /> <Header disableSettings={!isSignedIn} />
<div <div className={`default-layout`}>
className={`default-layout
${landingPage ? 'landing-page' : ''}`}
>
<OfflineWarning isOnline={isOnline} isSignedIn={isSignedIn} /> <OfflineWarning isOnline={isOnline} isSignedIn={isSignedIn} />
{hasMessage && flashMessage ? ( {hasMessage && flashMessage ? (
<Flash flashMessage={flashMessage} onClose={removeFlashMessage} /> <Flash flashMessage={flashMessage} onClose={removeFlashMessage} />

View File

@ -14,10 +14,6 @@ body {
white-space: normal; white-space: normal;
} }
.big-heading {
font-size: 50px !important;
}
.default-layout { .default-layout {
margin-top: var(--header-height); margin-top: var(--header-height);
background: var(--secondary-background); background: var(--secondary-background);
@ -62,6 +58,33 @@ th {
margin: 0 0 1.2rem; margin: 0 0 1.2rem;
} }
.big-heading {
font-size: 2.5rem !important;
}
.medium-heading {
font-size: 1.5rem !important;
font-weight: normal;
}
@media (max-width: 500px) {
.big-heading {
font-size: 1.6rem !important;
}
.medium-heading {
font-size: 1.4rem !important;
}
h1 {
font-size: 1.2rem;
}
h2 {
font-size: 1.1rem;
}
h3 {
font-size: 1rem;
}
}
.text-center { .text-center {
text-align: center !important; text-align: center !important;
} }
@ -351,3 +374,9 @@ pre {
.nav-tabs > li > a { .nav-tabs > li > a {
border-radius: 0; border-radius: 0;
} }
blockquote footer,
blockquote small,
blockquote .small {
color: var(--gray-45);
}

View File

@ -1,606 +0,0 @@
/* html {
font-family: 'Lato', sans-serif;
-ms-text-size-adjust: 100%;
-moz-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
}
article,
aside,
details,
figcaption,
figure,
footer,
header,
main,
menu,
nav,
section,
summary {
display: block;
}
audio,
canvas,
progress,
video {
display: inline-block;
}
audio:not([controls]) {
display: none;
height: 0;
}
progress {
vertical-align: baseline;
}
[hidden],
template {
display: none;
}
a {
background-color: transparent;
}
a:active,
a:hover {
outline-width: 0;
}
abbr[title] {
border-bottom: none;
text-decoration: underline;
text-decoration: underline dotted;
}
b,
strong {
font-weight: inherit;
font-weight: bolder;
}
dfn {
font-style: italic;
}
h1 {
font-size: 2em;
margin: 0.67em 0;
}
mark {
background-color: #ff0;
color: #000;
}
small {
font-size: 80%;
}
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
img {
border-style: none;
}
svg:not(:root) {
overflow: hidden;
}
code,
kbd,
pre,
samp {
font-family: monospace, monospace;
font-size: 1em;
}
figure {
margin: 1em 40px;
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
}
button,
input,
optgroup,
select,
textarea {
font: inherit;
margin: 0;
}
optgroup {
font-weight: 700;
}
button,
input {
overflow: visible;
}
button,
select {
text-transform: none;
}
[type='button']::-moz-focus-inner,
[type='reset']::-moz-focus-inner,
[type='submit']::-moz-focus-inner,
button::-moz-focus-inner {
border-style: none;
padding: 0;
}
[type='button']:-moz-focusring,
[type='reset']:-moz-focusring,
[type='submit']:-moz-focusring,
button:-moz-focusring {
outline: 1px dotted ButtonText;
}
fieldset {
border: 1px solid silver;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
legend {
box-sizing: border-box;
color: inherit;
display: table;
max-width: 100%;
padding: 0;
white-space: normal;
}
textarea {
overflow: auto;
}
[type='checkbox'],
[type='radio'] {
box-sizing: border-box;
padding: 0;
}
[type='number']::-webkit-inner-spin-button,
[type='number']::-webkit-outer-spin-button {
height: auto;
}
[type='search'] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
[type='search']::-webkit-search-cancel-button,
[type='search']::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-input-placeholder {
color: inherit;
opacity: 0.54;
}
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit;
}
html {
font: 112.5%/1.45em georgia, serif;
box-sizing: border-box;
overflow-y: scroll;
}
* {
box-sizing: inherit;
}
*:before {
box-sizing: inherit;
}
*:after {
box-sizing: inherit;
}
body {
color: hsla(0, 0%, 0%, 0.8);
font-family: 'Lato', sans-serif;
font-weight: normal;
word-wrap: break-word;
font-kerning: normal;
-moz-font-feature-settings: 'kern', 'liga', 'clig', 'calt';
-webkit-font-feature-settings: 'kern', 'liga', 'clig', 'calt';
font-feature-settings: 'kern', 'liga', 'clig', 'calt';
}
img {
max-width: 100%;
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
h1 {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
color: inherit;
font-family: 'Lato', -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
text-rendering: optimizeLegibility;
font-size: 2.25rem;
line-height: 1.1;
}
h2 {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
color: inherit;
font-family: 'Lato', -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
text-rendering: optimizeLegibility;
font-size: 1.62671rem;
line-height: 1.1;
}
h3 {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
color: inherit;
font-family: 'Lato', -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
text-rendering: optimizeLegibility;
font-size: 1.38316rem;
line-height: 1.1;
}
h4 {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
color: inherit;
font-family: 'Lato', -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
text-rendering: optimizeLegibility;
font-size: 1rem;
line-height: 1.1;
}
h5 {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
color: inherit;
font-family: 'Lato', -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
text-rendering: optimizeLegibility;
font-size: 0.85028rem;
line-height: 1.1;
}
h6 {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
color: inherit;
font-family: 'Lato', -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
text-rendering: optimizeLegibility;
font-size: 0.78405rem;
line-height: 1.1;
}
hgroup {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
ul {
margin-left: 1.45rem;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
list-style-position: outside;
list-style-image: none;
}
ol {
margin-left: 1.45rem;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
list-style-position: outside;
list-style-image: none;
}
dl {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
dd {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
p {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
figure {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
pre {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
font-size: 0.85rem;
line-height: 1.42;
background: hsla(0, 0%, 0%, 0.04);
border-radius: 3px;
overflow: auto;
word-wrap: normal;
padding: 1.45rem;
}
table {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
font-size: 1rem;
line-height: 1.45rem;
border-collapse: collapse;
width: 100%;
}
fieldset {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
blockquote {
margin-left: 1.45rem;
margin-right: 1.45rem;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
form {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
noscript {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
iframe {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
hr {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: calc(1.45rem - 1px);
background-image: linear-gradient(
to right,
rgba(0, 0, 0, 0),
rgba(0, 0, 0, 0.75),
rgba(0, 0, 0, 0)
);
border: none;
height: 1px;
}
address {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
b {
font-weight: bold;
}
strong {
font-weight: bold;
}
dt {
font-weight: bold;
}
th {
font-weight: bold;
}
li {
margin-bottom: calc(1.45rem / 2);
}
ol li {
padding-left: 0;
}
ul li {
padding-left: 0;
}
li > ol {
margin-left: 1.45rem;
margin-bottom: calc(1.45rem / 2);
margin-top: calc(1.45rem / 2);
}
li > ul {
margin-left: 1.45rem;
margin-bottom: calc(1.45rem / 2);
margin-top: calc(1.45rem / 2);
}
blockquote *:last-child {
margin-bottom: 0;
}
li *:last-child {
margin-bottom: 0;
}
p *:last-child {
margin-bottom: 0;
}
li > p {
margin-bottom: calc(1.45rem / 2);
}
code {
font-size: 0.85rem;
line-height: 1.45rem;
}
kbd {
font-size: 0.85rem;
line-height: 1.45rem;
}
samp {
font-size: 0.85rem;
line-height: 1.45rem;
}
abbr {
border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5);
cursor: help;
}
acronym {
border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5);
cursor: help;
}
abbr[title] {
border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5);
cursor: help;
text-decoration: none;
}
thead {
text-align: left;
}
td,
th {
text-align: left;
border-bottom: 1px solid hsla(0, 0%, 0%, 0.12);
font-feature-settings: 'tnum';
-moz-font-feature-settings: 'tnum';
-webkit-font-feature-settings: 'tnum';
padding-left: 0.96667rem;
padding-right: 0.96667rem;
padding-top: 0.725rem;
padding-bottom: calc(0.725rem - 1px);
}
th:first-child,
td:first-child {
padding-left: 0;
}
th:last-child,
td:last-child {
padding-right: 0;
}
tt,
code {
background-color: hsla(0, 0%, 0%, 0.04);
border-radius: 3px;
font-family: 'SFMono-Regular', Consolas, 'Roboto Mono', 'Droid Sans Mono',
'Liberation Mono', Menlo, Courier, monospace;
padding: 0.2em 0.3em;
}
pre code {
background: none;
line-height: 1.42;
}
@media only screen and (max-width: 480px) {
html {
font-size: 100%;
}
}
.default-layout {
display: flex;
flex-direction: column;
margin-top: var(--header-height);
min-height: calc(100vh - var(--header-height));
} */

View File

@ -0,0 +1,37 @@
/* global expect */
import React from 'react';
import renderer from 'react-test-renderer';
import 'jest-dom/extend-expect';
import { IndexPage } from '../../pages';
import Welcome from './';
describe('<Welcome />', () => {
it('renders when visiting index page and logged in', () => {
const container = renderer
.create(<IndexPage {...loggedInProps} />)
.toTree();
expect(container.rendered.type.displayName === 'Welcome').toBeTruthy();
});
it('has four links', () => {
const container = renderer.create(<Welcome name={'developmentuser'} />)
.root;
expect(container.findAllByType('a').length === 4).toBeTruthy();
});
});
const loggedInProps = {
fetchState: {
complete: true,
error: null,
errored: false,
pending: false
},
isSignedIn: true,
user: {
acceptedPrivacyTerms: true,
username: 'developmentuser'
}
};

View File

@ -0,0 +1,88 @@
import React, { Fragment } from 'react';
import { Grid, Row, Col } from '@freecodecamp/react-bootstrap';
import Helmet from 'react-helmet';
import PropTypes from 'prop-types';
import { Spacer, Link } from '../helpers';
import { randomQuote } from '../../utils/get-words';
import './welcome.css';
function Welcome({ name }) {
const { quote, author } = randomQuote();
return (
<Fragment>
<Helmet>
<title>Welcome | freeCodeCamp.org</title>
</Helmet>
<main>
<Grid>
<Row>
<Col sm={10} smOffset={1} xs={12}>
<Spacer />
<h1 className='text-center big-heading'>
Welcome {name ? name : 'Camper'}!
</h1>
</Col>
</Row>
<Spacer />
<Row className='text-center 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>
<Row>
<Col sm={10} smOffset={1} xs={12}>
<Spacer />
<h2 className='text-center medium-heading'>
What would you like to do today?
</h2>
</Col>
</Row>
<Spacer />
<Row>
<Col sm={10} smOffset={1} xs={12}>
<Link className='btn btn-lg btn-primary btn-block' to='/learn'>
Build Projects and Earn Certifications
</Link>
<Link className='btn btn-lg btn-primary btn-block' to='settings'>
Update Your Developer Portfolio
</Link>
<Link
className='btn btn-lg btn-primary btn-block'
external={true}
to='/news'
>
Read Developer News
</Link>
<Link
className='btn btn-lg btn-primary btn-block'
external={true}
to='/forum'
>
Help Developers on the Forum
</Link>
</Col>
</Row>
<Spacer size={4} />
</Grid>
</main>
</Fragment>
);
}
const propTypes = {
name: PropTypes.string
};
Welcome.propTypes = propTypes;
Welcome.displayName = 'Welcome';
export default Welcome;

View File

@ -0,0 +1,14 @@
.quote-partial .blockquote {
font-size: 1.3rem;
border: none;
}
@media (max-width: 500px) {
.quote-partial .blockquote {
font-size: 1.2rem;
border: none;
}
}
.quote-author {
font-style: normal;
}

View File

@ -32,7 +32,7 @@ const mapStateToProps = createSelector(
); );
const mapDispatchToProps = dispatch => const mapDispatchToProps = dispatch =>
bindActionCreators({ acceptTerms }, dispatch); bindActionCreators({ acceptTerms }, dispatch);
const RedirectWelcome = createRedirect('/welcome'); const RedirectHome = createRedirect('/');
class AcceptPrivacyTerms extends Component { class AcceptPrivacyTerms extends Component {
constructor(props) { constructor(props) {
@ -66,7 +66,7 @@ class AcceptPrivacyTerms extends Component {
render() { render() {
const { acceptedPrivacyTerms } = this.props; const { acceptedPrivacyTerms } = this.props;
if (acceptedPrivacyTerms) { if (acceptedPrivacyTerms) {
return <RedirectWelcome />; return <RedirectHome />;
} }
const { privacyPolicy, termsOfService, quincyEmail } = this.state; const { privacyPolicy, termsOfService, quincyEmail } = this.state;
return ( return (

View File

@ -11,8 +11,6 @@ import DonateForm from '../components/Donation/components/DonateForm';
import DonateText from '../components/Donation/components/DonateText'; import DonateText from '../components/Donation/components/DonateText';
import PoweredByStripe from '../components/Donation/components/poweredByStripe'; import PoweredByStripe from '../components/Donation/components/poweredByStripe';
import './index.css';
class DonatePage extends Component { class DonatePage extends Component {
constructor(...props) { constructor(...props) {
super(...props); super(...props);

View File

@ -1,240 +1,71 @@
import React, { Fragment } from 'react'; import React from 'react';
import { Grid, Row, Col, Image } from '@freecodecamp/react-bootstrap'; import { createSelector } from 'reselect';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { Loader } from '../components/helpers';
import Welcome from '../components/welcome';
import Landing from '../components/landing';
import { import {
faHtml5, userSelector,
faCss3Alt, userFetchStateSelector,
faJs, isSignedInSelector
faGithub, } from '../redux';
faNodeJs, import createRedirect from '../components/createRedirect';
faReact
} from '@fortawesome/free-brands-svg-icons';
import { faDatabase } from '@fortawesome/free-solid-svg-icons';
import Helmet from 'react-helmet';
import { Link, Spacer } from '../components/helpers'; const mapStateToProps = createSelector(
import Login from '../components/Header/components/Login'; userFetchStateSelector,
isSignedInSelector,
import './index.css'; userSelector,
(fetchState, isSignedIn, user) => ({
const BigCallToAction = () => ( fetchState,
<Row> isSignedIn,
<Col sm={8} smOffset={2} xs={12}> user
<Login block={true}>Start coding (it's free)</Login> })
</Col>
</Row>
); );
const IndexPage = () => ( const RedirectAcceptPrivacyTerm = createRedirect('/accept-privacy-terms');
<Fragment>
<Helmet>
<title>Learn to code | freeCodeCamp.org</title>
</Helmet>
<Spacer size={2} />
<Grid className='text-center'>
<Row>
<h1 className='big-heading'>Learn to code for free.</h1>
<Spacer />
<Col md={4} sm={12}>
<Image
alt={
'Get great references and connections to start your software ' +
'engineer career'
}
className='landing-icon img-center'
responsive={true}
src={
'https://s3.amazonaws.com/freecodecamp/landing-icon-community.svg'
}
/>
<p className='large-p'>
Join a supportive community of millions of coders.
</p>
</Col>
<Col md={4} sm={12}>
<Image
alt='Help nonprofits with pro bono code projects'
className='landing-icon img-center'
responsive={true}
src={
'https://s3.amazonaws.com/freecodecamp/landing-icon-' +
'certificate.svg'
}
/>
<p className='large-p'>
Build projects and earn free certifications.
</p>
</Col>
<Col md={4} sm={12}>
<Image
alt={
'Get hired as a developer and start your software engineer career'
}
className='landing-icon img-center'
responsive={true}
src={
'https://s3.amazonaws.com/freecodecamp/landing-icon-' +
'experience.svg'
}
/>
<p className='large-p'>Get experience by coding for nonprofits.</p>
</Col>
</Row>
<Spacer size={2} />
<BigCallToAction />
<Spacer />
<h2>As featured in:</h2>
<Image
alt='companies featuring freeCodeCamp'
className='img-center'
responsive={true}
src='https://s3.amazonaws.com/freecodecamp/as-seen-on.png'
/>
<Spacer />
<hr />
<Spacer />
<h2>Launch your developer career</h2>
<Spacer />
<Row>
<Col md={4} sm={12}>
<Image
alt="Meta's testimonial image"
className='testimonial-image img-center'
responsive={true}
src='https://cdn-media-1.freecodecamp.org/imgr/nsvNixW.jpg'
/>
<p className='testimonial-copy'>
Through freeCodeCamp, I built a robust and highly functional web app
for a nonprofit. This led me to getting a fantastic job.
</p>
<h3>- Meta Hirschl</h3>
</Col>
<Col md={4} sm={12}>
<Image
alt="Brian's testimonial image"
className='testimonial-image img-center'
responsive={true}
src='https://cdn-media-1.freecodecamp.org/imgr/QPpjPac.jpg'
/>
<p className='testimonial-copy'>
freeCodeCamp is a great way for disabled veterans like me to
retrain. I'm already receiving software engineering job offers.
</p>
<h3>- Brian Grant</h3>
</Col>
<Col md={4} sm={12}>
<Image
alt="Maxim Orlov's testimonial image"
className='testimonial-image img-center'
responsive={true}
src='https://cdn-media-1.freecodecamp.org/imgr/wjlDigg.jpg'
/>
<p className='testimonial-copy'>
I joined freeCodeCamp with zero knowledge of web development. 6
months later, I landed my first job as a back end engineer.
</p>
<h3>- Maxim Orlov</h3>
</Col>
</Row>
<Spacer />
<hr />
<Spacer />
<h2>Learn powerful skills</h2>
<Spacer />
<Row className='text-center'>
<Col md={3} sm={6} xs={12}>
<FontAwesomeIcon
className='landing-skill-icon'
icon={faHtml5}
size='9x'
/>
<h2 className='black-text'>HTML5</h2>
</Col>
<Col md={3} sm={6} xs={12}>
<FontAwesomeIcon
className='landing-skill-icon'
icon={faCss3Alt}
size='9x'
/>
<h2 className='black-text'>CSS3</h2>
</Col>
<Col md={3} sm={6} xs={12}>
<FontAwesomeIcon
className='landing-skill-icon'
icon={faJs}
size='9x'
/>
<h2 className='black-text'>JavaScript</h2>
</Col>
<Col md={3} sm={6} xs={12}>
<FontAwesomeIcon
className='landing-skill-icon'
icon={faDatabase}
size='9x'
/>
<h2 className='black-text'>Databases</h2>
</Col>
</Row>
<Row className='text-center'>
<Col md={3} sm={6} xs={12}>
<FontAwesomeIcon
className='landing-skill-icon'
icon={faGithub}
size='9x'
/>
<h2 className='black-text'>Git & GitHub</h2>
</Col>
<Col md={3} sm={6} xs={12}>
<FontAwesomeIcon
className='landing-skill-icon'
icon={faNodeJs}
size='9x'
/>
<h2 className='black-text'>Node.js</h2>
</Col>
<Col md={3} sm={6} xs={12}>
<FontAwesomeIcon
className='landing-skill-icon'
icon={faReact}
size='9x'
/>
<h2 className='black-text'>React.js</h2>
</Col>
<Col md={3} sm={6} xs={12}>
<Image
alt='The D3.js Logo'
className='landing-skill-icon custom-landing-skill-icon'
src='https://s3.amazonaws.com/freecodecamp/d3-logo.svg'
/>
<h2 className='black-text'>D3.js</h2>
</Col>
</Row>
<hr />
<Spacer />
<h3>
freeCodeCamp is a donor-supported tax-exempt 501(c)(3) nonprofit
organization (United States Federal Tax Identification Number:
82-0779546)
</h3>
<p className='large-p'>
Our mission: to help people learn to code for free. We accomplish this
by creating thousands of videos, articles, and interactive coding
lessons - all freely available to the public. We also have thousands of
freeCodeCamp study groups around the world.
</p>
<p className='large-p'>
Donations to freeCodeCamp go toward our education initiatives, and help
pay for servers, services, and staff. You can{' '}
<Link className='large-p underlined-link' external={true} to='/donate'>
make a tax-deductible donation here
</Link>
.
</p>
<Spacer />
<BigCallToAction />
<Spacer size={2} />
</Grid>
</Fragment>
);
export default IndexPage; export const IndexPage = ({
fetchState: { pending, complete },
isSignedIn,
user: { acceptedPrivacyTerms, name = '' }
}) => {
if (pending && !complete) {
return <Loader fullScreen={true} />;
}
if (isSignedIn && !acceptedPrivacyTerms) {
return <RedirectAcceptPrivacyTerm />;
}
if (isSignedIn) {
return <Welcome name={name} />;
}
return <Landing />;
};
const propTypes = {
fetchState: PropTypes.shape({
pending: PropTypes.bool,
complete: PropTypes.bool,
errored: PropTypes.bool
}),
isSignedIn: PropTypes.bool,
user: PropTypes.shape({
acceptedPrivacyTerms: PropTypes.bool,
completedCertCount: PropTypes.number,
completedChallengeCount: PropTypes.number,
completedLegacyCertCount: PropTypes.number,
completedProjectCount: PropTypes.number,
isDonating: PropTypes.bool,
name: PropTypes.string,
username: PropTypes.string
})
};
IndexPage.propTypes = propTypes;
IndexPage.displayName = 'IndexPage';
export default connect(mapStateToProps)(IndexPage);

View File

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

View File

@ -1,163 +0,0 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { Grid, Row, Col } from '@freecodecamp/react-bootstrap';
import Helmet from 'react-helmet';
import { CurrentChallengeLink, Loader, Spacer } from '../components/helpers';
import Supporters from '../components/Supporters';
import {
userSelector,
userFetchStateSelector,
isSignedInSelector,
activeDonationsSelector
} from '../redux';
import { randomQuote } from '../utils/get-words';
import createRedirect from '../components/createRedirect';
import RedirectHome from '../components/RedirectHome';
import './welcome.css';
const propTypes = {
activeDonations: PropTypes.number,
fetchState: PropTypes.shape({
pending: PropTypes.bool,
complete: PropTypes.bool,
errored: PropTypes.bool
}),
isSignedIn: PropTypes.bool,
user: PropTypes.shape({
acceptedPrivacyTerms: PropTypes.bool,
completedCertCount: PropTypes.number,
completedChallengeCount: PropTypes.number,
completedLegacyCertCount: PropTypes.number,
completedProjectCount: PropTypes.number,
isDonating: PropTypes.bool,
name: PropTypes.string,
username: PropTypes.string
})
};
const mapStateToProps = createSelector(
userFetchStateSelector,
isSignedInSelector,
userSelector,
activeDonationsSelector,
(fetchState, isSignedIn, user, activeDonations) => ({
activeDonations,
fetchState,
isSignedIn,
user
})
);
const mapDispatchToProps = dispatch => bindActionCreators({}, dispatch);
const RedirectAcceptPrivacyTerm = createRedirect('/accept-privacy-terms');
function Welcome({
fetchState: { pending, complete },
isSignedIn,
user: {
acceptedPrivacyTerms,
name = '',
completedChallengeCount: completedChallenges = 0,
completedProjectCount = 0,
completedCertCount = 0,
completedLegacyCertCount: completedLegacyCerts = 0,
isDonating
},
activeDonations
}) {
if (pending && !complete) {
return <Loader fullScreen={true} />;
}
if (!isSignedIn) {
return <RedirectHome />;
}
if (isSignedIn && !acceptedPrivacyTerms) {
return <RedirectAcceptPrivacyTerm />;
}
const { quote, author } = randomQuote();
return (
<Fragment>
<Helmet>
<title>Welcome | freeCodeCamp.org</title>
</Helmet>
<main>
<Grid>
<Row>
<Col xs={12}>
<Spacer />
<h1 className='big-heading text-center'>
Welcome {name ? name : 'Camper'}!
</h1>
</Col>
</Row>
<Spacer />
<Supporters
activeDonations={activeDonations}
isDonating={isDonating}
/>
<Spacer size={2} />
<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>{completedChallenges}</span> of{' '}
<span>1408</span> coding challenges.
</p>
<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={6} smOffset={3} xs={12}>
<CurrentChallengeLink>
Go to my next challenge
</CurrentChallengeLink>
</Col>
</Row>
<Spacer size={4} />
</Grid>
</main>
</Fragment>
);
}
Welcome.displayName = 'Welcome';
Welcome.propTypes = propTypes;
export default connect(
mapStateToProps,
mapDispatchToProps
)(Welcome);

View File

@ -18,7 +18,7 @@ function* acceptTermsSaga({ payload: quincyEmails }) {
} }
function* acceptCompleteSaga() { function* acceptCompleteSaga() {
yield call(navigate, '/welcome'); yield call(navigate, '/');
} }
export function createAcceptTermsSaga(types) { export function createAcceptTermsSaga(types) {

View File

@ -37,7 +37,7 @@ function* resetProgressSaga() {
// wait for the refresh to complete // wait for the refresh to complete
yield take(appTypes.fetchUserComplete); yield take(appTypes.fetchUserComplete);
// the complete action has been called // the complete action has been called
yield call(navigate, '/welcome'); yield call(navigate, '/');
} catch (e) { } catch (e) {
yield put(resetProgressError(e)); yield put(resetProgressError(e));
} }

View File

@ -20,7 +20,7 @@ export function wrapHandledError(err, { type, message, redirectTo }) {
return err; return err;
} }
export function handle400Error(e, options = { redirectTo: '/welcome' }) { export function handle400Error(e, options = { redirectTo: '/' }) {
const { const {
response: { status } response: { status }
} = e; } = e;
@ -54,7 +54,7 @@ export function handle400Error(e, options = { redirectTo: '/welcome' }) {
export function handle500Error( export function handle500Error(
e, e,
options = { options = {
redirectTo: '/welcome' redirectTo: '/'
}, },
_reportClientSideError = reportClientSideError _reportClientSideError = reportClientSideError
) { ) {

View File

@ -11,13 +11,6 @@ export default function layoutSelector({ element, props }) {
const { const {
location: { pathname } location: { pathname }
} = props; } = props;
if (pathname === '/') {
return (
<DefaultLayout landingPage={true} pathname={pathname}>
{element}
</DefaultLayout>
);
}
if (element.type === FourOhFourPage) { if (element.type === FourOhFourPage) {
return <DefaultLayout pathname={pathname}>{element}</DefaultLayout>; return <DefaultLayout pathname={pathname}>{element}</DefaultLayout>;
} }