Skip to content

Commit b11ba37

Browse files
authored
feat(client): update landing page ab tests (freeCodeCamp#56602)
1 parent 0ab1ff4 commit b11ba37

File tree

14 files changed

+368
-130
lines changed

14 files changed

+368
-130
lines changed

client/config/growthbook-features-default.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,37 @@
6262
"name": "stg show modal randomly"
6363
}
6464
]
65+
},
66+
"show-benefits": {
67+
"defaultValue": false,
68+
"rules": [
69+
{
70+
"coverage": 1,
71+
"hashAttribute": "id",
72+
"seed": "show-benefits",
73+
"hashVersion": 2,
74+
"variations": [
75+
false,
76+
true
77+
],
78+
"weights": [
79+
0.5,
80+
0.5
81+
],
82+
"key": "show-benefits",
83+
"meta": [
84+
{
85+
"key": "0",
86+
"name": "Control"
87+
},
88+
{
89+
"key": "1",
90+
"name": "Variation 1"
91+
}
92+
],
93+
"phase": "0",
94+
"name": "prod-show-benefits"
95+
}
96+
]
6597
}
6698
}

client/i18n/locales/english/translations.json

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@
112112
"big-heading-3": "Earn certifications.",
113113
"big-heading-4": "All for free.",
114114
"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:",
115+
"h2-heading-b": "More than <strong>100,000</strong> freeCodeCamp.org graduates have gotten <strong>jobs</strong> at tech companies including:",
116116
"hero-img-description": "freeCodeCamp students at a local study group in South Korea.",
117117
"hero-img-alt": "A group of people, including a White man, a Black woman, and an Asian woman, gathered around a laptop.",
118118
"hero-img-uis": "A group of screenshots showing the freeCodeCamp editor interface on both a mobile and desktop device and a certification.",
@@ -135,6 +135,29 @@
135135
"testimony": "\"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.\""
136136
}
137137
},
138+
"benefits": {
139+
"heading": "Why learn with freeCodeCamp:",
140+
"list": [
141+
{
142+
"title": "Large Community",
143+
"description": "Join our vibrant learning community of students, alumni, and educators."
144+
},
145+
{
146+
"title": "Free Education",
147+
"description": "Learn from our charity and save money on your education. No paywalls. No hidden costs."
148+
},
149+
{
150+
"title": "Extensive Certifications",
151+
"description": "Earn industry-recognized, verifiable certifications in high-demand technologies."
152+
},
153+
{
154+
"title": "Comprehensive Curriculum",
155+
"description": "Enhance your technical skills with our linear, world-class, project-based curriculum."
156+
}
157+
],
158+
"cta": "Start Learning Now (it's free)"
159+
},
160+
138161
"certification-heading": "Earn free verified certifications in:",
139162
"core-certs-heading": "Earn free verified certifications with freeCodeCamp's core curriculum:",
140163
"learn-english-heading": "Learn English for Developers:",

