refactor(client): replace Map's static query with page queries (#55792)
parent
7345989917
commit
8c0b459c2b
|
@ -1,5 +1,4 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import { graphql, useStaticQuery } from 'gatsby';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
|
@ -27,11 +26,7 @@ import {
|
|||
|
||||
import { RibbonIcon } from '../../assets/icons/completion-ribbon';
|
||||
|
||||
import {
|
||||
CurrentCert,
|
||||
ClaimedCertifications,
|
||||
AllChallengeNode
|
||||
} from '../../redux/prop-types';
|
||||
import { CurrentCert, ClaimedCertifications } from '../../redux/prop-types';
|
||||
import {
|
||||
certSlugTypeMap,
|
||||
superBlockCertTypeMap
|
||||
|
@ -44,6 +39,10 @@ interface MapProps {
|
|||
currentCerts: CurrentCert[];
|
||||
claimedCertifications?: ClaimedCertifications;
|
||||
completedChallengeIds: string[];
|
||||
allChallenges: {
|
||||
id: string;
|
||||
superBlock: SuperBlocks;
|
||||
}[];
|
||||
}
|
||||
|
||||
const linkSpacingStyle = {
|
||||
|
@ -131,29 +130,9 @@ function Map({
|
|||
forLanding = false,
|
||||
isSignedIn,
|
||||
currentCerts,
|
||||
completedChallengeIds
|
||||
completedChallengeIds,
|
||||
allChallenges
|
||||
}: MapProps): React.ReactElement {
|
||||
const {
|
||||
allChallengeNode: { edges }
|
||||
}: {
|
||||
allChallengeNode: AllChallengeNode;
|
||||
} = useStaticQuery(graphql`
|
||||
query allChallenges {
|
||||
allChallengeNode {
|
||||
edges {
|
||||
node {
|
||||
challenge {
|
||||
id
|
||||
superBlock
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const allChallenges = edges.map(edge => edge.node.challenge);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const allSuperblockChallengesCompleted = (superblock: SuperBlocks) => {
|
||||
|
|
|
@ -3,9 +3,17 @@ import { Col } from '@freecodecamp/ui';
|
|||
|
||||
import Map from '../../Map/index';
|
||||
import { Spacer } from '../../helpers';
|
||||
import { type SuperBlocks } from '../../../../../shared/config/curriculum';
|
||||
import BigCallToAction from './big-call-to-action';
|
||||
|
||||
const Certifications = (): JSX.Element => {
|
||||
const Certifications = ({
|
||||
allChallenges
|
||||
}: {
|
||||
allChallenges: {
|
||||
id: string;
|
||||
superBlock: SuperBlocks;
|
||||
}[];
|
||||
}): JSX.Element => {
|
||||
return (
|
||||
<Col
|
||||
className='certification-section'
|
||||
|
@ -15,7 +23,7 @@ const Certifications = (): JSX.Element => {
|
|||
smOffset={1}
|
||||
xs={12}
|
||||
>
|
||||
<Map forLanding={true} />
|
||||
<Map allChallenges={allChallenges} forLanding={true} />
|
||||
<Spacer size='medium' />
|
||||
<BigCallToAction />
|
||||
<Spacer size='medium' />
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
import React, { ReactElement } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useGrowthBook } from '@growthbook/growthbook-react';
|
||||
import SEO from '../seo';
|
||||
import { Loader } from '../helpers';
|
||||
import AsSeenIn from './components/as-seen-in';
|
||||
import Certifications from './components/certifications';
|
||||
import LandingTop from './components/landing-top';
|
||||
import LandingTopB from './components/landing-top-b';
|
||||
import Testimonials from './components/testimonials';
|
||||
import Faq from './components/faq';
|
||||
|
||||
import './landing.css';
|
||||
|
||||
const LandingA = () => (
|
||||
<main className='landing-page'>
|
||||
<LandingTop />
|
||||
<AsSeenIn />
|
||||
<Testimonials />
|
||||
<Certifications />
|
||||
<Faq />
|
||||
</main>
|
||||
);
|
||||
|
||||
const LandingB = () => (
|
||||
<main className='landing-page landing-page-b'>
|
||||
<LandingTopB />
|
||||
<Testimonials />
|
||||
<Certifications />
|
||||
<Faq />
|
||||
</main>
|
||||
);
|
||||
|
||||
function Landing(): ReactElement {
|
||||
const { t } = useTranslation();
|
||||
const growthbook = useGrowthBook();
|
||||
if (growthbook && growthbook.ready) {
|
||||
const showLandingPageRedesign = growthbook.getFeatureValue(
|
||||
'landing-page-redesign',
|
||||
false
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<SEO title={t('metaTags:title')} />
|
||||
{showLandingPageRedesign === true ? <LandingB /> : <LandingA />}
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<SEO title={t('metaTags:title')} />
|
||||
<Loader fullScreen={true} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Landing.displayName = 'Landing';
|
||||
export default Landing;
|
|
@ -1,30 +0,0 @@
|
|||
import React from 'react';
|
||||
import { createRenderer } from 'react-test-renderer/shallow';
|
||||
|
||||
import mockChallengeNodes from '../../__mocks__/challenge-nodes';
|
||||
import IndexPage from '../../pages';
|
||||
|
||||
jest.mock('../../analytics');
|
||||
|
||||
describe('<Landing />', () => {
|
||||
it('renders when visiting index page and logged out', () => {
|
||||
const utils = createRenderer();
|
||||
// @ts-expect-error Type definition mismatch
|
||||
utils.render(<IndexPage {...loggedOutProps} />);
|
||||
const view = utils.getRenderOutput();
|
||||
// @ts-expect-error Type definition mismatch
|
||||
expect(view.type.displayName === 'Landing').toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
const loggedOutProps = {
|
||||
data: { allChallengeNode: { edges: mockChallengeNodes } },
|
||||
fetchState: {
|
||||
complete: true,
|
||||
error: null,
|
||||
errored: false,
|
||||
pending: false
|
||||
},
|
||||
isSignedIn: false,
|
||||
user: {}
|
||||
};
|
|
@ -1,11 +1,103 @@
|
|||
import React from 'react';
|
||||
import { graphql } from 'gatsby';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useGrowthBook } from '@growthbook/growthbook-react';
|
||||
|
||||
import Landing from '../components/landing';
|
||||
import { SuperBlocks } from '../../../shared/config/curriculum';
|
||||
import SEO from '../components/seo';
|
||||
import { Loader } from '../components/helpers';
|
||||
import LandingTop from '../components/landing/components/landing-top';
|
||||
import LandingTopB from '../components/landing/components/landing-top-b';
|
||||
import AsSeenIn from '../components/landing/components/as-seen-in';
|
||||
import Testimonials from '../components/landing/components/testimonials';
|
||||
import Certifications from '../components/landing/components/certifications';
|
||||
import Faq from '../components/landing/components/faq';
|
||||
import '../components/landing/landing.css';
|
||||
|
||||
function IndexPage(): JSX.Element {
|
||||
return <Landing />;
|
||||
type Challenge = {
|
||||
id: string;
|
||||
superBlock: SuperBlocks;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
data: {
|
||||
allChallengeNode: {
|
||||
nodes: {
|
||||
challenge: Challenge;
|
||||
}[];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
type LandingProps = {
|
||||
allChallenges: Challenge[];
|
||||
};
|
||||
|
||||
const LandingA = ({ allChallenges }: LandingProps) => (
|
||||
<main className='landing-page'>
|
||||
<LandingTop />
|
||||
<AsSeenIn />
|
||||
<Testimonials />
|
||||
<Certifications allChallenges={allChallenges} />
|
||||
<Faq />
|
||||
</main>
|
||||
);
|
||||
|
||||
const LandingB = ({ allChallenges }: LandingProps) => (
|
||||
<main className='landing-page landing-page-b'>
|
||||
<LandingTopB />
|
||||
<Testimonials />
|
||||
<Certifications allChallenges={allChallenges} />
|
||||
<Faq />
|
||||
</main>
|
||||
);
|
||||
|
||||
function IndexPage({
|
||||
data: {
|
||||
allChallengeNode: { nodes: challengeNodes }
|
||||
}
|
||||
}: Props): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
const growthbook = useGrowthBook();
|
||||
const allChallenges = challengeNodes.map(node => node.challenge);
|
||||
if (growthbook && growthbook.ready) {
|
||||
const showLandingPageRedesign = growthbook.getFeatureValue(
|
||||
'landing-page-redesign',
|
||||
false
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<SEO title={t('metaTags:title')} />
|
||||
{showLandingPageRedesign === true ? (
|
||||
<LandingB allChallenges={allChallenges} />
|
||||
) : (
|
||||
<LandingA allChallenges={allChallenges} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<SEO title={t('metaTags:title')} />
|
||||
<Loader fullScreen={true} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
IndexPage.displayName = 'IndexPage';
|
||||
|
||||
export default IndexPage;
|
||||
|
||||
export const query = graphql`
|
||||
query AllChallengeNode {
|
||||
allChallengeNode {
|
||||
nodes {
|
||||
challenge {
|
||||
id
|
||||
superBlock
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
} from '../redux/selectors';
|
||||
|
||||
import callGA from '../analytics/call-ga';
|
||||
import { SuperBlocks } from '../../../shared/config/curriculum';
|
||||
|
||||
interface FetchState {
|
||||
pending: boolean;
|
||||
|
@ -57,6 +58,14 @@ interface LearnPageProps {
|
|||
fields: Slug;
|
||||
};
|
||||
};
|
||||
allChallengeNode: {
|
||||
nodes: {
|
||||
challenge: {
|
||||
id: string;
|
||||
superBlock: SuperBlocks;
|
||||
};
|
||||
}[];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -69,7 +78,8 @@ function LearnPage({
|
|||
challenge: {
|
||||
fields: { slug }
|
||||
}
|
||||
}
|
||||
},
|
||||
allChallengeNode: { nodes: challengeNodes }
|
||||
}
|
||||
}: LearnPageProps) {
|
||||
const { t } = useTranslation();
|
||||
|
@ -96,7 +106,7 @@ function LearnPage({
|
|||
onLearnDonationAlertClick={onLearnDonationAlertClick}
|
||||
isDonating={isDonating}
|
||||
/>
|
||||
<Map />
|
||||
<Map allChallenges={challengeNodes.map(node => node.challenge)} />
|
||||
<Spacer size='large' />
|
||||
</Col>
|
||||
</Row>
|
||||
|
@ -110,7 +120,7 @@ LearnPage.displayName = 'LearnPage';
|
|||
export default connect(mapStateToProps)(LearnPage);
|
||||
|
||||
export const query = graphql`
|
||||
query FirstChallenge {
|
||||
query LearnPageQuery {
|
||||
challengeNode(
|
||||
challenge: {
|
||||
superOrder: { eq: 0 }
|
||||
|
@ -124,5 +134,13 @@ export const query = graphql`
|
|||
}
|
||||
}
|
||||
}
|
||||
allChallengeNode {
|
||||
nodes {
|
||||
challenge {
|
||||
id
|
||||
superBlock
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -25,7 +25,8 @@ import {
|
|||
userFetchStateSelector,
|
||||
signInLoadingSelector
|
||||
} from '../../redux/selectors';
|
||||
import { MarkdownRemark, AllChallengeNode, User } from '../../redux/prop-types';
|
||||
import type { AllChallengeNode, User } from '../../redux/prop-types';
|
||||
import { CertTitle } from '../../../config/cert-and-project-map';
|
||||
import Block from './components/block';
|
||||
import CertChallenge from './components/cert-challenge';
|
||||
import LegacyLinks from './components/legacy-links';
|
||||
|
@ -44,7 +45,6 @@ type FetchState = {
|
|||
type SuperBlockProp = {
|
||||
currentChallengeId: string;
|
||||
data: {
|
||||
markdownRemark: MarkdownRemark;
|
||||
allChallengeNode: AllChallengeNode;
|
||||
};
|
||||
expandedState: {
|
||||
|
@ -54,6 +54,11 @@ type SuperBlockProp = {
|
|||
isSignedIn: boolean;
|
||||
signInLoading: boolean;
|
||||
location: WindowLocation<{ breadcrumbBlockClick: string }>;
|
||||
pageContext: {
|
||||
superBlock: SuperBlocks;
|
||||
title: CertTitle;
|
||||
certification: string;
|
||||
};
|
||||
resetExpansion: () => void;
|
||||
toggleBlock: (arg0: string) => void;
|
||||
tryToShowDonationModal: () => void;
|
||||
|
@ -164,17 +169,18 @@ const SuperBlockIntroductionPage = (props: SuperBlockProp) => {
|
|||
|
||||
const {
|
||||
data: {
|
||||
markdownRemark: {
|
||||
frontmatter: { superBlock, title, certification }
|
||||
},
|
||||
allChallengeNode: { edges }
|
||||
},
|
||||
isSignedIn,
|
||||
signInLoading,
|
||||
user
|
||||
user,
|
||||
pageContext: { superBlock, title, certification }
|
||||
} = props;
|
||||
|
||||
const nodesForSuperBlock = edges.map(({ node }) => node);
|
||||
const allChallenges = edges.map(({ node }) => node.challenge);
|
||||
const nodesForSuperBlock = edges
|
||||
.filter(edge => edge.node.challenge.superBlock === superBlock)
|
||||
.map(({ node }) => node);
|
||||
const blockDashedNames = uniq(
|
||||
nodesForSuperBlock.map(({ challenge: { block } }) => block)
|
||||
);
|
||||
|
@ -257,7 +263,7 @@ const SuperBlockIntroductionPage = (props: SuperBlockProp) => {
|
|||
{t(`intro:misc-text.browse-other`)}
|
||||
</h3>
|
||||
<Spacer size='medium' />
|
||||
<Map />
|
||||
<Map allChallenges={allChallenges} />
|
||||
<Spacer size='large' />
|
||||
</Col>
|
||||
</Row>
|
||||
|
@ -276,14 +282,7 @@ export default connect(
|
|||
)(withTranslation()(memo(SuperBlockIntroductionPage)));
|
||||
|
||||
export const query = graphql`
|
||||
query SuperBlockIntroPageBySlug($id: String!, $superBlock: String!) {
|
||||
markdownRemark(id: { eq: $id }) {
|
||||
frontmatter {
|
||||
certification
|
||||
superBlock
|
||||
title
|
||||
}
|
||||
}
|
||||
query SuperBlockIntroPageQuery {
|
||||
allChallengeNode(
|
||||
sort: {
|
||||
fields: [
|
||||
|
@ -292,7 +291,6 @@ export const query = graphql`
|
|||
challenge___challengeOrder
|
||||
]
|
||||
}
|
||||
filter: { challenge: { superBlock: { eq: $superBlock } } }
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
|
|
|
@ -198,8 +198,7 @@ exports.createSuperBlockIntroPages = function (createPage) {
|
|||
return function (edge) {
|
||||
const {
|
||||
fields: { slug },
|
||||
frontmatter: { superBlock, certification },
|
||||
id
|
||||
frontmatter: { superBlock, certification, title }
|
||||
} = edge.node;
|
||||
|
||||
if (!certification) {
|
||||
|
@ -215,8 +214,9 @@ exports.createSuperBlockIntroPages = function (createPage) {
|
|||
path: slug,
|
||||
component: superBlockIntro,
|
||||
context: {
|
||||
id,
|
||||
superBlock
|
||||
certification,
|
||||
superBlock,
|
||||
title
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue