mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-02-28 23:02:24 -05:00
fix(client): add message about accepting ahp (#65675)
Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
@@ -699,7 +699,8 @@
|
||||
"exit-header": "Exit Exam",
|
||||
"exit": "Are you sure you want to leave the exam? You will lose any progress you have made.",
|
||||
"exit-yes": "Yes, I want to leave the exam",
|
||||
"exit-no": "No, I would like to continue the exam"
|
||||
"exit-no": "No, I would like to continue the exam",
|
||||
"not-honest": "You need to <0>accept the Academic Honesty Policy</0> to take this exam"
|
||||
},
|
||||
"ms": {
|
||||
"link-header": "Link your Microsoft account",
|
||||
|
||||
@@ -11,14 +11,14 @@ import {
|
||||
Row,
|
||||
Col
|
||||
} from '@freecodecamp/ui';
|
||||
import { useTranslation, withTranslation } from 'react-i18next';
|
||||
import { Trans, useTranslation, withTranslation } from 'react-i18next';
|
||||
import { createSelector } from 'reselect';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import LearnLayout from '../../../components/layouts/learn';
|
||||
import ChallengeTitle from '../components/challenge-title';
|
||||
import useDetectOS, { type UserOSState } from '../utils/use-detect-os';
|
||||
import {
|
||||
import type {
|
||||
ChallengeNode,
|
||||
CompletedChallenge,
|
||||
User
|
||||
@@ -36,6 +36,8 @@ import { Attempts } from './attempts';
|
||||
import ExamTokenControls from './exam-token-controls';
|
||||
|
||||
import './show.css';
|
||||
import { Link, Loader } from '../../../components/helpers';
|
||||
import { SuperBlocks } from '@freecodecamp/shared/config/curriculum';
|
||||
|
||||
const { deploymentEnv } = envData;
|
||||
|
||||
@@ -49,6 +51,110 @@ interface GitProps {
|
||||
prerelease: boolean;
|
||||
}
|
||||
|
||||
function PrerequisitesCallout({
|
||||
id,
|
||||
completedChallenges,
|
||||
challenges,
|
||||
examSuperBlock,
|
||||
isSignedIn,
|
||||
isHonest
|
||||
}: ExamPrerequisitesProps & {
|
||||
isSignedIn: boolean;
|
||||
isHonest: boolean;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
if (!isSignedIn) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isHonest) {
|
||||
return (
|
||||
<Callout variant='caution' label={t('misc.caution')}>
|
||||
<p>
|
||||
<Trans i18nKey={'learn.exam.not-honest'}>
|
||||
<Link to={'/settings#honesty'}>settings</Link>
|
||||
</Trans>
|
||||
</p>
|
||||
</Callout>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ExamPrerequisites
|
||||
id={id}
|
||||
completedChallenges={completedChallenges}
|
||||
challenges={challenges}
|
||||
examSuperBlock={examSuperBlock}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
interface ExamPrerequisitesProps {
|
||||
id: string;
|
||||
completedChallenges: CompletedChallenge[];
|
||||
challenges: ChallengeNode['challenge'][];
|
||||
examSuperBlock: SuperBlocks;
|
||||
}
|
||||
|
||||
function ExamPrerequisites({
|
||||
id,
|
||||
completedChallenges,
|
||||
challenges,
|
||||
examSuperBlock
|
||||
}: ExamPrerequisitesProps) {
|
||||
const { t } = useTranslation();
|
||||
const getExamsQuery = examAttempts.useGetExamsQuery();
|
||||
const examIdsQuery = examAttempts.useGetExamIdsByChallengeIdQuery(id);
|
||||
|
||||
if (getExamsQuery.isFetching || examIdsQuery.isFetching) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
if (getExamsQuery.isError || examIdsQuery.isError) {
|
||||
console.error(getExamsQuery.error);
|
||||
console.error(examIdsQuery.error);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!getExamsQuery.isSuccess || !examIdsQuery.isSuccess) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const examId = examIdsQuery.data.at(0)?.examId;
|
||||
const exam = getExamsQuery.data.find(examItem => examItem.id === examId);
|
||||
|
||||
if (!exam) {
|
||||
// This should never happen
|
||||
return null;
|
||||
}
|
||||
|
||||
const unmetPrerequisites = exam.prerequisites.filter(
|
||||
prereq => !completedChallenges.some(challenge => challenge.id === prereq)
|
||||
);
|
||||
const unmetChallenges = challenges.filter(
|
||||
challenge =>
|
||||
unmetPrerequisites?.includes(challenge.id) &&
|
||||
challenge.superBlock === examSuperBlock
|
||||
);
|
||||
const missingPrerequisites = unmetChallenges.map(challenge => {
|
||||
return {
|
||||
id: challenge.id,
|
||||
title: challenge.title,
|
||||
slug: challenge.fields?.slug || ''
|
||||
};
|
||||
});
|
||||
|
||||
if (missingPrerequisites.length < 1) {
|
||||
return (
|
||||
<Callout className='exam-qualified' variant='note' label={t('misc.note')}>
|
||||
<p>{t('learn.exam.qualified')}</p>
|
||||
</Callout>
|
||||
);
|
||||
}
|
||||
|
||||
return <MissingPrerequisites missingPrerequisites={missingPrerequisites} />;
|
||||
}
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
completedChallengesSelector,
|
||||
isChallengeCompletedSelector,
|
||||
@@ -168,13 +274,6 @@ function ShowExamDownload({
|
||||
const [downloadLink, setDownloadLink] = useState<string | undefined>('');
|
||||
const [downloadLinks, setDownloadLinks] = useState<string[]>([]);
|
||||
|
||||
const getExamsQuery = examAttempts.useGetExamsQuery(undefined, {
|
||||
skip: !isSignedIn
|
||||
});
|
||||
const examIdsQuery = examAttempts.useGetExamIdsByChallengeIdQuery(id, {
|
||||
skip: !isSignedIn
|
||||
});
|
||||
|
||||
const userOSState = useDetectOS();
|
||||
|
||||
const { t } = useTranslation();
|
||||
@@ -231,27 +330,6 @@ function ShowExamDownload({
|
||||
}
|
||||
}, [userOSState]);
|
||||
|
||||
const examId = examIdsQuery.data?.at(0)?.examId;
|
||||
const exam = getExamsQuery.data?.find(examItem => examItem.id === examId);
|
||||
const unmetPrerequisites = exam?.prerequisites?.filter(
|
||||
prereq => !completedChallenges.some(challenge => challenge.id === prereq)
|
||||
);
|
||||
const challenges = nodes.filter(
|
||||
({ challenge }) =>
|
||||
unmetPrerequisites?.includes(challenge.id) &&
|
||||
challenge.superBlock === examSuperBlock
|
||||
);
|
||||
const missingPrerequisites = challenges.map(({ challenge }) => {
|
||||
return {
|
||||
id: challenge.id,
|
||||
title: challenge.title,
|
||||
slug: challenge.fields?.slug || ''
|
||||
};
|
||||
});
|
||||
|
||||
const showPrereqAlert =
|
||||
isSignedIn && !examIdsQuery.isLoading && !getExamsQuery.isLoading;
|
||||
|
||||
return (
|
||||
<LearnLayout>
|
||||
<Helmet>
|
||||
@@ -270,20 +348,14 @@ function ShowExamDownload({
|
||||
{title}
|
||||
</ChallengeTitle>
|
||||
<Spacer size='m' />
|
||||
{showPrereqAlert &&
|
||||
(missingPrerequisites.length > 0 ? (
|
||||
<MissingPrerequisites
|
||||
missingPrerequisites={missingPrerequisites}
|
||||
/>
|
||||
) : (
|
||||
<Callout
|
||||
className='exam-qualified'
|
||||
variant='note'
|
||||
label={t('misc.note')}
|
||||
>
|
||||
<p>{t('learn.exam.qualified')}</p>
|
||||
</Callout>
|
||||
))}
|
||||
<PrerequisitesCallout
|
||||
isSignedIn={isSignedIn}
|
||||
isHonest={user?.isHonest ?? false}
|
||||
id={id}
|
||||
challenges={nodes.map(({ challenge }) => challenge)}
|
||||
completedChallenges={completedChallenges}
|
||||
examSuperBlock={examSuperBlock}
|
||||
/>
|
||||
<h2>{t('exam.download-header')}</h2>
|
||||
<p>{t('exam.explanation')}</p>
|
||||
<Spacer size='l' />
|
||||
|
||||
Reference in New Issue
Block a user