feat(client): add superblock intro ab test (#60685)

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
Co-authored-by: Shaun Hamilton <shauhami020@gmail.com>
This commit is contained in:
Ahmad Abdolsaheb
2025-06-12 12:47:14 +03:00
committed by GitHub
parent 4342119e21
commit f09732fdc6
4 changed files with 252 additions and 8 deletions

View File

@@ -796,7 +796,15 @@
"iframe-form-submit-alert": "Normally this form would be submitted! It works. This will be submitted to: {{externalLink}}",
"document-notfound": "document not found",
"slow-load-msg": "Looks like this is taking longer than usual, please try refreshing the page.",
"navigation-warning": "If you leave this page, you will lose your progress. Are you sure?"
"navigation-warning": "If you leave this page, you will lose your progress. Are you sure?",
"fsd-b-description": "This comprehensive course prepares you to become a Certified Full Stack Developer. You'll learn to build complete web applications using HTML, CSS, JavaScript, React, TypeScript, Node.js, Python, and more.",
"fsd-b-cta": "Start Learning",
"fsd-b-benefit-1-title": "100k+ Students",
"fsd-b-benefit-1-description": "Join more than 100k students taking this certification.",
"fsd-b-benefit-2-title": "Professional Certification",
"fsd-b-benefit-2-description": "Prove your skills with an official, verifiable certification.",
"fsd-b-benefit-3-title": "500+ Exercises",
"fsd-b-benefit-3-description": "Solidify your knowledge with plenty of practice."
},
"icons": {
"gold-cup": "Gold Cup",

View File

@@ -0,0 +1,49 @@
import React from 'react';
function DumbbellIcon(
props: JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>
): JSX.Element {
return (
<svg
xmlns='http://www.w3.org/2000/svg'
width='62'
height='55'
viewBox='0 0 62 42'
fill='none'
{...props}
>
<rect y='17.2132' width='61.5396' height='7.69244' fill='#d0d0d5' />
<rect
x='12.8208'
y='0.973633'
width='8.54716'
height='41.0264'
fill='#d0d0d5'
/>
<rect
x='39.3169'
y='0.973633'
width='8.54716'
height='41.0264'
fill='#d0d0d5'
/>
<rect
x='4.14014'
y='5.84572'
width='8.77419'
height='31.2822'
fill='#d0d0d5'
/>
<rect
x='47.0361'
y='5.84572'
width='8.77419'
height='31.2822'
fill='#d0d0d5'
/>
</svg>
);
}
DumbbellIcon.displayName = 'DumbbellIcon';
export default DumbbellIcon;

View File

@@ -139,3 +139,62 @@
.super-block-accordion .coming-soon-module {
font-size: 1.25rem;
}
.full-width-container {
position: relative;
left: 50%;
right: 50%;
margin-left: -50vw;
margin-right: -50vw;
width: 100vw;
}
.super-benefits-container {
background-color: var(--gray-75);
color: var(--gray-15);
padding-inline: 15px;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
}
.super-block-intro-page .btn-cta-big {
max-width: 350px;
}
.super-block-benefits > div {
display: flex;
align-items: flex-start;
gap: 20px;
}
.super-block-benefits p {
margin: 0;
}
.super-block-benefits {
display: grid;
grid-template-columns: 1fr;
gap: 30px;
}
.super-block-benefits svg path,
.super-block-benefits p {
fill: var(--gray-15);
}
.super-block-benefits h3 {
color: var(--gray05);
}
@media screen and (min-width: 700px) {
.super-block-benefits {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 30px;
}
.super-block-benefits svg {
margin-top: -10px;
}
}

View File

@@ -1,21 +1,53 @@
import React from 'react';
import { graphql, useStaticQuery } from 'gatsby';
import { useTranslation, Trans } from 'react-i18next';
import { Alert, Spacer } from '@freecodecamp/ui';
import { Alert, Spacer, Container, Row, Col } from '@freecodecamp/ui';
import { ConnectedProps, connect } from 'react-redux';
import { useFeatureIsOn } from '@growthbook/growthbook-react';
import { SuperBlocks } from '../../../../../shared/config/curriculum';
import { SuperBlockIcon } from '../../../assets/superblock-icon';
import { Link } from '../../../components/helpers';
import CapIcon from '../../../assets/icons/cap';
import DumbbellIcon from '../../../assets/icons/dumbbell';
import CommunityIcon from '../../../assets/icons/community';
import { CompletedChallenge } from '../../../redux/prop-types';
import { completedChallengesSelector } from '../../../redux/selectors';
interface SuperBlockIntroProps {
interface SuperBlockIntroQueryData {
challengeNode: {
challenge: {
fields: {
slug: string;
};
};
};
}
type ReduxProps = ConnectedProps<typeof connector>;
interface ConditionalDonationAlertProps {
superBlock: SuperBlocks;
onCertificationDonationAlertClick: () => void;
isDonating: boolean;
}
interface SuperBlockIntroProps
extends ConditionalDonationAlertProps,
ReduxProps {}
const mapStateToProps = (state: unknown) => ({
completedChallenges: completedChallengesSelector(
state
) as CompletedChallenge[]
});
const connector = connect(mapStateToProps);
export const ConditionalDonationAlert = ({
superBlock,
onCertificationDonationAlertClick,
isDonating
}: SuperBlockIntroProps): JSX.Element | null => {
}: ConditionalDonationAlertProps): JSX.Element | null => {
const { t } = useTranslation();
const betaCertifications: SuperBlocks[] = [];
@@ -63,9 +95,13 @@ export const ConditionalDonationAlert = ({
return null;
};
function SuperBlockIntro(props: SuperBlockIntroProps): JSX.Element {
function SuperBlockIntro({
superBlock,
onCertificationDonationAlertClick,
isDonating,
completedChallenges
}: SuperBlockIntroProps): JSX.Element {
const { t } = useTranslation();
const { superBlock, onCertificationDonationAlertClick, isDonating } = props;
const superBlockIntroObj: {
title: string;
@@ -77,13 +113,37 @@ function SuperBlockIntro(props: SuperBlockIntroProps): JSX.Element {
note: string;
};
const {
challengeNode: {
challenge: {
fields: { slug: firstChallengeSlug }
}
}
} = useStaticQuery<SuperBlockIntroQueryData>(graphql`
query SuperBlockIntroQuery {
challengeNode(
challenge: {
superOrder: { eq: 0 }
order: { eq: 0 }
challengeOrder: { eq: 0 }
}
) {
challenge {
fields {
slug
}
}
}
}
`);
const {
title: i18nSuperBlock,
intro: superBlockIntroText,
note: superBlockNoteText
} = superBlockIntroObj;
return (
const introTopA = (
<>
<h1 id='content-start' className='text-center big-heading'>
{i18nSuperBlock}
@@ -99,6 +159,74 @@ function SuperBlockIntro(props: SuperBlockIntroProps): JSX.Element {
{superBlockNoteText}
</div>
)}
</>
);
const introTopB = (
<>
<SuperBlockIcon className='cert-header-icon' superBlock={superBlock} />
<Spacer size='m' />
<h1 id='content-start' className='text-center big-heading'>
{i18nSuperBlock}
</h1>
<Spacer size='m' />
<p>{t('misc.fsd-b-description')}</p>
{superBlockNoteText && (
<div className='alert alert-info' style={{ marginTop: '2rem' }}>
{superBlockNoteText}
</div>
)}
<Spacer size='s' />
<a
className={'btn-cta-big btn-block signup-btn btn-cta'}
href={firstChallengeSlug}
>
{t('misc.fsd-b-cta')}
</a>
<Spacer size='l' />
<Container
fluid={true}
className='full-width-container super-benefits-container'
>
<Container>
<Row>
<Col xs={12} className='super-block-benefits'>
<div>
<CommunityIcon />
<div className='benefit-text'>
<h3>{t('misc.fsd-b-benefit-1-title')}</h3>
<p>{t('misc.fsd-b-benefit-1-description')}</p>
</div>
</div>
<div>
<CapIcon />
<div className='benefit-text'>
<h3>{t('misc.fsd-b-benefit-2-title')}</h3>
<p>{t('misc.fsd-b-benefit-2-description')}</p>
</div>
</div>
<div>
<DumbbellIcon />
<div className='benefit-text'>
<h3>{t('misc.fsd-b-benefit-3-title')}</h3>
<p>{t('misc.fsd-b-benefit-3-description')}</p>
</div>
</div>
</Col>
</Row>
</Container>
</Container>
<Spacer size='l' />
</>
);
const showFSDnewIntro = useFeatureIsOn('fsd-new-intro');
const showIntroTopB = completedChallenges.length === 0 && showFSDnewIntro;
return (
<>
{showIntroTopB ? introTopB : introTopA}
<ConditionalDonationAlert
superBlock={superBlock}
onCertificationDonationAlertClick={onCertificationDonationAlertClick}
@@ -110,4 +238,4 @@ function SuperBlockIntro(props: SuperBlockIntroProps): JSX.Element {
SuperBlockIntro.displayName = 'SuperBlockIntro';
export default SuperBlockIntro;
export default connector(SuperBlockIntro);