client/src/assets/icons/cap.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import React from 'react';
2+
function FreeIcon(
3+
props: JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>
4+
): JSX.Element {
5+
return (
6+
<svg
7+
xmlns='http://www.w3.org/2000/svg'
8+
width='61'
9+
height='60'
10+
viewBox='0 0 61 60'
11+
fill='none'
12+
{...props}
13+
>
14+
<path
15+
d='M10.1154 48.7552L10.1154 30.7553L28.1154 42.7553L47.1154 30.7553L47.1154 48.7552C47.1154 48.7552 39.854 59.7899 28.1154 59.7553C16.3769 59.7206 10.1154 48.7552 10.1154 48.7552Z'
16+
fill='black'
17+
/>
18+
<path
19+
d='M53.0108 46.5927L60.0108 46.5927L57.1154 36.5926L57.1154 20.6612L28.1574 0.0826199L0.115235 20.6612L28.1154 38.7553L56.0108 21.3504L56.0108 36.5926L53.0108 46.5927Z'
20+
fill='black'
21+
/>
22+
</svg>
23+
);
24+
}
25+
26+
FreeIcon.displayName = 'FreeIcon';
27+
28+
export default FreeIcon;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import React from 'react';
2+
function CommunityIcon(
3+
props: JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>
4+
): JSX.Element {
5+
return (
6+
<svg
7+
xmlns='http://www.w3.org/2000/svg'
8+
width='60'
9+
height='60'
10+
viewBox='0 0 60 60'
11+
fill='none'
12+
{...props}
13+
>
14+
<path
15+
d='M18.1176 7.11765C18.1176 11.0486 14.9309 14.2353 10.9999 14.2353C7.06896 14.2353 3.88228 11.0486 3.88228 7.11765C3.88228 3.18668 7.06896 0 10.9999 0C14.9309 0 18.1176 3.18668 18.1176 7.11765Z'
16+
fill='black'
17+
/>
18+
<path
19+
d='M48.9999 14.2353C52.9309 14.2353 56.1176 11.0486 56.1176 7.11765C56.1176 3.18668 52.9309 0 48.9999 0C45.069 0 41.8823 3.18668 41.8823 7.11765C41.8823 11.0486 45.069 14.2353 48.9999 14.2353Z'
20+
fill='black'
21+
/>
22+
<path
23+
d='M13 47H0V21.3531C-1.63171e-05 17.8904 2.69272 15.6097 5 14C6.94118 15.4503 8.44322 16 10.9999 16C13.5566 16 15.0588 15.2941 17 14C17.857 14.5978 18.615 14.9334 19.4511 15.814C19.1576 16.8238 19 17.8932 19 19C19 21.574 19.8523 24.0831 21.2851 26.1359C17.7193 28.6696 13 33.1608 13 38.6113V47Z'
24+
fill='black'
25+
/>
26+
<path
27+
d='M40.5488 15.814C41.385 14.9334 42.143 14.5979 43 14C44.9412 15.4503 46.4433 16 49 16C51.5567 16 53.0588 15.2941 55 14C57.3073 15.6097 60 17.8904 60 21.3531V47H47V38.6113C47 33.1608 42.2807 28.6696 38.7149 26.1359C40.1477 24.0831 41 21.574 41 19C41 17.8932 40.8424 16.8238 40.5488 15.814Z'
28+
fill='black'
29+
/>
30+
<path
31+
d='M39.5 19.1851C39.5 24.8102 35.3604 29 29.9999 29C24.6395 29 21 24.8102 21 19.1851C21 13.56 24.6395 10 29.9999 10C35.3604 10 39.5 13.56 39.5 19.1851Z'
32+
fill='black'
33+
/>
34+
<path
35+
d='M15 39.5558C15 34.6007 19.3537 30.3034 22.5 28C25.1471 30.0753 26.5135 31 29.9999 31C33.4863 31 35.3529 29.8518 38 28C41.1463 30.3034 45 34.6007 45 39.5558C45 54.3705 45 60 45 60H15C15 60 15.0001 55.2965 15 39.5558Z'
36+
fill='black'
37+
/>
38+
</svg>
39+
);
40+
}
41+
42+
CommunityIcon.displayName = 'CommunityIcon';
43+
44+
export default CommunityIcon;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React from 'react';
2+
function CurriculumIcon(
3+
props: JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>
4+
): JSX.Element {
5+
return (
6+
<svg
7+
xmlns='http://www.w3.org/2000/svg'
8+
width='60'
9+
height='60'
10+
viewBox='0 0 60 60'
11+
fill='none'
12+
{...props}
13+
>
14+
<path
15+
fillRule='evenodd'
16+
clipRule='evenodd'
17+
d='M15 6L0 0V55L15 60L30 55L45 60L60 55L60 0L45 6L30 0L15 6ZM12.5 24C15.1285 24 17.3264 22.1561 17.8707 19.6912C19.8628 19.3554 22.2378 19.0587 24.5904 19.0077C27.4964 18.9447 30.121 19.2649 31.9469 20.1449C32.8296 20.5703 33.4622 21.0962 33.8796 21.7178C34.2893 22.3279 34.5735 23.151 34.5735 24.3188C34.5735 25.1295 34.3378 25.6225 33.9959 25.998C33.6073 26.4247 32.9532 26.8343 31.9219 27.1922C30.0008 27.859 27.4496 28.1393 24.5266 28.4606C24.2102 28.4954 23.8895 28.5306 23.5648 28.5669C20.4252 28.9175 16.8925 29.3676 14.1462 30.6563C12.7329 31.3196 11.4204 32.2504 10.4666 33.5976C9.49867 34.9646 9 36.6199 9 38.5481C9.00001 40.5376 9.92037 42.1227 11.3028 43.285C12.6248 44.3965 14.3792 45.1412 16.2448 45.6607C19.9853 46.7022 24.8335 47.0124 29.5055 46.9996C34.2155 46.9868 38.9077 46.6433 42.4108 46.3054L42.5226 46.2946C43.5201 47.6901 45.1538 48.5997 47 48.5997C50.0376 48.5997 52.5 46.1373 52.5 43.0997C52.5 40.4036 50.5601 38.1607 48 37.6904V24.8L56.0001 19.9998L46 14L46 16V26V37.6904C43.675 38.1175 41.8615 40.0066 41.5481 42.3694C38.175 42.6849 33.8267 42.9878 29.4945 42.9996C24.9166 43.0122 20.5147 42.6974 17.3177 41.8073C15.7146 41.3609 14.5783 40.8131 13.8769 40.2234C13.2359 39.6844 13 39.1595 13 38.5481C13 37.34 13.3005 36.5172 13.7311 35.909C14.1759 35.2809 14.8613 34.7393 15.8455 34.2774C17.8942 33.316 20.7549 32.9056 24.0087 32.5422C24.3375 32.5054 24.6711 32.4692 25.008 32.4326L25.0081 32.4326C27.8155 32.1274 30.8531 31.7972 33.2334 30.9711C34.5996 30.4969 35.9434 29.8002 36.9534 28.6911C38.01 27.5308 38.5735 26.0585 38.5735 24.3188C38.5735 22.4581 38.1061 20.8368 37.2004 19.4879C36.3024 18.1506 35.0559 17.2029 33.6836 16.5416C30.9995 15.2479 27.6109 14.9413 24.5037 15.0087C21.9248 15.0646 19.368 15.3841 17.2573 15.7381C16.3047 14.1007 14.5309 13 12.5 13C9.46243 13 7 15.4624 7 18.5C7 21.5376 9.46243 24 12.5 24Z'
18+
fill='black'
19+
/>
20+
</svg>
21+
);
22+
}
23+
24+
CurriculumIcon.displayName = 'CurriculumIcon';
25+
26+
export default CurriculumIcon;

