diff --git a/client/gatsby-node.js b/client/gatsby-node.js index 9526430448b..a897756686b 100644 --- a/client/gatsby-node.js +++ b/client/gatsby-node.js @@ -4,8 +4,12 @@ const { createFilePath } = require('gatsby-source-filesystem'); const uniq = require('lodash/uniq'); const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); const webpack = require('webpack'); -const env = require('./config/env.json'); +const { + superBlockStages, + SuperBlockStage +} = require('../shared/config/curriculum'); +const env = require('./config/env.json'); const { createChallengePages, createBlockIntroPages, @@ -135,8 +139,54 @@ exports.createPages = async function createPages({ } `); + const allChallengeNodes = result.data.allChallengeNode.edges.map( + ({ node }) => node + ); + + const inNextCurriculum = superBlock => + superBlockStages[SuperBlockStage.Next].includes(superBlock); + + const currentChallengeNodes = allChallengeNodes.filter( + node => !inNextCurriculum(node.challenge.superBlock) + ); + + const createIdToNextPathMap = nodes => + nodes.reduce((map, node, index) => { + const nextNode = nodes[index + 1]; + const nextPath = nextNode ? nextNode.challenge.fields.slug : null; + if (nextPath) map[node.id] = nextPath; + return map; + }, {}); + + const createIdToPrevPathMap = nodes => + nodes.reduce((map, node, index) => { + const prevNode = nodes[index - 1]; + const prevPath = prevNode ? prevNode.challenge.fields.slug : null; + if (prevPath) map[node.id] = prevPath; + return map; + }, {}); + + const idToNextPathCurrentCurriculum = createIdToNextPathMap( + currentChallengeNodes + ); + + const idToPrevPathCurrentCurriculum = createIdToPrevPathMap( + currentChallengeNodes + ); + + const idToNextPathNextCurriculum = createIdToNextPathMap(allChallengeNodes); + + const idToPrevPathNextCurriculum = createIdToPrevPathMap(allChallengeNodes); + // Create challenge pages. - result.data.allChallengeNode.edges.forEach(createChallengePages(createPage)); + result.data.allChallengeNode.edges.forEach( + createChallengePages(createPage, { + idToNextPathCurrentCurriculum, + idToPrevPathCurrentCurriculum, + idToNextPathNextCurriculum, + idToPrevPathNextCurriculum + }) + ); const blocks = uniq( result.data.allChallengeNode.edges.map( diff --git a/client/src/redux/prop-types.ts b/client/src/redux/prop-types.ts index 3dad46649d4..7fef737d7f8 100644 --- a/client/src/redux/prop-types.ts +++ b/client/src/redux/prop-types.ts @@ -404,14 +404,17 @@ export type ChallengeMeta = { id: string; introPath: string; isFirstStep: boolean; - nextChallengePath: string | null; - prevChallengePath: string | null; superBlock: SuperBlocks; title?: string; challengeType?: number; helpCategory: string; disableLoopProtectTests: boolean; disableLoopProtectPreview: boolean; +} & NavigationPaths; + +export type NavigationPaths = { + nextChallengePath?: string; + prevChallengePath?: string; }; export type PortfolioProjectData = { diff --git a/client/src/templates/Challenges/classic/show.tsx b/client/src/templates/Challenges/classic/show.tsx index 59da3443d24..085a2287c1d 100644 --- a/client/src/templates/Challenges/classic/show.tsx +++ b/client/src/templates/Challenges/classic/show.tsx @@ -21,6 +21,7 @@ import { ChallengeMeta, ChallengeNode, CompletedChallenge, + NavigationPaths, ResizeProps, SavedChallengeFiles, Test @@ -62,6 +63,7 @@ import { getGuideUrl } from '../utils'; import { preloadPage } from '../../../../utils/gatsby/page-loading'; import envData from '../../../../config/env.json'; import ToolPanel from '../components/tool-panel'; +import { getChallengePaths } from '../utils/challenge-paths'; import { XtermTerminal } from './xterm'; import MultifileEditor from './multifile-editor'; import DesktopLayout from './desktop-layout'; @@ -111,6 +113,7 @@ interface ShowClassicProps extends Pick { output: string[]; pageContext: { challengeMeta: ChallengeMeta; + nextCurriculumPaths: NavigationPaths; projectPreview: { challengeData: CompletedChallenge; }; @@ -205,6 +208,7 @@ function ShowClassic({ pageContext: { challengeMeta, challengeMeta: { isFirstStep, nextChallengePath }, + nextCurriculumPaths, projectPreview: { challengeData } }, createFiles, @@ -232,6 +236,8 @@ function ShowClassic({ const isMobile = useMediaQuery({ query: `(max-width: ${MAX_MOBILE_WIDTH}px)` }); + const showNextCurriculum = useFeature('fcc-10').on; + const guideUrl = getGuideUrl({ forumTopicId, title }); const blockNameTitle = `${t( @@ -370,11 +376,18 @@ function ShowClassic({ // project and is shown (once) automatically. In contrast, labs are more // freeform, so the preview is shown on demand. if (demoType === 'onLoad') openModal('projectPreview'); + const challengePaths = getChallengePaths({ + showNextCurriculum, + currentCurriculumPaths: challengeMeta, + nextCurriculumPaths + }); + updateChallengeMeta({ ...challengeMeta, title, challengeType, - helpCategory + helpCategory, + ...challengePaths }); challengeMounted(challengeMeta.id); setIsAdvancing(false); diff --git a/client/src/templates/Challenges/codeally/show.tsx b/client/src/templates/Challenges/codeally/show.tsx index ec3074ff68f..41f6fd573ab 100644 --- a/client/src/templates/Challenges/codeally/show.tsx +++ b/client/src/templates/Challenges/codeally/show.tsx @@ -9,6 +9,7 @@ import { bindActionCreators } from 'redux'; import type { Dispatch } from 'redux'; import { createSelector } from 'reselect'; import { Container, Col, Row, Alert, Spacer } from '@freecodecamp/ui'; +import { useFeature } from '@growthbook/growthbook-react'; // Local Utilities import LearnLayout from '../../../components/layouts/learn'; @@ -39,9 +40,11 @@ import { ChallengeNode, ChallengeMeta, CompletedChallenge, + NavigationPaths, Test } from '../../../redux/prop-types'; import ProjectToolPanel from '../projects/tool-panel'; +import { getChallengePaths } from '../utils/challenge-paths'; import SolutionForm from '../projects/solution-form'; import { FlashMessages } from '../../../components/Flash/redux/flash-messages'; import { SuperBlocks } from '../../../../../shared/config/curriculum'; @@ -99,6 +102,7 @@ interface ShowCodeAllyProps { openCompletionModal: () => void; pageContext: { challengeMeta: ChallengeMeta; + nextCurriculumPaths: NavigationPaths; }; partiallyCompletedChallenges: CompletedChallenge[]; t: TFunction; @@ -111,6 +115,8 @@ interface ShowCodeAllyProps { function ShowCodeAlly(props: ShowCodeAllyProps) { const container = useRef(null); + const showNextCurriculum = useFeature('fcc-10').on; + const { completedChallenges, data: { @@ -130,6 +136,7 @@ function ShowCodeAlly(props: ShowCodeAllyProps) { }, isChallengeCompleted, isSignedIn, + pageContext: { nextCurriculumPaths }, partiallyCompletedChallenges, t, updateSolutionFormValues @@ -166,11 +173,17 @@ function ShowCodeAlly(props: ShowCodeAllyProps) { updateChallengeMeta } = props; initTests(tests); + const challengePaths = getChallengePaths({ + showNextCurriculum, + currentCurriculumPaths: challengeMeta, + nextCurriculumPaths + }); updateChallengeMeta({ ...challengeMeta, title, challengeType, - helpCategory + helpCategory, + ...challengePaths }); challengeMounted(challengeMeta.id); container.current?.focus(); diff --git a/client/src/templates/Challenges/exam/show.tsx b/client/src/templates/Challenges/exam/show.tsx index fa17192de0a..63743f9cdee 100644 --- a/client/src/templates/Challenges/exam/show.tsx +++ b/client/src/templates/Challenges/exam/show.tsx @@ -11,6 +11,7 @@ import type { Dispatch } from 'redux'; import { createSelector } from 'reselect'; import { Container, Col, Alert, Row, Button, Spacer } from '@freecodecamp/ui'; import { micromark } from 'micromark'; +import { useFeature } from '@growthbook/growthbook-react'; // Local Utilities import LearnLayout from '../../../components/layouts/learn'; @@ -46,6 +47,7 @@ import { CompletedChallenge, UserExamQuestion, UserExam, + NavigationPaths, GeneratedExamResults, GeneratedExamQuestion, PrerequisiteChallenge, @@ -54,6 +56,7 @@ import { } from '../../../redux/prop-types'; import { FlashMessages } from '../../../components/Flash/redux/flash-messages'; import { formatSecondsToTime } from '../../../utils/format-seconds'; +import { getChallengePaths } from '../utils/challenge-paths'; import ExitExamModal from './components/exit-exam-modal'; import FinishExamModal from './components/finish-exam-modal'; import ExamResults from './components/exam-results'; @@ -127,6 +130,7 @@ interface ShowExamProps { closeFinishExamModal: () => void; pageContext: { challengeMeta: ChallengeMeta; + nextCurriculumPaths: NavigationPaths; }; t: TFunction; startExam: () => void; @@ -164,11 +168,13 @@ function ShowExam(props: ShowExamProps) { isChallengeCompleted, openExitExamModal, openFinishExamModal, + pageContext: { nextCurriculumPaths }, t } = props; let timerInterval: NodeJS.Timeout; + const showNextCurriculum = useFeature('fcc-10').on; const container = useRef(null); const [examTimeInSeconds, setExamTimeInSeconds] = useState(0); @@ -198,11 +204,17 @@ function ShowExam(props: ShowExamProps) { updateChallengeMeta } = props; initTests(tests); + const challengePaths = getChallengePaths({ + showNextCurriculum, + currentCurriculumPaths: challengeMeta, + nextCurriculumPaths + }); updateChallengeMeta({ ...challengeMeta, title, challengeType, - helpCategory + helpCategory, + ...challengePaths }); challengeMounted(challengeMeta.id); diff --git a/client/src/templates/Challenges/fill-in-the-blank/show.tsx b/client/src/templates/Challenges/fill-in-the-blank/show.tsx index 0b4e5f2baf1..e7d78a2881c 100644 --- a/client/src/templates/Challenges/fill-in-the-blank/show.tsx +++ b/client/src/templates/Challenges/fill-in-the-blank/show.tsx @@ -1,6 +1,7 @@ // Package Utilities import { graphql } from 'gatsby'; import React, { useEffect, useRef, useState } from 'react'; +import { useFeature } from '@growthbook/growthbook-react'; import Helmet from 'react-helmet'; import { ObserveKeys } from 'react-hotkeys'; import type { TFunction } from 'i18next'; @@ -14,7 +15,12 @@ import ShortcutsModal from '../components/shortcuts-modal'; // Local Utilities import LearnLayout from '../../../components/layouts/learn'; -import { ChallengeNode, ChallengeMeta, Test } from '../../../redux/prop-types'; +import { + ChallengeNode, + ChallengeMeta, + NavigationPaths, + Test +} from '../../../redux/prop-types'; import Hotkeys from '../components/hotkeys'; import ChallengeTitle from '../components/challenge-title'; import ChallegeExplanation from '../components/challenge-explanation'; @@ -30,6 +36,7 @@ import { initTests } from '../redux/actions'; import Scene from '../components/scene/scene'; +import { getChallengePaths } from '../utils/challenge-paths'; import { isChallengeCompletedSelector } from '../redux/selectors'; import './show.css'; @@ -64,6 +71,7 @@ interface ShowFillInTheBlankProps { openHelpModal: () => void; pageContext: { challengeMeta: ChallengeMeta; + nextCurriculumPaths: NavigationPaths; }; t: TFunction; updateChallengeMeta: (arg0: ChallengeMeta) => void; @@ -93,7 +101,7 @@ const ShowFillInTheBlank = ({ openHelpModal, updateChallengeMeta, openCompletionModal, - pageContext: { challengeMeta }, + pageContext: { challengeMeta, nextCurriculumPaths }, isChallengeCompleted }: ShowFillInTheBlankProps) => { const { t } = useTranslation(); @@ -109,14 +117,21 @@ const ShowFillInTheBlank = ({ const [isScenePlaying, setIsScenePlaying] = useState(false); const container = useRef(null); + const showNextCurriculum = useFeature('fcc-10').on; useEffect(() => { initTests(tests); + const challengePaths = getChallengePaths({ + showNextCurriculum, + currentCurriculumPaths: challengeMeta, + nextCurriculumPaths + }); updateChallengeMeta({ ...challengeMeta, title, challengeType, - helpCategory + helpCategory, + ...challengePaths }); challengeMounted(challengeMeta.id); container.current?.focus(); diff --git a/client/src/templates/Challenges/generic/show.tsx b/client/src/templates/Challenges/generic/show.tsx index 129a1e52870..b6d0b3d8128 100644 --- a/client/src/templates/Challenges/generic/show.tsx +++ b/client/src/templates/Challenges/generic/show.tsx @@ -1,5 +1,6 @@ import { graphql } from 'gatsby'; import React, { useEffect, useRef, useState } from 'react'; +import { useFeature } from '@growthbook/growthbook-react'; import Helmet from 'react-helmet'; import { useTranslation } from 'react-i18next'; import { connect } from 'react-redux'; @@ -8,7 +9,12 @@ import { isEqual } from 'lodash'; // Local Utilities import LearnLayout from '../../../components/layouts/learn'; -import { ChallengeNode, ChallengeMeta, Test } from '../../../redux/prop-types'; +import { + ChallengeNode, + ChallengeMeta, + NavigationPaths, + Test +} from '../../../redux/prop-types'; import ChallengeDescription from '../components/challenge-description'; import Hotkeys from '../components/hotkeys'; import ChallengeTitle from '../components/challenge-title'; @@ -24,6 +30,7 @@ import { } from '../redux/actions'; import { isChallengeCompletedSelector } from '../redux/selectors'; import { BlockTypes } from '../../../../../shared/config/blocks'; +import { getChallengePaths } from '../utils/challenge-paths'; import Scene from '../components/scene/scene'; import MultipleChoiceQuestions from '../components/multiple-choice-questions'; import ChallengeExplanation from '../components/challenge-explanation'; @@ -58,6 +65,7 @@ interface ShowQuizProps { openHelpModal: () => void; pageContext: { challengeMeta: ChallengeMeta; + nextCurriculumPaths: NavigationPaths; }; updateChallengeMeta: (arg0: ChallengeMeta) => void; updateSolutionFormValues: () => void; @@ -88,7 +96,7 @@ const ShowGeneric = ({ } } }, - pageContext: { challengeMeta }, + pageContext: { challengeMeta, nextCurriculumPaths }, initTests, updateChallengeMeta, openCompletionModal, @@ -104,11 +112,17 @@ const ShowGeneric = ({ useEffect(() => { initTests(tests); + const challengePaths = getChallengePaths({ + showNextCurriculum, + currentCurriculumPaths: challengeMeta, + nextCurriculumPaths + }); updateChallengeMeta({ ...challengeMeta, title, challengeType, - helpCategory + helpCategory, + ...challengePaths }); challengeMounted(challengeMeta.id); container.current?.focus(); @@ -146,6 +160,8 @@ const ShowGeneric = ({ ); const [showFeedback, setShowFeedback] = useState(false); + const showNextCurriculum = useFeature('fcc-10').on; + const handleMcqOptionChange = ( questionIndex: number, answerIndex: number diff --git a/client/src/templates/Challenges/ms-trophy/show.tsx b/client/src/templates/Challenges/ms-trophy/show.tsx index 734bc21f060..56997a2f49b 100644 --- a/client/src/templates/Challenges/ms-trophy/show.tsx +++ b/client/src/templates/Challenges/ms-trophy/show.tsx @@ -7,14 +7,21 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import type { Dispatch } from 'redux'; import { createSelector } from 'reselect'; +import { useFeature } from '@growthbook/growthbook-react'; import { Container, Col, Row, Button, Spacer } from '@freecodecamp/ui'; import LearnLayout from '../../../components/layouts/learn'; -import { ChallengeNode, ChallengeMeta, Test } from '../../../redux/prop-types'; +import { + ChallengeNode, + ChallengeMeta, + NavigationPaths, + Test +} from '../../../redux/prop-types'; import ChallengeDescription from '../components/challenge-description'; import Hotkeys from '../components/hotkeys'; import ChallengeTitle from '../components/challenge-title'; import CompletionModal from '../components/completion-modal'; +import { getChallengePaths } from '../utils/challenge-paths'; import HelpModal from '../components/help-modal'; import { challengeMounted, @@ -76,6 +83,7 @@ interface MsTrophyProps { openHelpModal: () => void; pageContext: { challengeMeta: ChallengeMeta; + nextCurriculumPaths: NavigationPaths; }; submitChallenge: () => void; t: TFunction; @@ -83,6 +91,7 @@ interface MsTrophyProps { } function MsTrophy(props: MsTrophyProps) { + const showNextCurriculum = useFeature('fcc-10').on; const container = useRef(null); const { data: { @@ -104,16 +113,22 @@ function MsTrophy(props: MsTrophyProps) { } } }, - pageContext: { challengeMeta }, + pageContext: { challengeMeta, nextCurriculumPaths }, initTests, updateChallengeMeta } = props; initTests(tests); + const challengePaths = getChallengePaths({ + showNextCurriculum, + currentCurriculumPaths: challengeMeta, + nextCurriculumPaths + }); updateChallengeMeta({ ...challengeMeta, title, challengeType, - helpCategory + helpCategory, + ...challengePaths }); challengeMounted(challengeMeta.id); container.current?.focus(); diff --git a/client/src/templates/Challenges/projects/backend/show.tsx b/client/src/templates/Challenges/projects/backend/show.tsx index d12b488e0df..bb565d79be9 100644 --- a/client/src/templates/Challenges/projects/backend/show.tsx +++ b/client/src/templates/Challenges/projects/backend/show.tsx @@ -6,12 +6,14 @@ import type { TFunction } from 'i18next'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import { Container, Col, Row, Spacer } from '@freecodecamp/ui'; +import { useFeature } from '@growthbook/growthbook-react'; import LearnLayout from '../../../../components/layouts/learn'; import { isSignedInSelector } from '../../../../redux/selectors'; import { ChallengeMeta, ChallengeNode, + NavigationPaths, Test } from '../../../../redux/prop-types'; import ChallengeDescription from '../../components/challenge-description'; @@ -34,7 +36,9 @@ import { consoleOutputSelector, isChallengeCompletedSelector } from '../../redux/selectors'; + import { getGuideUrl } from '../../utils'; +import { getChallengePaths } from '../../utils/challenge-paths'; import SolutionForm from '../solution-form'; import ProjectToolPanel from '../tool-panel'; @@ -83,6 +87,7 @@ interface BackEndProps { output: string[]; pageContext: { challengeMeta: ChallengeMeta; + nextCurriculumPaths: NavigationPaths; }; t: TFunction; tests: Test[]; @@ -92,6 +97,7 @@ interface BackEndProps { } const ShowBackEnd = (props: BackEndProps) => { + const showNextCurriculum = useFeature('fcc-10').on; const container = useRef(null); const handleSubmit = ({ @@ -118,15 +124,21 @@ const ShowBackEnd = (props: BackEndProps) => { } } }, - pageContext: { challengeMeta } + pageContext: { challengeMeta, nextCurriculumPaths } } = props; initConsole(); initTests(tests); + const challengePaths = getChallengePaths({ + showNextCurriculum, + currentCurriculumPaths: challengeMeta, + nextCurriculumPaths + }); updateChallengeMeta({ ...challengeMeta, title, challengeType, - helpCategory + helpCategory, + ...challengePaths }); challengeMounted(challengeMeta.id); container.current?.focus(); diff --git a/client/src/templates/Challenges/projects/frontend/show.tsx b/client/src/templates/Challenges/projects/frontend/show.tsx index aa036d12dcd..88ad872d870 100644 --- a/client/src/templates/Challenges/projects/frontend/show.tsx +++ b/client/src/templates/Challenges/projects/frontend/show.tsx @@ -8,11 +8,13 @@ import { bindActionCreators } from 'redux'; import type { Dispatch } from 'redux'; import { createSelector } from 'reselect'; import { Container, Col, Row, Spacer } from '@freecodecamp/ui'; +import { useFeature } from '@growthbook/growthbook-react'; import LearnLayout from '../../../../components/layouts/learn'; import { ChallengeNode, ChallengeMeta, + NavigationPaths, Test } from '../../../../redux/prop-types'; import ChallengeDescription from '../../components/challenge-description'; @@ -31,6 +33,7 @@ import { isChallengeCompletedSelector } from '../../redux/selectors'; import { getGuideUrl } from '../../utils'; import SolutionForm from '../solution-form'; import ProjectToolPanel from '../tool-panel'; +import { getChallengePaths } from '../../utils/challenge-paths'; // Redux Setup const mapStateToProps = createSelector( @@ -61,6 +64,7 @@ interface ProjectProps { openCompletionModal: () => void; pageContext: { challengeMeta: ChallengeMeta; + nextCurriculumPaths: NavigationPaths; }; t: TFunction; updateChallengeMeta: (arg0: ChallengeMeta) => void; @@ -78,6 +82,7 @@ const ShowFrontEndProject = (props: ProjectProps) => { } }; + const showNextCurriculum = useFeature('fcc-10').on; const container = useRef(null); useEffect(() => { @@ -88,16 +93,22 @@ const ShowFrontEndProject = (props: ProjectProps) => { challenge: { fields, title, challengeType, helpCategory } } }, - pageContext: { challengeMeta }, + pageContext: { challengeMeta, nextCurriculumPaths }, initTests, updateChallengeMeta } = props; initTests(fields.tests); + const challengePaths = getChallengePaths({ + showNextCurriculum, + currentCurriculumPaths: challengeMeta, + nextCurriculumPaths + }); updateChallengeMeta({ ...challengeMeta, title, challengeType, - helpCategory + helpCategory, + ...challengePaths }); challengeMounted(challengeMeta.id); container.current?.focus(); diff --git a/client/src/templates/Challenges/quiz/show.tsx b/client/src/templates/Challenges/quiz/show.tsx index de89be0808a..cf6b180f330 100644 --- a/client/src/templates/Challenges/quiz/show.tsx +++ b/client/src/templates/Challenges/quiz/show.tsx @@ -17,15 +17,22 @@ import { useQuiz, Spacer } from '@freecodecamp/ui'; +import { useFeature } from '@growthbook/growthbook-react'; // Local Utilities import { shuffleArray } from '../../../../../shared/utils/shuffle-array'; import LearnLayout from '../../../components/layouts/learn'; -import { ChallengeNode, ChallengeMeta, Test } from '../../../redux/prop-types'; +import { + ChallengeNode, + ChallengeMeta, + NavigationPaths, + Test +} from '../../../redux/prop-types'; import ChallengeDescription from '../components/challenge-description'; import Hotkeys from '../components/hotkeys'; import ChallengeTitle from '../components/challenge-title'; import CompletionModal from '../components/completion-modal'; +import { getChallengePaths } from '../utils/challenge-paths'; import { challengeMounted, updateChallengeMeta, @@ -74,6 +81,7 @@ interface ShowQuizProps { isChallengeCompleted: boolean; pageContext: { challengeMeta: ChallengeMeta; + nextCurriculumPaths: NavigationPaths; }; updateChallengeMeta: (arg0: ChallengeMeta) => void; updateSolutionFormValues: () => void; @@ -101,7 +109,7 @@ const ShowQuiz = ({ } } }, - pageContext: { challengeMeta }, + pageContext: { challengeMeta, nextCurriculumPaths }, initTests, updateChallengeMeta, isChallengeCompleted, @@ -126,6 +134,7 @@ const ShowQuiz = ({ const [showUnanswered, setShowUnanswered] = useState(false); const [exitConfirmed, setExitConfirmed] = useState(false); + const showNextCurriculum = useFeature('fcc-10').on; const blockNameTitle = `${t( `intro:${superBlock}.blocks.${block}.title` @@ -189,11 +198,17 @@ const ShowQuiz = ({ useEffect(() => { initTests(tests); + const challengePaths = getChallengePaths({ + showNextCurriculum, + currentCurriculumPaths: challengeMeta, + nextCurriculumPaths + }); updateChallengeMeta({ ...challengeMeta, title, challengeType, - helpCategory + helpCategory, + ...challengePaths }); challengeMounted(challengeMeta.id); container.current?.focus(); diff --git a/client/src/templates/Challenges/utils/challenge-paths.ts b/client/src/templates/Challenges/utils/challenge-paths.ts new file mode 100644 index 00000000000..d5382e08e06 --- /dev/null +++ b/client/src/templates/Challenges/utils/challenge-paths.ts @@ -0,0 +1,23 @@ +import { NavigationPaths } from '../../../redux/prop-types'; + +export const getChallengePaths = ({ + showNextCurriculum, + currentCurriculumPaths, + nextCurriculumPaths +}: { + showNextCurriculum: boolean; + currentCurriculumPaths: NavigationPaths; + nextCurriculumPaths: NavigationPaths; +}): NavigationPaths => { + const nextChallengePath = showNextCurriculum + ? nextCurriculumPaths.nextChallengePath + : currentCurriculumPaths.nextChallengePath; + + const prevChallengePath = showNextCurriculum + ? nextCurriculumPaths.prevChallengePath + : currentCurriculumPaths.prevChallengePath; + return { + nextChallengePath, + prevChallengePath + }; +}; diff --git a/client/utils/gatsby/challenge-page-creator.js b/client/utils/gatsby/challenge-page-creator.js index 480ea5c1c29..c4d28218a5e 100644 --- a/client/utils/gatsby/challenge-page-creator.js +++ b/client/utils/gatsby/challenge-page-creator.js @@ -72,21 +72,19 @@ function getIsFirstStepInBlock(id, edges) { return previous.node.challenge.block !== current.node.challenge.block; } -function getNextChallengePath(id, edges) { - const next = edges[id + 1]; - return next ? next.node.challenge.fields.slug : null; -} - -function getPrevChallengePath(id, edges) { - const prev = edges[id - 1]; - return prev ? prev.node.challenge.fields.slug : null; -} - function getTemplateComponent(challengeType) { return views[viewTypes[challengeType]]; } -exports.createChallengePages = function (createPage) { +exports.createChallengePages = function ( + createPage, + { + idToNextPathCurrentCurriculum, + idToPrevPathCurrentCurriculum, + idToNextPathNextCurriculum, + idToPrevPathNextCurriculum + } +) { return function ({ node }, index, allChallengeEdges) { const { dashedName, @@ -121,10 +119,14 @@ exports.createChallengePages = function (createPage) { template, required, isLastChallengeInBlock: isLastChallengeInBlock, - nextChallengePath: getNextChallengePath(index, allChallengeEdges), - prevChallengePath: getPrevChallengePath(index, allChallengeEdges), + nextChallengePath: idToNextPathCurrentCurriculum[node.id], + prevChallengePath: idToPrevPathCurrentCurriculum[node.id], id }, + nextCurriculumPaths: { + nextChallengePath: idToNextPathNextCurriculum[node.id], + prevChallengePath: idToPrevPathNextCurriculum[node.id] + }, projectPreview: getProjectPreviewConfig( node.challenge, allChallengeEdges diff --git a/client/utils/gatsby/page-loading.ts b/client/utils/gatsby/page-loading.ts index 45db48515bf..283ea9c6fae 100644 --- a/client/utils/gatsby/page-loading.ts +++ b/client/utils/gatsby/page-loading.ts @@ -3,7 +3,7 @@ * but the link of the page isn't rendered on the screen. * For more details, see https://github.com/freeCodeCamp/freeCodeCamp/pull/55472. */ -export const preloadPage = (path: string | null) => { +export const preloadPage = (path?: string) => { if (!window.___loader || !path) return; window.___loader.hovering(path);