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:
16
client/package-lock.json
generated
16
client/package-lock.json
generated
@ -7310,6 +7310,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
|
||||||
"integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="
|
"integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="
|
||||||
},
|
},
|
||||||
|
"eventlistener": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventlistener/-/eventlistener-0.0.1.tgz",
|
||||||
|
"integrity": "sha1-7Suqu4UiJ68rz4iRUscsY8pTLrg="
|
||||||
|
},
|
||||||
"events": {
|
"events": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
|
||||||
@ -17398,6 +17403,17 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
},
|
},
|
||||||
|
"react-lazy-load": {
|
||||||
|
"version": "3.1.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-lazy-load/-/react-lazy-load-3.1.13.tgz",
|
||||||
|
"integrity": "sha512-eAVNUn3vhNj79Iv04NOCwy/sCLyqDEhL3j9aJKV7VJuRBDg6rCiB+BIWHuG7VXJGCgb//6nX/soR8PTyWRhFvQ==",
|
||||||
|
"requires": {
|
||||||
|
"eventlistener": "0.0.1",
|
||||||
|
"lodash.debounce": "^4.0.0",
|
||||||
|
"lodash.throttle": "^4.0.0",
|
||||||
|
"prop-types": "^15.5.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-lifecycles-compat": {
|
"react-lifecycles-compat": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||||
|
@ -56,6 +56,7 @@
|
|||||||
"react-hotkeys": "^2.0.0",
|
"react-hotkeys": "^2.0.0",
|
||||||
"react-identicons": "^1.1.7",
|
"react-identicons": "^1.1.7",
|
||||||
"react-instantsearch-dom": "^6.0.0-beta.0",
|
"react-instantsearch-dom": "^6.0.0-beta.0",
|
||||||
|
"react-lazy-load": "^3.1.13",
|
||||||
"react-monaco-editor": "^0.31.0",
|
"react-monaco-editor": "^0.31.0",
|
||||||
"react-redux": "^5.0.7",
|
"react-redux": "^5.0.7",
|
||||||
"react-reflex": "^3.0.18",
|
"react-reflex": "^3.0.18",
|
||||||
|
34
client/src/assets/icons/LinkButton.js
Normal file
34
client/src/assets/icons/LinkButton.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default function LinkButton(props) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
aria-hidden='true'
|
||||||
|
height='20px'
|
||||||
|
version='1.1'
|
||||||
|
viewBox='0 0 16 20'
|
||||||
|
width='18px'
|
||||||
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
xmlnsXlink='http://www.w3.org/1999/xlink'
|
||||||
|
{...props}
|
||||||
|
fill='inherit'
|
||||||
|
>
|
||||||
|
<g>
|
||||||
|
<polygon
|
||||||
|
points={
|
||||||
|
'-2.68014473e-15 -1.06357708e-13 2.01917516 ' +
|
||||||
|
'-1.06357708e-13 8.99824941 9.00746464 2.01917516 ' +
|
||||||
|
'18.0149293 -2.66453526e-15 18.0149293 7.00955027 9'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<polygon
|
||||||
|
points={
|
||||||
|
'7.99971435 -1.06357708e-13 10.0188895 ' +
|
||||||
|
'-1.06357708e-13 16.9979638 9.00746464 ' +
|
||||||
|
'10.0188895 18.0149293 7.99971435 18.0149293 15.0092646 9'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
@ -7,7 +7,7 @@ function SpotifyLogo(props) {
|
|||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
id='amazon-logo'
|
id='amazon-logo'
|
||||||
viewBox='93.907 230.751 1400 572'
|
viewBox='93.907 250 1350 472'
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
xmlnsXlink='http://www.w3.org/1999/xlink'
|
xmlnsXlink='http://www.w3.org/1999/xlink'
|
||||||
{...props}
|
{...props}
|
@ -7,7 +7,7 @@ function AppleLogo(props) {
|
|||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
id='apple-logo'
|
id='apple-logo'
|
||||||
viewBox='170 0 1000 572'
|
viewBox='450 0 500 650'
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
xmlnsXlink='http://www.w3.org/1999/xlink'
|
xmlnsXlink='http://www.w3.org/1999/xlink'
|
||||||
{...props}
|
{...props}
|
@ -1,17 +1,19 @@
|
|||||||
/* eslint-disable max-len */
|
/* eslint-disable max-len */
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
const propTypes = {};
|
const propTypes = { fill: PropTypes.string };
|
||||||
|
|
||||||
function AsSeenLogo(props) {
|
function AsSeenLogo(props) {
|
||||||
|
const fill = props.fill === 'dark' ? 'var(--gray-75)' : 'var(--gray-15)';
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
id='featured-logos'
|
id='featured-logos'
|
||||||
viewBox='0 0 1700 340'
|
viewBox='0 40 1700 200'
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
xmlnsXlink='http://www.w3.org/1999/xlink'
|
xmlnsXlink='http://www.w3.org/1999/xlink'
|
||||||
{...props}
|
{...props}
|
||||||
fill='var(--quaternary-color)'
|
fill={fill}
|
||||||
>
|
>
|
||||||
<defs>
|
<defs>
|
||||||
<path
|
<path
|
@ -7,7 +7,7 @@ function GoogleLogo(props) {
|
|||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
id='google-logo'
|
id='google-logo'
|
||||||
viewBox='1635 200 1400 600'
|
viewBox='1635 200 1185 600'
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
xmlnsXlink='http://www.w3.org/1999/xlink'
|
xmlnsXlink='http://www.w3.org/1999/xlink'
|
||||||
{...props}
|
{...props}
|
@ -7,7 +7,7 @@ function MicrosoftLogo(props) {
|
|||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
id='microsoft-logo'
|
id='microsoft-logo'
|
||||||
viewBox='939.813 1145 1400 572'
|
viewBox='939.813 1185 1400 472'
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
xmlnsXlink='http://www.w3.org/1999/xlink'
|
xmlnsXlink='http://www.w3.org/1999/xlink'
|
||||||
{...props}
|
{...props}
|
@ -7,7 +7,7 @@ function SpotifyLogo(props) {
|
|||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
id='spotify-logo'
|
id='spotify-logo'
|
||||||
viewBox='3018 235.379 1579 572'
|
viewBox='3018 235.379 1525 572'
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
xmlnsXlink='http://www.w3.org/1999/xlink'
|
xmlnsXlink='http://www.w3.org/1999/xlink'
|
||||||
{...props}
|
{...props}
|
BIN
client/src/assets/images/landing/Emma.png
Normal file
BIN
client/src/assets/images/landing/Emma.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 82 KiB |
BIN
client/src/assets/images/landing/Sarah.png
Normal file
BIN
client/src/assets/images/landing/Sarah.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 63 KiB |
BIN
client/src/assets/images/landing/Shawn.png
Normal file
BIN
client/src/assets/images/landing/Shawn.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 63 KiB |
BIN
client/src/assets/images/landing/wide-image.png
Normal file
BIN
client/src/assets/images/landing/wide-image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 331 KiB |
@ -23,8 +23,6 @@ import {
|
|||||||
} from '../redux';
|
} from '../redux';
|
||||||
import { Spacer, Loader, FullWidthRow } from '../components/helpers';
|
import { Spacer, Loader, FullWidthRow } from '../components/helpers';
|
||||||
|
|
||||||
import './showuser.css';
|
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
email: PropTypes.string,
|
email: PropTypes.string,
|
||||||
isSignedIn: PropTypes.bool,
|
isSignedIn: PropTypes.bool,
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
/* remove bootstrap margin*/
|
|
||||||
.row {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
@ -43,7 +43,7 @@ function Intro({
|
|||||||
<Row>
|
<Row>
|
||||||
<Col sm={10} smOffset={1} xs={12}>
|
<Col sm={10} smOffset={1} xs={12}>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<h1 className='text-center big-heading'>
|
<h1 className='text-center '>
|
||||||
{name ? `Welcome back, ${name}.` : `Welcome to freeCodeCamp.org`}
|
{name ? `Welcome back, ${name}.` : `Welcome to freeCodeCamp.org`}
|
||||||
</h1>
|
</h1>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
@ -101,9 +101,7 @@ function Intro({
|
|||||||
<Row>
|
<Row>
|
||||||
<Col sm={8} smOffset={2} xs={12}>
|
<Col sm={8} smOffset={2} xs={12}>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<h1 className='big-heading'>
|
<h1>Welcome to freeCodeCamp's curriculum.</h1>
|
||||||
Welcome to freeCodeCamp's curriculum.
|
|
||||||
</h1>
|
|
||||||
<Spacer size={1} />
|
<Spacer size={1} />
|
||||||
</Col>
|
</Col>
|
||||||
<IntroDescription />
|
<IntroDescription />
|
||||||
|
@ -9,14 +9,6 @@
|
|||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo-row {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: space-around;
|
|
||||||
align-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo-row h2 {
|
.logo-row h2 {
|
||||||
height: 35px;
|
height: 35px;
|
||||||
padding: 0 10px 0 10px;
|
padding: 0 10px 0 10px;
|
||||||
@ -27,11 +19,6 @@
|
|||||||
padding-left: 0px;
|
padding-left: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#learn-app-wrapper h2.medium-heading {
|
|
||||||
margin-bottom: 50px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quote-partial .blockquote {
|
.quote-partial .blockquote {
|
||||||
font-size: 1.3rem;
|
font-size: 1.3rem;
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -15,7 +15,7 @@ function CurrentChallengeLink({ children, isLargeBtn }) {
|
|||||||
if (isLargeBtn) {
|
if (isLargeBtn) {
|
||||||
classNames = 'btn btn-lg btn-primary btn-block';
|
classNames = 'btn btn-lg btn-primary btn-block';
|
||||||
} else {
|
} else {
|
||||||
classNames = 'btn btn-cta-big btn-primary btn-block';
|
classNames = 'btn btn-primary btn-block';
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<a className={classNames} href={`${apiLocation}${currentChallengeApi}`}>
|
<a className={classNames} href={`${apiLocation}${currentChallengeApi}`}>
|
||||||
|
65
client/src/components/helpers/ImageLoader.js
Normal file
65
client/src/components/helpers/ImageLoader.js
Normal 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;
|
23
client/src/components/helpers/image-loader.css
Normal file
23
client/src/components/helpers/image-loader.css
Normal 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;
|
||||||
|
}
|
@ -6,3 +6,4 @@ export { default as SkeletonSprite } from './SkeletonSprite';
|
|||||||
export { default as Spacer } from './Spacer';
|
export { default as Spacer } from './Spacer';
|
||||||
export { default as Link } from './Link';
|
export { default as Link } from './Link';
|
||||||
export { default as CurrentChallengeLink } from './CurrentChallengeLink';
|
export { default as CurrentChallengeLink } from './CurrentChallengeLink';
|
||||||
|
export { default as ImageLoader } from './ImageLoader';
|
||||||
|
18
client/src/components/landing/components/AsSeenIn.js
Normal file
18
client/src/components/landing/components/AsSeenIn.js
Normal 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;
|
19
client/src/components/landing/components/BigCallToAction.js
Normal file
19
client/src/components/landing/components/BigCallToAction.js
Normal 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;
|
47
client/src/components/landing/components/Certifications.js
Normal file
47
client/src/components/landing/components/Certifications.js
Normal 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;
|
@ -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;
|
|
78
client/src/components/landing/components/LandingTop.js
Normal file
78
client/src/components/landing/components/LandingTop.js
Normal 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;
|
61
client/src/components/landing/components/Testimonials.js
Normal file
61
client/src/components/landing/components/Testimonials.js
Normal 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;
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,108 +1,49 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
|
import { Grid } from '@freecodecamp/react-bootstrap';
|
||||||
import { Grid, Row, Col } from '@freecodecamp/react-bootstrap';
|
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Link } from 'gatsby';
|
import { graphql, useStaticQuery } from 'gatsby';
|
||||||
import { uniq, partition } from 'lodash';
|
|
||||||
import { Spacer } from '../helpers';
|
import Testimonials from './components/Testimonials';
|
||||||
import Login from '../Header/components/Login';
|
import LandingTop from './components/LandingTop';
|
||||||
import CompanyLogos from './components/CompanyLogos';
|
import Certifications from './components/Certifications';
|
||||||
import { AsFeatureLogo } from '../../assets/images';
|
import AsSeenIn from './components/AsSeenIn';
|
||||||
|
|
||||||
import './landing.css';
|
import './landing.css';
|
||||||
import '../Map/map.css';
|
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
nodes: PropTypes.array
|
page: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
const BigCallToAction = () => (
|
export const Landing = ({ page = 'landing' }) => {
|
||||||
<Row>
|
const data = useStaticQuery(graphql`
|
||||||
<Col md={6} mdOffset={3} sm={8} smOffset={2} xs={12}>
|
query certifications {
|
||||||
<Login block={true} data-test-label='landing-big-cta'>
|
challenges: allChallengeNode(
|
||||||
Get started (it's free)
|
filter: { isHidden: { eq: false } }
|
||||||
</Login>
|
sort: { fields: [superOrder, order, challengeOrder] }
|
||||||
</Col>
|
) {
|
||||||
</Row>
|
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 (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>Learn to code at home | freeCodeCamp.org</title>
|
<title>Learn to code at home | freeCodeCamp.org</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<main className='landing-page'>
|
<main className='landing-page'>
|
||||||
<Spacer />
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Row>
|
<LandingTop page={page} />
|
||||||
<Col sm={10} smOffset={1} xs={12}>
|
</Grid>
|
||||||
<h1
|
<Grid fluid={true}>
|
||||||
className='big-heading text-center'
|
<AsSeenIn />
|
||||||
data-test-label='landing-header'
|
</Grid>
|
||||||
>
|
<Grid>
|
||||||
Welcome to freeCodeCamp.org
|
<Testimonials />
|
||||||
</h1>
|
<Certifications nodes={data.challenges.nodes} page={page} />
|
||||||
<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 />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</main>
|
</main>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
@ -9,21 +9,203 @@
|
|||||||
white-space: pre-line;
|
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 {
|
.landing-page ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding-left: 0px;
|
padding-left: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#featured-logos {
|
#featured-logos {
|
||||||
margin-top: 10px;
|
|
||||||
max-width: 600px;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
48
client/src/components/landing/landingMeta.js
Normal file
48
client/src/components/landing/landingMeta.js
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
@ -11,7 +11,7 @@ body {
|
|||||||
|
|
||||||
.btn-cta-big {
|
.btn-cta-big {
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
font-size: 1.3rem;
|
font-size: 1.5rem;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@ -64,25 +64,13 @@ p {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.big-heading {
|
.big-heading {
|
||||||
font-size: 1.5rem !important;
|
font-size: 2.5rem !important;
|
||||||
overflow-wrap: break-word;
|
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) {
|
@media (max-width: 500px) {
|
||||||
.big-heading {
|
.big-heading {
|
||||||
font-size: 1.2rem !important;
|
font-size: 1.5rem !important;
|
||||||
}
|
|
||||||
.medium-heading {
|
|
||||||
font-size: 1.1rem !important;
|
|
||||||
}
|
}
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 1.3rem;
|
font-size: 1.3rem;
|
||||||
@ -99,6 +87,12 @@ p {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1199px) {
|
||||||
|
.btn-cta-big {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.text-center {
|
.text-center {
|
||||||
text-align: center !important;
|
text-align: center !important;
|
||||||
}
|
}
|
||||||
@ -160,6 +154,7 @@ input[type='submit'],
|
|||||||
color: var(--secondary-color);
|
color: var(--secondary-color);
|
||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
white-space: pre-line;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover,
|
button:hover,
|
||||||
@ -257,6 +252,33 @@ fieldset[disabled] .btn-primary.focus {
|
|||||||
color: inherit;
|
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) {
|
.button-group .btn:not(:last-child) {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
@ -494,27 +516,3 @@ blockquote .small {
|
|||||||
#search::placeholder {
|
#search::placeholder {
|
||||||
color: var(--secondary-color);
|
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;
|
|
||||||
}
|
|
||||||
|
@ -1,16 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { graphql } from 'gatsby';
|
|
||||||
|
|
||||||
import Landing from '../components/landing';
|
import Landing from '../components/landing';
|
||||||
import { AllChallengeNode } from '../redux/propTypes';
|
import { AllChallengeNode } from '../redux/propTypes';
|
||||||
|
|
||||||
export const IndexPage = ({
|
export const IndexPage = () => {
|
||||||
data: {
|
return <Landing />;
|
||||||
allChallengeNode: { nodes }
|
|
||||||
}
|
|
||||||
}) => {
|
|
||||||
return <Landing nodes={nodes} />;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
@ -23,16 +18,3 @@ IndexPage.propTypes = propTypes;
|
|||||||
IndexPage.displayName = 'IndexPage';
|
IndexPage.displayName = 'IndexPage';
|
||||||
|
|
||||||
export default IndexPage;
|
export default IndexPage;
|
||||||
|
|
||||||
export const query = graphql`
|
|
||||||
query MyQuery {
|
|
||||||
allChallengeNode(
|
|
||||||
filter: { isHidden: { eq: false } }
|
|
||||||
sort: { fields: [superOrder, order, challengeOrder] }
|
|
||||||
) {
|
|
||||||
nodes {
|
|
||||||
superBlock
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
@ -32,7 +32,7 @@ export default function layoutSelector({ element, props }) {
|
|||||||
</DefaultLayout>
|
</DefaultLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (/^\/donation(\/.*)*|^\/donate(\/.*)*/.test(pathname)) {
|
if (/^\/donation(\/.*)*|^\/$|^\/donate(\/.*)*/.test(pathname)) {
|
||||||
return (
|
return (
|
||||||
<DefaultLayout pathname={pathname} useTheme={false}>
|
<DefaultLayout pathname={pathname} useTheme={false}>
|
||||||
{element}
|
{element}
|
||||||
|
@ -1,16 +1,41 @@
|
|||||||
/* global cy */
|
/* global cy */
|
||||||
const selectors = {
|
const selectors = {
|
||||||
heading: "[data-test-label='landing-header']",
|
heading: "[data-test-label='landing-header']",
|
||||||
callToAction: "[data-test-label='landing-big-cta']"
|
callToAction: "[data-test-label='landing-big-cta']",
|
||||||
|
certifications: "[data-test-label='certifications']",
|
||||||
|
testimonials: "[data-test-label='testimonial-cards']",
|
||||||
|
landingPageImage: '.landing-page-image'
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('Landing page', function() {
|
describe('Landing page', function() {
|
||||||
it('renders', function() {
|
it('renders', function() {
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
|
|
||||||
cy.title().should('eq', 'Learn to code at home | freeCodeCamp.org');
|
cy.title().should('eq', 'Learn to code at home | freeCodeCamp.org');
|
||||||
|
cy.contains(selectors.heading, 'Learn to code at home.');
|
||||||
cy.contains(selectors.heading, 'Welcome to freeCodeCamp.org');
|
|
||||||
cy.contains(selectors.callToAction, "Get started (it's free)");
|
cy.contains(selectors.callToAction, "Get started (it's free)");
|
||||||
|
cy.get(selectors.callToAction).should('have.length', 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has a visible large image on large viewports', function() {
|
||||||
|
cy.viewport(1200, 660)
|
||||||
|
.get(selectors.landingPageImage)
|
||||||
|
.should('be.visible');
|
||||||
|
|
||||||
|
cy.viewport(1199, 660)
|
||||||
|
.get(selectors.landingPageImage)
|
||||||
|
.should('not.be.visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has 10 certifications', function() {
|
||||||
|
cy.get(selectors.certifications)
|
||||||
|
.children()
|
||||||
|
.its('length')
|
||||||
|
.should('eq', 10);
|
||||||
|
});
|
||||||
|
it('has 3 testimonial cards', function() {
|
||||||
|
cy.get(selectors.testimonials)
|
||||||
|
.children()
|
||||||
|
.its('length')
|
||||||
|
.should('eq', 3);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user