Skip to content

Commit b34291f

Browse files
ahmaxednaomi-lgbthuyenltnguyenojeytonwilliams
authored
feat: add AB test landing page top (freeCodeCamp#55659)
Co-authored-by: Naomi the Technomancer <[email protected]> Co-authored-by: Huyen Nguyen <[email protected]> Co-authored-by: Oliver Eyton-Williams <[email protected]>
1 parent fa818eb commit b34291f

File tree

10 files changed

+1507
-136
lines changed

10 files changed

+1507
-136
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"aa-test": {
3+
"defaultValue": false
4+
},
5+
"aa-test-in-component": {
6+
"defaultValue": false
7+
},
8+
"landing-page-redesign": {
9+
"defaultValue": false,
10+
"rules": [
11+
{
12+
"coverage": 1,
13+
"hashAttribute": "id",
14+
"seed": "landing-page-redesign",
15+
"hashVersion": 2,
16+
"variations": [false, true],
17+
"weights": [0.5, 0.5],
18+
"key": "landing-page-redesign",
19+
"meta": [
20+
{
21+
"key": "0",
22+
"name": "Control"
23+
},
24+
{
25+
"key": "1",
26+
"name": "Variation 1"
27+
}
28+
],
29+
"phase": "0",
30+
"name": "tests the conversion rate of the new design comparing to the old one"
31+
}
32+
]
33+
}
34+
}
35+