client/src/assets/icons/free.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React from 'react';
2+
function FreeIcon(
3+
props: JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>
4+
): JSX.Element {
5+
return (
6+
<svg
7+
xmlns='http://www.w3.org/2000/svg'
8+
width='60'
9+
height='60'
10+
viewBox='0 0 60 60'
11+
fill='none'
12+
{...props}
13+
>
14+
<path
15+
fillRule='evenodd'
16+
clipRule='evenodd'
17+
d='M14.9393 17.0607L7.7525 9.87383C2.93461 15.1962 0 22.2554 0 30C0 46.5685 13.4315 60 30 60C37.7446 60 44.8038 57.0654 50.1262 52.2475L42.9393 45.0607L45.0607 42.9393L52.2475 50.1262C57.0654 44.8038 60 37.7446 60 30C60 13.4315 46.5685 0 30 0C22.2554 0 15.1962 2.93461 9.87383 7.75251L17.0607 14.9393L14.9393 17.0607ZM31.5 11.5523V7H28.5V11.5383C26.4603 11.6476 24.1085 12.0266 22.0959 13.1193C20.8041 13.8207 19.6338 14.8258 18.7947 16.244C17.9561 17.6612 17.5 19.4035 17.5 21.5C17.5 23.0594 17.9306 24.3879 18.7277 25.4956C19.5089 26.5812 20.5812 27.3712 21.742 27.9786C23.686 28.9958 26.1584 29.6304 28.5 30.2141V45.4771C26.5399 45.4091 24.7338 45.1643 23.3339 44.4126C22.5029 43.9663 21.8229 43.3427 21.3361 42.4384C20.8415 41.5196 20.5 40.2237 20.5 38.3727H17.5C17.5 40.5854 17.9082 42.3997 18.6946 43.8604C19.4887 45.3357 20.6209 46.3609 21.9146 47.0556C23.9294 48.1375 26.344 48.4138 28.5 48.4802V52H31.5V48.4438C33.764 48.2729 36.1711 47.693 38.17 46.4435C40.7066 44.8578 42.5 42.2345 42.5 38.3727C42.5 34.6878 40.8257 32.2579 38.3954 30.6353C36.4028 29.305 33.8961 28.513 31.5 27.88V14.5609C33.2192 14.7025 35.0055 15.1059 36.4432 15.9274C38.1994 16.9309 39.5 18.5785 39.5 21.5H42.5C42.5 17.4215 40.5506 14.8191 37.9315 13.3226C35.9247 12.1759 33.5735 11.698 31.5 11.5523ZM28.5 14.5437C26.7464 14.6512 24.9593 14.9783 23.5274 15.7557C22.632 16.2419 21.8965 16.893 21.3766 17.7717C20.8561 18.6513 20.5 19.8465 20.5 21.5C20.5 22.4724 20.7568 23.1791 21.1628 23.7434C21.5848 24.3298 22.2312 24.8487 23.1329 25.3205C24.5777 26.0765 26.4192 26.5914 28.5 27.1195V14.5437ZM31.5 30.9894V45.4338C33.349 45.2686 35.1618 44.786 36.5798 43.8997C38.2933 42.8285 39.5 41.1382 39.5 38.3727C39.5 35.8025 38.4243 34.2618 36.7296 33.1303C35.3311 32.1966 33.5267 31.5557 31.5 30.9894Z'
18+
fill='black'
19+
/>
20+
</svg>
21+
);
22+
}
23+
24+
FreeIcon.displayName = 'FreeIcon';
25+
26+
export default FreeIcon;
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import React from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
import { Col, Row, Container } from '@freecodecamp/ui';
4+
5+
import { Spacer } from '../../helpers';
6+
import FreeIcon from '../../../assets/icons/free';
7+
import CapIcon from '../../../assets/icons/cap';
8+
import CommunityIcon from '../../../assets/icons/community';
9+
import CurriculumIcon from '../../../assets/icons/curriculum';
10+
import BigCallToAction from './big-call-to-action';
11+
12+
interface BenefitsItem {
13+
title: string;
14+
description: string;
15+
}
16+
17+
const iconsList = [CommunityIcon, FreeIcon, CapIcon, CurriculumIcon];
18+
19+
const Benefits = (): JSX.Element => {
20+
const { t } = useTranslation();
21+
const benefitItems: BenefitsItem[] = t('landing.benefits.list', {
22+
returnObjects: true
23+
});
24+
25+
return (
26+
<Container fluid={true} className='benefits-container'>
27+
<Container className='benefits'>
28+
<Row>
29+
<Col xs={12}>
30+
<h2 className='big-heading text-center'>
31+
{t('landing.benefits.heading')}
32+
</h2>
33+
</Col>
34+
</Row>
35+
<Spacer size='small' />
36+
<Row>
37+
<Col xs={12} className='landing-benefits'>
38+
{benefitItems.map((benefit, index) => {
39+
const IconComponent = iconsList[index % iconsList.length]; // Get the correct icon component based on index
40+
return (
41+
<div
42+
key={index}
43+
data-playwright-test-label='landing-page-description'
44+
>
45+
<IconComponent /> {/* Dynamically render the icon */}
46+
<Spacer size='small' />
47+
<h3>{benefit.title}</h3>
48+
<p>{benefit.description}</p>
49+
<Spacer size='small' />
50+
</div>
51+
);
52+
})}
53+
</Col>
54+
</Row>
55+
<Row>
56+
<Col xs={12}>
57+
<Spacer size='medium' />
58+
<BigCallToAction text={t('landing.benefits.cta')} />
59+
</Col>
60+
</Row>
61+
</Container>
62+
</Container>
63+
);
64+
};
65+
66+
Benefits.displayName = 'Benefits';
67+
export default Benefits;

