From f09732fdc6a261086fde0f27db26bd2039dd2fbd Mon Sep 17 00:00:00 2001 From: Ahmad Abdolsaheb Date: Thu, 12 Jun 2025 12:47:14 +0300 Subject: [PATCH] feat(client): add superblock intro ab test (#60685) Co-authored-by: Oliver Eyton-Williams Co-authored-by: Shaun Hamilton --- client/i18n/locales/english/translations.json | 10 +- client/src/assets/icons/dumbbell.tsx | 49 ++++++ .../components/super-block-accordion.css | 59 ++++++++ .../components/super-block-intro.tsx | 142 +++++++++++++++++- 4 files changed, 252 insertions(+), 8 deletions(-) create mode 100644 client/src/assets/icons/dumbbell.tsx diff --git a/client/i18n/locales/english/translations.json b/client/i18n/locales/english/translations.json index ab52e0cea29..2f270ef0552 100644 --- a/client/i18n/locales/english/translations.json +++ b/client/i18n/locales/english/translations.json @@ -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", diff --git a/client/src/assets/icons/dumbbell.tsx b/client/src/assets/icons/dumbbell.tsx new file mode 100644 index 00000000000..c01c5359b1f --- /dev/null +++ b/client/src/assets/icons/dumbbell.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +function DumbbellIcon( + props: JSX.IntrinsicAttributes & React.SVGProps +): JSX.Element { + return ( + + + + + + + + ); +} + +DumbbellIcon.displayName = 'DumbbellIcon'; + +export default DumbbellIcon; diff --git a/client/src/templates/Introduction/components/super-block-accordion.css b/client/src/templates/Introduction/components/super-block-accordion.css index 667e0f74d6f..3f2d70fa1f6 100644 --- a/client/src/templates/Introduction/components/super-block-accordion.css +++ b/client/src/templates/Introduction/components/super-block-accordion.css @@ -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; + } +} diff --git a/client/src/templates/Introduction/components/super-block-intro.tsx b/client/src/templates/Introduction/components/super-block-intro.tsx index a51d1aa3971..1651e3b757e 100644 --- a/client/src/templates/Introduction/components/super-block-intro.tsx +++ b/client/src/templates/Introduction/components/super-block-intro.tsx @@ -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; + +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(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 = ( <>

{i18nSuperBlock} @@ -99,6 +159,74 @@ function SuperBlockIntro(props: SuperBlockIntroProps): JSX.Element { {superBlockNoteText} )} + + ); + + const introTopB = ( + <> + + +

+ {i18nSuperBlock} +

+ +

{t('misc.fsd-b-description')}

+ {superBlockNoteText && ( +
+ {superBlockNoteText} +
+ )} + + + {t('misc.fsd-b-cta')} + + + + + + +
+ +
+

{t('misc.fsd-b-benefit-1-title')}

+

{t('misc.fsd-b-benefit-1-description')}

+
+
+
+ +
+

{t('misc.fsd-b-benefit-2-title')}

+

{t('misc.fsd-b-benefit-2-description')}

+
+
+
+ +
+

{t('misc.fsd-b-benefit-3-title')}

+

{t('misc.fsd-b-benefit-3-description')}

+
+
+ +
+
+
+ + + ); + + const showFSDnewIntro = useFeatureIsOn('fsd-new-intro'); + + const showIntroTopB = completedChallenges.length === 0 && showFSDnewIntro; + + return ( + <> + {showIntroTopB ? introTopB : introTopA}