client/i18n/locales/english/translations.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"buttons": {
33
"logged-in-cta-btn": "Get started (it's free)",
4+
"get-started": "Get Started",
45
"logged-out-cta-btn": "Sign in to save your progress (it's free)",
56
"view-curriculum": "View the Curriculum",
67
"first-lesson": "Go to the first lesson",
@@ -106,11 +107,15 @@
106107
},
107108
"landing": {
108109
"big-heading-1": "Learn to code — for free.",
110+
"big-heading-1-b": "Learn to code.",
109111
"big-heading-2": "Build projects.",
110112
"big-heading-3": "Earn certifications.",
113+
"big-heading-4": "All for free.",
111114
"h2-heading": "Since 2014, more than 40,000 freeCodeCamp.org graduates have gotten jobs at tech companies including:",
115+
"h2-heading-b": "More than 100,000 freeCodeCamp.org graduates have gotten jobs at tech companies including:",
112116
"hero-img-description": "freeCodeCamp students at a local study group in South Korea.",
113117
"hero-img-alt": "A group of people, including a White man, a Black woman, and an Asian woman, gathered around a laptop.",
118+
"hero-img-uis": "A group of screenshots showing the freeCodeCamp editor interface on both a mobile and desktop device and a certification.",
114119
"as-seen-in": "As seen in:",
115120
"testimonials": {
116121
"heading": "Here is what our alumni say about freeCodeCamp:",

client/src/assets/images/landing/landing-page-b.svg

Lines changed: 987 additions & 0 deletions
Loading

client/src/components/Donation/donation.css

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,30 @@ a.patreon-button:hover {
699699
);
700700
}
701701

702+
.light-palette .gradient-foreground {
703+
background-image: linear-gradient(
704+
-10deg,
705+
rgb(0, 51, 133) 35%,
706+
rgba(237, 202, 216, 0) 75%,
707+
rgb(150, 15, 46) 100%
708+
),
709+
radial-gradient(circle, rgb(157, 1, 69) 0%, rgb(0, 89, 189) 100%);
710+
color: transparent;
711+
background-clip: text;
712+
}
713+
714+
.dark-palette .gradient-foreground {
715+
background-image: linear-gradient(
716+
-10deg,
717+
rgb(223 243 255) 35%,
718+
rgba(237, 202, 216, 0) 75%,
719+
rgb(255 215 224) 100%
720+
),
721+
radial-gradient(circle, rgb(255 139 189) 0%, rgb(187 219 255) 100%);
722+
color: transparent;
723+
background-clip: text;
724+
}
725+
702726
.supporters-background {
703727
background-repeat: repeat;
704728
}

client/src/components/growth-book/growth-book-wrapper.tsx

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
userFetchStateSelector
1313
} from '../../redux/selectors';
1414
import envData from '../../../config/env.json';
15+
import defaultGrowthBookFeatures from '../../../config/growthbook-features-default.json';
1516
import { User, UserFetchState } from '../../redux/prop-types';
1617
import { getUUID } from '../../utils/growthbook-cookie';
1718
import callGA from '../../analytics/call-ga';
@@ -77,17 +78,20 @@ const GrowthBookWrapper = ({
7778

7879
useEffect(() => {
7980
async function setGrowthBookFeatures() {
80-
if (!growthbookUri) return;
81-
82-
try {
83-
const res = await fetch(growthbookUri);
84-
const data = (await res.json()) as {
85-
features: Record<string, FeatureDefinition>;
86-
};
87-
growthbook.setFeatures(data.features);
88-
} catch (e) {
89-
// TODO: report to sentry when it's enabled
90-
console.error(e);
81+
if (!growthbookUri) {
82+
// Defaults are added to facilitate testing, and avoid passing the related env
83+
growthbook.setFeatures(defaultGrowthBookFeatures);
84+
} else {
85+
try {
86+
const res = await fetch(growthbookUri);
87+
const data = (await res.json()) as {
88+
features: Record<string, FeatureDefinition>;
89+
};
90+
growthbook.setFeatures(data.features);
91+
} catch (e) {
92+
// TODO: report to sentry when it's enabled
93+
console.error(e);
94+
}
9195
}
9296
}
9397

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import React from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
import { Container, Col, Row } from '@freecodecamp/ui';
4+
import { clientLocale } from '../../../../config/env.json';
5+
import {
6+
AmazonLogo,
7+
AppleLogo,
8+
MicrosoftLogo,
9+
SpotifyLogo,
10+
GoogleLogo,
11+
TencentLogo,
12+
AlibabaLogo
13+
} from '../../../assets/images/components';
14+
import { Spacer } from '../../helpers';
15+
import BigCallToAction from './big-call-to-action';
16+
import UIImages from './ui-images';
17+
18+
const LogoRow = (): JSX.Element => {
19+
const { t } = useTranslation();
20+
const showChineseLogos = ['chinese', 'chinese-tradition'].includes(
21+
clientLocale
22+
);
23+
24+
return (
25+
<>
26+
<p
27+
className='logo-row-title'
28+
data-playwright-test-label='landing-h2-heading-b'
29+
>
30+
{t('landing.h2-heading-b')}
31+
</p>
32+
<Spacer size='small' />
33+
<ul
34+
className='logo-row'
35+
data-playwright-test-label='brand-logo-container'
36+
>
37+
<AppleLogo />
38+
<GoogleLogo />
39+
<MicrosoftLogo />
40+
{showChineseLogos ? (
41+
<>
42+
<TencentLogo />
43+
<AlibabaLogo />
44+
</>
45+
) : (
46+
<>
47+
<SpotifyLogo />
48+
<AmazonLogo />
49+
</>
50+
)}
51+
</ul>
52+
</>
53+
);
54+
};
55+
56+
function LandingTop(): JSX.Element {
57+
const { t } = useTranslation();
58+
59+
return (
60+
<Container fluid={true} className='gradient-container'>
61+
<Container className='landing-top'>
62+
<Row>
63+
<Spacer size='medium' />
64+
<Col lg={6} sm={12} xs={12}>
65+
<h1
66+
id='content-start'
67+
className='mega-heading'
68+
data-test-label='landing-header'
69+
>
70+
{t('landing.big-heading-1-b')}
71+
</h1>
72+
<p
73+
className='mega-heading'
74+
data-playwright-test-label='landing-big-heading-2'
75+
>
76+
{t('landing.big-heading-2')}
77+
</p>
78+
<p
79+
className='mega-heading'
80+
data-playwright-test-label='landing-big-heading-3'
81+
>
82+
{t('landing.big-heading-3')}
83+
</p>
84+
<p
85+
className='mega-heading gradient-foreground'
86+
data-playwright-test-label='landing-big-heading-4'
87+
>
88+
{t('landing.big-heading-4')}
89+
</p>
90+
91+
<Spacer size='medium' />
92+
<BigCallToAction text={t('buttons.get-started')} />
93+
</Col>
94+
<Col lg={6}>
95+
<UIImages />
96+
</Col>
97+
</Row>
98+
<Row>
99+
<Spacer size='large' />
100+
<Col xs={12}>
101+
<LogoRow />
102+
</Col>
103+
</Row>
104+
</Container>
105+
</Container>
106+
);
107+
}
108+
109+
LandingTop.displayName = 'LandingTop';
110+
export default LandingTop;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// eslint-disable-next-line filenames-simple/naming-convention
2+
import React from 'react';
3+
import { useTranslation } from 'react-i18next';
4+
import Media from 'react-responsive';
5+
import landingPageb from '../../../assets/images/landing/landing-page-b.svg';
6+
import { LazyImage, Spacer } from '../../helpers';
7+
8+
const LARGE_SCREEN_SIZE = 1200;
9+
10+
function UIImages(): JSX.Element {
11+
const { t } = useTranslation();
12+
13+
return (
14+
<Media minWidth={LARGE_SCREEN_SIZE}>
15+
<figure
16+
className='ui-images'
17+
data-playwright-test-label='landing-page-figure'
18+
>
19+
<LazyImage alt={t('landing.hero-img-uis')} src={landingPageb} />
20+
</figure>
21+
<Spacer size='exLarge' />
22+
</Media>
23+
);
24+
}
25+
26+
UIImages.displayName = 'UIImages';
27+
export default UIImages;

client/src/components/landing/index.tsx

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,58 @@
11
import React, { ReactElement } from 'react';
22
import { useTranslation } from 'react-i18next';
3-
3+
import { useGrowthBook } from '@growthbook/growthbook-react';
44
import SEO from '../seo';
5+
import { Loader } from '../helpers';
56
import AsSeenIn from './components/as-seen-in';
67
import Certifications from './components/certifications';
78
import LandingTop from './components/landing-top';
9+
import LandingTopB from './components/landing-top-b';
810
import Testimonials from './components/testimonials';
911
import Faq from './components/faq';
1012

1113
import './landing.css';
1214

15+
const LandingA = () => (
16+
<main className='landing-page'>
17+
<LandingTop />
18+
<AsSeenIn />
19+
<Testimonials />
20+
<Certifications />
21+
<Faq />
22+
</main>
23+
);
24+
25+
const LandingB = () => (
26+
<main className='landing-page landing-page-b'>
27+
<LandingTopB />
28+
<Testimonials />
29+
<Certifications />
30+
<Faq />
31+
</main>
32+
);
33+
1334
function Landing(): ReactElement {
1435
const { t } = useTranslation();
15-
16-
return (
17-
<>
18-
<SEO title={t('metaTags:title')} />
19-
<main className='landing-page'>
20-
<LandingTop />
21-
<AsSeenIn />
22-
<Testimonials />
23-
<Certifications />
24-
<Faq />
25-
</main>
26-
</>
27-
);
36+
const growthbook = useGrowthBook();
37+
if (growthbook && growthbook.ready) {
38+
const showLandingPageRedesign = growthbook.getFeatureValue(
39+
'landing-page-redesign',
40+
false
41+
);
42+
return (
43+
<>
44+
<SEO title={t('metaTags:title')} />
45+
{showLandingPageRedesign === true ? <LandingB /> : <LandingA />}
46+
</>
47+
);
48+
} else {
49+
return (
50+
<>
51+
<SEO title={t('metaTags:title')} />
52+
<Loader fullScreen={true} />
53+
</>
54+
);
55+
}
2856
}
2957

3058
Landing.displayName = 'Landing';

client/src/components/landing/landing.css

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,3 +214,54 @@ figcaption.caption {
214214
padding: 40px;
215215
}
216216
}
217+
218+
/* AB testing styles */
219+
.landing-page-b .mega-heading {
220+
font-size: 2rem;
221+
margin: 0px 0px 1rem;
222+
font-weight: 700;
223+
line-height: 3rem;
224+
}
225+
226+
@media (min-width: 500px) {
227+
.landing-page-b .mega-heading {
228+
font-size: 3rem;
229+
}
230+
}
231+
232+
.landing-page-b .landing-top .btn-cta-big {
233+
margin: 0px;
234+
min-width: 300px;
235+
max-width: fit-content;
236+
background-image: none;
237+
color: var(--primary-background) !important;
238+
background-color: var(--primary-color);
239+
border-color: var(--primary-color);
240+
padding: 12px;
241+
}
242+
243+
.landing-page-b .landing-top .btn-cta-big:hover,
244+
.landing-page-b .landing-top .btn-cta-big:focus {
245+
background-color: var(--quaternary-color) !important;
246+
}
247+
248+
.landing-page-b .logo-row-title {
249+
font-weight: normal;
250+
}
251+
252+
.landing-page-b {
253+
overflow-x: hidden;
254+
}
255+
256+
figure.ui-images {
257+
position: absolute;
258+
left: 50%;
259+
height: auto;
260+
width: 750px;
261+
top: 3vw;
262+
}
263+
264+
figure.ui-images img {
265+
width: 100%;
266+
height: auto;
267+
}

0 commit comments

Comments
 (0)