client/src/components/landing/components/campers-image.tsx

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,37 +6,16 @@ import { LazyImage } from '../../helpers';
66

77
const LARGE_SCREEN_SIZE = 1200;
88

9-
interface CampersImageProps {
10-
pageName: string;
11-
}
12-
13-
const donateImageSize = {
14-
height: 345,
15-
width: 585
16-
};
17-
18-
const landingImageSize = {
19-
marginTop: '30px',
20-
height: 442,
21-
width: 750
22-
};
23-
function CampersImage({ pageName }: CampersImageProps): JSX.Element {
9+
function CampersImage(): JSX.Element {
2410
const { t } = useTranslation();
2511

26-
const figureSize = pageName === 'donate' ? donateImageSize : landingImageSize;
27-
2812
return (
2913
<Media minWidth={LARGE_SCREEN_SIZE}>
3014
<figure
31-
style={figureSize}
3215
data-test-label='landing-page-figure'
3316
data-playwright-test-label='landing-page-figure'
3417
>
35-
<LazyImage
36-
alt={t('landing.hero-img-alt')}
37-
src={wideImg}
38-
style={figureSize}
39-
/>
18+
<LazyImage alt={t('landing.hero-img-alt')} src={wideImg} />
4019
<figcaption className='caption'>
4120
{t('landing.hero-img-description')}
4221
</figcaption>

client/src/components/landing/components/landing-top-b.tsx

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { useTranslation } from 'react-i18next';
2+
import { Trans, useTranslation } from 'react-i18next';
33
import { Container, Col, Row } from '@freecodecamp/ui';
44
import { clientLocale } from '../../../../config/env.json';
55
import {
@@ -13,10 +13,9 @@ import {
1313
} from '../../../assets/images/components';
1414
import { Spacer } from '../../helpers';
1515
import BigCallToAction from './big-call-to-action';
16-
import UIImages from './ui-images';
16+
import CampersImage from './campers-image';
1717

1818
const LogoRow = (): JSX.Element => {
19-
const { t } = useTranslation();
2019
const showChineseLogos = ['chinese', 'chinese-tradition'].includes(
2120
clientLocale
2221
);
@@ -27,9 +26,8 @@ const LogoRow = (): JSX.Element => {
2726
className='logo-row-title'
2827
data-playwright-test-label='landing-h2-heading-b'
2928
>
30-
{t('landing.h2-heading-b')}
29+
<Trans>landing.h2-heading-b</Trans>
3130
</p>
32-
<Spacer size='small' />
3331
<ul
3432
className='logo-row'
3533
data-playwright-test-label='brand-logo-container'
@@ -58,10 +56,10 @@ function LandingTop(): JSX.Element {
5856

5957
return (
6058
<Container fluid={true} className='gradient-container'>
61-
<Container className='landing-top'>
59+
<Container className='landing-top landing-top-b'>
60+
<Spacer size='medium' />
6261
<Row>
63-
<Spacer size='medium' />
64-
<Col lg={6} sm={12} xs={12}>
62+
<Col lg={8} lgOffset={2} sm={10} smOffset={1} xs={12}>
6563
<h1
6664
id='content-start'
6765
className='mega-heading'
@@ -87,18 +85,15 @@ function LandingTop(): JSX.Element {
8785
>
8886
{t('landing.big-heading-4')}
8987
</p>
90-
88+
<LogoRow />
9189
<Spacer size='medium' />
92-
<BigCallToAction text={t('buttons.get-started')} />
93-
</Col>
94-
<Col lg={6}>
95-
<UIImages />
90+
<BigCallToAction />
9691
</Col>
9792
</Row>
9893
<Row>
99-
<Spacer size='large' />
100-
<Col xs={12}>
101-
<LogoRow />
94+
<Col lg={8} lgOffset={2} sm={10} smOffset={1} xs={12}>
95+
<CampersImage />
96+
<Spacer size='medium' />
10297
</Col>
10398
</Row>
10499
</Container>

client/src/components/landing/components/landing-top.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ function LandingTop(): JSX.Element {
2222
clientLocale
2323
);
2424
return (
25-
<Container className='landing-top'>
25+
<Container className='landing-top landing-top-a'>
2626
<Row>
2727
<Spacer size='medium' />
2828
<Col lg={8} lgOffset={2} sm={10} smOffset={1} xs={12}>
@@ -70,7 +70,7 @@ function LandingTop(): JSX.Element {
7070
</div>
7171
<Spacer size='medium' />
7272
<BigCallToAction />
73-
<CampersImage pageName='landing' />
73+
<CampersImage />
7474
<Spacer size='medium' />
7575
</Col>
7676
</Row>

0 commit comments

Comments
 (0)