Files
freeCodeCamp/client/src/templates/Introduction/components/super-block-intro.tsx
2025-10-02 12:30:33 -07:00

250 lines
6.8 KiB
TypeScript

import React from 'react';
import { graphql, useStaticQuery } from 'gatsby';
import { useTranslation, Trans } from 'react-i18next';
import { Alert, Spacer, Container, Row, Col, Callout } from '@freecodecamp/ui';
import { ConnectedProps, connect } from 'react-redux';
import { useFeatureIsOn } from '@growthbook/growthbook-react';
import {
archivedSuperBlocks,
SuperBlocks
} from '../../../../../shared-dist/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';
import ArchivedWarning from '../../../components/archived-warning';
interface SuperBlockIntroQueryData {
challengeNode: {
challenge: {
fields: {
slug: string;
};
};
} | null;
}
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
}: ConditionalDonationAlertProps): JSX.Element | null => {
const { t } = useTranslation();
const betaCertifications: SuperBlocks[] = [];
const unfinishedCertifications = [
SuperBlocks.A2English,
SuperBlocks.B1English,
SuperBlocks.A1Spanish,
SuperBlocks.A2Spanish,
SuperBlocks.A2Chinese,
SuperBlocks.A1Chinese,
SuperBlocks.FullStackDeveloper
];
if (!isDonating && betaCertifications.includes(superBlock))
return (
<Alert variant='info' className='annual-donation-alert'>
<p>{t('donate.beta-certification')}</p>
<hr />
<p className='btn-container'>
<Link
className='btn donate-button'
key='donate'
sameTab={false}
to='/donate'
onClick={onCertificationDonationAlertClick}
>
{t('buttons.donate-now')}
</Link>
</p>
</Alert>
);
if (!isDonating && unfinishedCertifications.includes(superBlock))
return (
<Alert variant='info' className='annual-donation-alert'>
<p>
<Trans i18nKey='donate.consider-donating-2'>
<Link className='inline' to='/donate'>
placeholder
</Link>
</Trans>
</p>
</Alert>
);
return null;
};
function SuperBlockIntro({
superBlock,
onCertificationDonationAlertClick,
isDonating,
completedChallenges
}: SuperBlockIntroProps): JSX.Element {
const { t } = useTranslation();
const superBlockIntroObj: {
title: string;
intro: string[];
note: string;
} = t(`intro:${superBlock}`, { returnObjects: true }) as {
title: string;
intro: string[];
note: string;
};
const { challengeNode } = useStaticQuery<SuperBlockIntroQueryData>(graphql`
query SuperBlockIntroQuery {
challengeNode(
challenge: {
superOrder: { eq: 0 }
order: { eq: 0 }
challengeOrder: { eq: 0 }
}
) {
challenge {
fields {
slug
}
}
}
}
`);
const firstChallengeSlug = challengeNode?.challenge?.fields?.slug || '';
const {
title: i18nSuperBlock,
intro: superBlockIntroText,
note: superBlockNoteText
} = superBlockIntroObj;
const introTopA = (
<>
{archivedSuperBlocks.includes(superBlock) && <ArchivedWarning />}
<Spacer size='s' />
<h1 id='content-start' className='text-center big-heading'>
{i18nSuperBlock}
</h1>
<Spacer size='m' />
<SuperBlockIcon className='cert-header-icon' superBlock={superBlock} />
<Spacer size='m' />
{superBlockIntroText.map((str, i) => (
<p dangerouslySetInnerHTML={{ __html: str }} key={i} />
))}
{superBlockNoteText && (
<>
<Spacer size='m' />
<Callout variant='info'>{superBlockNoteText}</Callout>
</>
)}
</>
);
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 && (
<>
<Spacer size='m' />
<Callout variant='info'>{superBlockNoteText}</Callout>
</>
)}
<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 &&
superBlock === SuperBlocks.FullStackDeveloper &&
showFSDnewIntro;
return (
<>
{showIntroTopB ? introTopB : introTopA}
<ConditionalDonationAlert
superBlock={superBlock}
onCertificationDonationAlertClick={onCertificationDonationAlertClick}
isDonating={isDonating}
/>
</>
);
}
SuperBlockIntro.displayName = 'SuperBlockIntro';
export default connector(SuperBlockIntro);