feat(client): shinny new landing page 🎉 (#39400)

Co-authored-by: Mrugesh Mohapatra <1884376+raisedadead@users.noreply.github.com>
Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
Ahmad Abdolsaheb
2020-08-24 21:06:40 +03:00
committed by GitHub
parent 99585403f9
commit d9dad10f43
36 changed files with 714 additions and 252 deletions

View File

@@ -43,7 +43,7 @@ function Intro({
<Row>
<Col sm={10} smOffset={1} xs={12}>
<Spacer />
<h1 className='text-center big-heading'>
<h1 className='text-center '>
{name ? `Welcome back, ${name}.` : `Welcome to freeCodeCamp.org`}
</h1>
<Spacer />
@@ -101,9 +101,7 @@ function Intro({
<Row>
<Col sm={8} smOffset={2} xs={12}>
<Spacer />
<h1 className='big-heading'>
Welcome to freeCodeCamp's curriculum.
</h1>
<h1>Welcome to freeCodeCamp's curriculum.</h1>
<Spacer size={1} />
</Col>
<IntroDescription />

View File

@@ -9,14 +9,6 @@
white-space: pre-line;
}
.logo-row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-around;
align-content: center;
}
.logo-row h2 {
height: 35px;
padding: 0 10px 0 10px;
@@ -27,11 +19,6 @@
padding-left: 0px;
}
#learn-app-wrapper h2.medium-heading {
margin-bottom: 50px;
text-align: center;
}
.quote-partial .blockquote {
font-size: 1.3rem;
border: none;

View File

@@ -15,7 +15,7 @@ function CurrentChallengeLink({ children, isLargeBtn }) {
if (isLargeBtn) {
classNames = 'btn btn-lg btn-primary btn-block';
} else {
classNames = 'btn btn-cta-big btn-primary btn-block';
classNames = 'btn btn-primary btn-block';
}
return (
<a className={classNames} href={`${apiLocation}${currentChallengeApi}`}>

View File

@@ -0,0 +1,65 @@
import React from 'react';
import './image-loader.css';
import LazyLoad from 'react-lazy-load';
import PropTypes from 'prop-types';
const propTypes = {
alt: PropTypes.string,
className: PropTypes.string,
height: PropTypes.number,
loadedClassName: PropTypes.string,
loadingClassName: PropTypes.string,
src: PropTypes.string,
width: PropTypes.number
};
class ImageLoader extends React.Component {
// initial state: image loaded stage
state = {
loaded: false
};
// define our loading and loaded image classes
static defaultProps = {
className: '',
loadingClassName: 'img-loading',
loadedClassName: 'img-loaded'
};
// image onLoad handler to update state to loaded
onLoad = () => {
this.setState(() => ({ loaded: true }));
};
render() {
let {
className,
loadedClassName,
loadingClassName,
alt,
src,
width,
height
} = this.props;
className = `${className} ${
this.state.loaded ? loadedClassName : loadingClassName
}`;
return (
<LazyLoad
debounce={false}
height={height}
offsetVertical={100}
width={width}
>
{/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */}
<img alt={alt} className={className} onLoad={this.onLoad} src={src} />
{/* eslint-enable jsx-a11y/no-noninteractive-element-interactions */}
</LazyLoad>
);
}
}
ImageLoader.propTypes = propTypes;
export default ImageLoader;

View File

@@ -0,0 +1,23 @@
@keyframes fadeInImg {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.img-loading {
opacity: 0;
width: 100%;
height: auto;
}
.img-loaded {
animation: fadeInImg cubic-bezier(0.23, 1, 0.32, 1) 1;
position: relative;
opacity: 0;
animation-fill-mode: forwards;
animation-duration: 0.7s;
animation-delay: 0.1s;
}

View File

@@ -6,3 +6,4 @@ export { default as SkeletonSprite } from './SkeletonSprite';
export { default as Spacer } from './Spacer';
export { default as Link } from './Link';
export { default as CurrentChallengeLink } from './CurrentChallengeLink';
export { default as ImageLoader } from './ImageLoader';

View File

@@ -0,0 +1,18 @@
import React from 'react';
import { Col, Row } from '@freecodecamp/react-bootstrap';
import { AsFeatureLogo } from '../../../assets/images/components';
const AsSeenIn = () => (
<Row className='as-seen-in'>
<Col sm={8} smOffset={2} xs={12}>
<div className='text-center'>
<h1 className='big-heading'>As seen in:</h1>
<AsFeatureLogo fill='light' />
</div>
</Col>
</Row>
);
AsSeenIn.displayName = 'AsSeenIn';
export default AsSeenIn;

View File

@@ -0,0 +1,19 @@
import React from 'react';
import PropTypes from 'prop-types';
import Login from '../../Header/components/Login';
const propTypes = {
page: PropTypes.string
};
const BigCallToAction = ({ page }) => (
<Login block={true} data-test-label={`${page}-big-cta`}>
{page === 'landing'
? "Get started (it's free)"
: "Sign in to save your progress (it's free)"}
</Login>
);
BigCallToAction.displayName = 'BigCallToAction';
BigCallToAction.propTypes = propTypes;
export default BigCallToAction;

View File

@@ -0,0 +1,47 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Col, Row } from '@freecodecamp/react-bootstrap';
import { uniq } from 'lodash';
import { Spacer, Link } from '../../helpers';
import LinkButton from '../../../assets/icons/LinkButton';
import BigCallToAction from './BigCallToAction';
const propTypes = {
nodes: PropTypes.array,
page: PropTypes.string
};
const Certifications = ({ nodes, page }) => {
const superBlocks = uniq(nodes.map(node => node.superBlock)).filter(
cert => cert !== 'Coding Interview Prep'
);
return (
<Row className='certification-section'>
<Col md={8} mdOffset={2} sm={10} smOffset={1} xs={12}>
<h1 className='big-heading'>Earn free verified certifications in:</h1>
<ul data-test-label='certifications'>
{superBlocks.map((superBlock, i) => (
<li key={i}>
<Link
className='btn link-btn btn-lg'
state={{ superBlock: superBlock }}
to={`/learn`}
>
{superBlock}
<LinkButton />
</Link>
</li>
))}
</ul>
<Spacer />
<BigCallToAction page={page} />
<Spacer />
</Col>
</Row>
);
};
Certifications.displayName = 'Certifications';
Certifications.propTypes = propTypes;
export default Certifications;

View File

@@ -1,26 +0,0 @@
import React from 'react';
import {
AmazonLogo,
AppleLogo,
MicrosoftLogo,
SpotifyLogo,
GoogleLogo
} from '../../../assets/images';
import './companyLogos.css';
export const CompanyLogos = () => {
return (
<div className='logo-row'>
<GoogleLogo />
<MicrosoftLogo />
<AppleLogo />
<SpotifyLogo />
<AmazonLogo />
</div>
);
};
CompanyLogos.displayName = 'CompanyLogos';
export default CompanyLogos;

View File

@@ -0,0 +1,78 @@
import React from 'react';
import PropTypes from 'prop-types';
import Media from 'react-responsive';
import { Col, Row } from '@freecodecamp/react-bootstrap';
import { Spacer, ImageLoader } from '../../helpers';
import wideImg from '../../../assets/images/landing/wide-image.png';
import Login from '../../Header/components/Login';
import {
AmazonLogo,
AppleLogo,
MicrosoftLogo,
SpotifyLogo,
GoogleLogo
} from '../../../assets/images/components';
const propTypes = {
page: PropTypes.string
};
const LARGE_SCREEN_SIZE = 1200;
function landingTop({ page }) {
const landingImageSection = (
<Media minWidth={LARGE_SCREEN_SIZE}>
<Spacer size={2} />
<ImageLoader
alt='Freecodecamp students at a local study'
className='landing-page-image'
height={442}
src={wideImg}
width={750}
/>
<p className='text-center caption'>
freeCodeCamp students at a local study group in South Korea.
</p>
</Media>
);
const BigCallToAction = (
<Login block={true} data-test-label={`${page}-big-cta`}>
{page === 'landing'
? "Get started (it's free)"
: "Sign in to save your progress (it's free)"}
</Login>
);
return (
<div className='landing-top'>
<Row>
<Spacer />
<Col lg={8} lgOffset={2} sm={10} smOffset={1} xs={12}>
<h1 className='big-heading' data-test-label={`${page}-header`}>
Learn to code at home.
</h1>
<h1 className='big-heading '>Build projects.</h1>
<h1 className='big-heading'>Earn certifications.</h1>
<h2>
Since 2014, more than 40,000 freeCodeCamp.org graduates have gotten
jobs at tech companies including:
</h2>
<div className='logo-row'>
<AppleLogo />
<GoogleLogo />
<MicrosoftLogo />
<AmazonLogo />
<SpotifyLogo />
</div>
<Spacer />
{BigCallToAction}
{landingImageSection}
<Spacer />
</Col>
</Row>
</div>
);
}
landingTop.displayName = 'LandingTop';
landingTop.propTypes = propTypes;
export default landingTop;

View File

@@ -0,0 +1,61 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ImageLoader } from '../../helpers';
import testimonialsMeta from '../landingMeta';
const propTypes = {
page: PropTypes.string
};
const Testimonials = () => {
const campers = Object.keys(testimonialsMeta);
return (
<div className='testimonials'>
<h1 className='big-heading text-center'>
Here is what our alumini say about freeCodeCamp:
</h1>
<div className='testimonials-row' data-test-label='testimonial-cards'>
{campers.map((camper, i) => {
let {
name,
country,
position,
company,
testimony
} = testimonialsMeta[camper];
let imageSource = require(`../../../assets/images/landing/${camper}.png`);
return (
<div className='testimonial-card' key={i}>
<div className='testimonial-card-header'>
<ImageLoader
alt={`${name}`}
className='testimonial-image'
src={imageSource}
/>
</div>
<div className='testimonials-footer'>
<div className='testimonial-meta'>
<p>
{' '}
<strong>{name}</strong> in {country}
</p>
<p>
{position} at <strong>{company}</strong>
</p>
</div>
<div className='testimony'>{testimony}</div>
</div>
</div>
);
})}
</div>
</div>
);
};
Testimonials.displayName = 'Testimonals';
Testimonials.propTypes = propTypes;
export default Testimonials;

View File

@@ -1,32 +0,0 @@
.logo-row svg {
height: 25px;
margin: 10px;
}
@media (min-width: 343px) {
.logo-row svg {
height: 35px;
}
}
@media (min-width: 380px) {
.logo-row svg {
height: 40px;
}
}
@media (min-width: 480px) {
.logo-row svg {
height: 25px;
}
}
@media (min-width: 600px) {
.logo-row svg {
height: 40px;
margin: 10px;
}
.logo-row #apple-logo {
height: 35px;
}
}

View File

@@ -1,108 +1,49 @@
import React, { Fragment } from 'react';
import { Grid, Row, Col } from '@freecodecamp/react-bootstrap';
import { Grid } from '@freecodecamp/react-bootstrap';
import Helmet from 'react-helmet';
import PropTypes from 'prop-types';
import { Link } from 'gatsby';
import { uniq, partition } from 'lodash';
import { Spacer } from '../helpers';
import Login from '../Header/components/Login';
import CompanyLogos from './components/CompanyLogos';
import { AsFeatureLogo } from '../../assets/images';
import { graphql, useStaticQuery } from 'gatsby';
import Testimonials from './components/Testimonials';
import LandingTop from './components/LandingTop';
import Certifications from './components/Certifications';
import AsSeenIn from './components/AsSeenIn';
import './landing.css';
import '../Map/map.css';
const propTypes = {
nodes: PropTypes.array
page: PropTypes.string
};
const BigCallToAction = () => (
<Row>
<Col md={6} mdOffset={3} sm={8} smOffset={2} xs={12}>
<Login block={true} data-test-label='landing-big-cta'>
Get started (it's free)
</Login>
</Col>
</Row>
);
export const Landing = ({ page = 'landing' }) => {
const data = useStaticQuery(graphql`
query certifications {
challenges: allChallengeNode(
filter: { isHidden: { eq: false } }
sort: { fields: [superOrder, order, challengeOrder] }
) {
nodes {
superBlock
}
}
}
`);
const AsFeaturedSection = () => (
<Row>
<Col sm={8} smOffset={2} xs={12}>
<div className='text-center'>
<h2 className='medium-heading'>As Featured In:</h2>
<AsFeatureLogo />
</div>
</Col>
</Row>
);
export const Landing = ({ nodes }) => {
const [superBlocks, rest] = partition(
uniq(nodes.map(node => node.superBlock)),
name => name !== 'Coding Interview Prep'
);
const interviewPrep = rest[0];
return (
<Fragment>
<Helmet>
<title>Learn to code at home | freeCodeCamp.org</title>
</Helmet>
<main className='landing-page'>
<Spacer />
<Grid>
<Row>
<Col sm={10} smOffset={1} xs={12}>
<h1
className='big-heading text-center'
data-test-label='landing-header'
>
Welcome to freeCodeCamp.org
</h1>
<Spacer />
<h2 className='medium-heading text-center'>
Learn to code at home.
</h2>
<h2 className='medium-heading text-center'>Build projects.</h2>
<h2 className='medium-heading text-center'>
Earn certifications.
</h2>
<h2 className='medium-heading text-center'>
Since 2014, more than 40,000 freeCodeCamp.org graduates have
gotten jobs at tech companies including:
</h2>
<CompanyLogos />
</Col>
</Row>
<Spacer />
<BigCallToAction />
<Spacer />
<AsFeaturedSection />
<Row>
<Col sm={10} smOffset={1} xs={12}>
<h2 className='medium-heading'>Certifications:</h2>
<ul>
{superBlocks.map((superBlock, i) => (
<li className={'superblock'} key={i}>
<Link state={{ superBlock: superBlock }} to={`/learn`}>
<h2 className='medium-heading'>{superBlock}</h2>
</Link>
</li>
))}
</ul>
<Spacer />
<h2 className='medium-heading'>Additional Learning:</h2>
<ul>
<li>
<Link state={{ superBlock: interviewPrep }} to={`/learn`}>
<h2 className='medium-heading'>{interviewPrep}</h2>
</Link>
</li>
</ul>
</Col>
</Row>
<Spacer />
<LandingTop page={page} />
</Grid>
<Grid fluid={true}>
<AsSeenIn />
</Grid>
<Grid>
<Testimonials />
<Certifications nodes={data.challenges.nodes} page={page} />
</Grid>
</main>
</Fragment>

View File

@@ -9,21 +9,203 @@
white-space: pre-line;
}
.logo-row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-around;
align-content: center;
margin-bottom: 10px;
}
.landing-page ul {
list-style: none;
padding-left: 0px;
}
#featured-logos {
margin-top: 10px;
max-width: 600px;
}
.landing-top h1:first-child {
margin-top: 0px;
}
.landing-page h1,
.landing-page h2,
.landing-page p {
font-family: 'Lato', sans-serif;
}
p.caption {
margin: 10px 0 0 0;
font-size: 0.8rem;
color: var(--quaternary-color);
}
.as-seen-in {
background-color: var(--gray-75);
color: var(--gray-15);
}
.as-seen-in h1 {
color: var(--gray-15);
}
.landing-page h1 {
margin-bottom: 40px;
}
.campers-images {
display: none !important;
}
@media (min-width: 1200px) {
.campers-images {
display: inline-block !important;
width: 100%;
height: 100%;
}
}
.logo-row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-around;
align-content: center;
margin: 10px 0;
}
.logo-row svg {
height: 40px;
}
@media (min-width: 370px) {
.logo-row svg {
margin: 5px 15px;
}
}
@media (min-width: 480px) {
.logo-row svg {
height: 25px;
margin: 5px;
}
}
@media (min-width: 550px) {
.logo-row {
justify-content: space-between;
}
.logo-row svg {
height: 40px;
}
}
.cta-landing-section h2 {
font-size: 1.3rem;
font-weight: 400;
}
/* testimonials */
.testimonials-row {
display: flex;
flex-direction: column;
justify-content: space-around;
flex-wrap: wrap;
align-items: center;
}
.testimonials p,
.testimonials strong {
font-family: 'Lato', sans-serif;
font-size: 1.1rem;
margin: 0;
color: var(--gray-75);
}
.testimonial-meta p:last-child {
margin-top: 15px;
}
.testimonial-card {
max-width: 350px;
display: flex;
flex-direction: column;
margin: 10px 10px 50px 10px;
background: var(--gray-00);
-webkit-box-shadow: 0 3px 13px 1px rgba(0, 0, 0, 0.09);
box-shadow: 0 3px 13px 1px rgba(0, 0, 0, 0.09);
}
.testimonial-image,
.landing-page-image {
width: 100%;
}
.testimonial-meta {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 10px 0px 30px 0px;
}
.testimonial-meta p,
.testimonial-meta strong {
font-size: 1.2rem;
}
.testimonial-card-header {
height: 100%;
padding-bottom: 100%;
position: relative;
}
.testimonial-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: auto;
}
.testimonials-footer {
display: flex;
flex-direction: column;
padding: 25px;
text-align: justify;
justify-content: center;
}
.landing-top,
.as-seen-in,
.certification-section,
.testimonials {
padding: 4vw 0;
}
@media (min-width: 500px) {
.cta-landing-section h2 {
font-size: 1.5rem;
}
}
@media (min-width: 992px) {
.testimonial-card {
flex-direction: row;
width: auto;
max-width: none;
}
.testimonial-image {
width: auto;
height: 100%;
}
.testimonial-meta {
padding: 0px 0px 30px;
}
.testimonial-meta p,
.testimonial-meta strong {
font-size: 1.3rem;
}
.testimonials-footer {
padding: 40px;
}
.testimonial-card-header {
height: 350px;
min-width: 350px;
padding-bottom: 0;
}
}

View File

@@ -0,0 +1,48 @@
import React from 'react';
export default {
Shawn: {
name: 'Shawn Wang',
country: 'Singapore',
position: 'Software Engineer',
company: 'Amazon',
testimony: (
<p>
"It's scary to change careers. I only gained confidence that I could
code by working through the hundreds of hours of free lessons on
freeCodeCamp. Within a year I had a six-figure job as a Software
Engineer.
<strong> freeCodeCamp changed my life.</strong>"
</p>
)
},
Sarah: {
name: 'Sarah Chima',
country: 'Nigeria',
position: 'Software Engineer',
company: 'ChatDesk',
testimony: (
<p>
<strong>freeCodeCamp was the gateway to my career </strong>
as a software developer. The well-structured curriculum took my coding
knowledge from a total beginner level to a very confident level. It was
everything I needed to land my first dev job at an amazing company."
</p>
)
},
Emma: {
name: 'Emma Bostian',
country: 'Sweden',
position: 'Software Engineer',
company: 'Spotify',
testimony: (
<p>
"I've always struggled with learning JavaScript. I've taken many courses
but freeCodeCamp's course was the one which stuck. Studying JavaScript
as well as data structures and algorithms on{' '}
<strong>freeCodeCamp gave me the skills</strong> and confidence I needed
to land my dream job as a software engineer at Spotify."
</p>
)
}
};

View File

@@ -11,7 +11,7 @@ body {
.btn-cta-big {
max-height: 100%;
font-size: 1.3rem;
font-size: 1.5rem;
white-space: normal;
width: 100%;
}
@@ -64,25 +64,13 @@ p {
}
.big-heading {
font-size: 1.5rem !important;
font-size: 2.5rem !important;
overflow-wrap: break-word;
}
.medium-heading {
font-size: 1.25rem !important;
font-weight: normal;
}
.medium-heading.text-center {
margin-bottom: 50px;
}
@media (max-width: 500px) {
.big-heading {
font-size: 1.2rem !important;
}
.medium-heading {
font-size: 1.1rem !important;
font-size: 1.5rem !important;
}
h1 {
font-size: 1.3rem;
@@ -99,6 +87,12 @@ p {
}
}
@media (max-width: 1199px) {
.btn-cta-big {
font-size: 1.3rem;
}
}
.text-center {
text-align: center !important;
}
@@ -160,6 +154,7 @@ input[type='submit'],
color: var(--secondary-color);
border-radius: 0px;
text-decoration: none;
white-space: pre-line;
}
button:hover,
@@ -257,6 +252,33 @@ fieldset[disabled] .btn-primary.focus {
color: inherit;
}
.link-btn {
margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
font-size: 1rem;
text-align: left;
}
.link-btn.btn-lg svg {
height: 100%;
min-height: 20px;
min-width: 16px;
margin-left: 5px;
}
.link-btn:hover svg {
fill: var(--quaternary-background);
}
@media (min-width: 700px) {
.link-btn {
font-size: 1.1rem;
}
}
.button-group .btn:not(:last-child) {
margin-bottom: 10px;
}
@@ -494,27 +516,3 @@ blockquote .small {
#search::placeholder {
color: var(--secondary-color);
}
/* checkbox */
.checkbox-inline input[type='checkbox'] {
margin-left: -30px;
}
.checkbox-inline {
padding-left: 30px;
}
input[type='checkbox'] {
height: 21px;
width: 21px;
}
/* general page styling */
.default-page-wrapper {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
padding: 20px;
}