From 3074bb199dd9ca486f9a4e388614f6fc71a0aa57 Mon Sep 17 00:00:00 2001 From: Tom <20648924+moT01@users.noreply.github.com> Date: Fri, 6 Sep 2024 05:16:26 -0500 Subject: [PATCH] refactor(client/challenge views): extract items into sharable components (#55946) --- client/i18n/locales/english/translations.json | 2 +- .../Challenges/components/assignments.css | 4 + .../Challenges/components/assignments.tsx | 58 +++++++ .../components/fill-in-the-blanks.tsx | 87 +++++++++++ .../components/multiple-choice-questions.tsx | 85 +++++++++++ .../Challenges/components/scene/scene.tsx | 3 +- .../Challenges/components/video-player.tsx | 12 +- .../templates/Challenges/dialogue/show.tsx | 45 +----- .../Challenges/fill-in-the-blank/show.css | 19 --- .../Challenges/fill-in-the-blank/show.tsx | 101 +++---------- client/src/templates/Challenges/odin/show.tsx | 142 ++++-------------- .../src/templates/Challenges/video/show.tsx | 94 +++--------- 12 files changed, 318 insertions(+), 334 deletions(-) create mode 100644 client/src/templates/Challenges/components/assignments.css create mode 100644 client/src/templates/Challenges/components/assignments.tsx create mode 100644 client/src/templates/Challenges/components/fill-in-the-blanks.tsx create mode 100644 client/src/templates/Challenges/components/multiple-choice-questions.tsx diff --git a/client/i18n/locales/english/translations.json b/client/i18n/locales/english/translations.json index 2529db851b0..361049204b1 100644 --- a/client/i18n/locales/english/translations.json +++ b/client/i18n/locales/english/translations.json @@ -385,7 +385,7 @@ "add-subtitles": "Help improve or add subtitles", "wrong-answer": "Sorry, that's not the right answer. Give it another try?", "check-answer": "Click the button below to check your answer.", - "assignment-not-complete": "Please finish the assignments", + "assignment-not-complete": "Please complete the assignments", "assignments": "Assignments", "question": "Question", "solution-link": "Solution Link", diff --git a/client/src/templates/Challenges/components/assignments.css b/client/src/templates/Challenges/components/assignments.css new file mode 100644 index 00000000000..7e52d42aaa1 --- /dev/null +++ b/client/src/templates/Challenges/components/assignments.css @@ -0,0 +1,4 @@ +.assignments-not-complete { + text-align: center; + color: var(--danger-color); +} diff --git a/client/src/templates/Challenges/components/assignments.tsx b/client/src/templates/Challenges/components/assignments.tsx new file mode 100644 index 00000000000..1282a00ae06 --- /dev/null +++ b/client/src/templates/Challenges/components/assignments.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import Spacer from '../../../components/helpers/spacer'; +import ChallengeHeading from './challenge-heading'; +import PrismFormatted from './prism-formatted'; + +import './assignments.css'; + +type AssignmentsProps = { + assignments: string[]; + allAssignmentsCompleted: boolean; + handleAssignmentChange: ( + event: React.ChangeEvent, + totalAssignments: number + ) => void; +}; + +function Assignments({ + assignments, + allAssignmentsCompleted, + handleAssignmentChange +}: AssignmentsProps): JSX.Element { + const { t } = useTranslation(); + return ( + <> + +
+ {assignments.map((assignment, index) => ( + + ))} +
+ {!allAssignmentsCompleted && ( + <> + +
+ {t('learn.assignment-not-complete')} +
+ + )} + + + ); +} + +Assignments.displayName = 'Assignments'; + +export default Assignments; diff --git a/client/src/templates/Challenges/components/fill-in-the-blanks.tsx b/client/src/templates/Challenges/components/fill-in-the-blanks.tsx new file mode 100644 index 00000000000..ca1981bc3c7 --- /dev/null +++ b/client/src/templates/Challenges/components/fill-in-the-blanks.tsx @@ -0,0 +1,87 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import { parseBlanks } from '../fill-in-the-blank/parse-blanks'; +import Spacer from '../../../components/helpers/spacer'; +import PrismFormatted from '../components/prism-formatted'; +import { FillInTheBlank } from '../../../redux/prop-types'; +import ChallengeHeading from './challenge-heading'; + +type FillInTheBlankProps = { + fillInTheBlank: FillInTheBlank; + answersCorrect: (boolean | null)[]; + showFeedback: boolean; + feedback: string | null; + showWrong: boolean; + handleInputChange: (e: React.ChangeEvent) => void; +}; + +function FillInTheBlanks({ + fillInTheBlank: { sentence, blanks }, + answersCorrect, + showFeedback, + feedback, + showWrong, + handleInputChange +}: FillInTheBlankProps): JSX.Element { + const { t } = useTranslation(); + + const addInputClass = (index: number): string => { + if (answersCorrect[index] === true) return 'green-underline'; + if (answersCorrect[index] === false) return 'red-underline'; + return ''; + }; + + const paragraphs = parseBlanks(sentence); + const blankAnswers = blanks.map(b => b.answer); + + return ( + <> + + +
+ {paragraphs.map((p, i) => { + return ( + // both keys, i and j, are stable between renders, since + // the paragraphs are static. +

+ {p.map((node, j) => { + const { type, value } = node; + if (type === 'text') return value; + if (type === 'blank') + return ( + + ); + })} +

+ ); + })} +
+ + {showFeedback && feedback && ( + <> + + + + )} +
+ {showWrong && {t('learn.wrong-answer')}} +
+ + ); +} + +FillInTheBlanks.displayName = 'FillInTheBlanks'; + +export default FillInTheBlanks; diff --git a/client/src/templates/Challenges/components/multiple-choice-questions.tsx b/client/src/templates/Challenges/components/multiple-choice-questions.tsx new file mode 100644 index 00000000000..291c7077bc7 --- /dev/null +++ b/client/src/templates/Challenges/components/multiple-choice-questions.tsx @@ -0,0 +1,85 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import { Question } from '../../../redux/prop-types'; +import Spacer from '../../../components/helpers/spacer'; +import ChallengeHeading from './challenge-heading'; +import PrismFormatted from './prism-formatted'; + +type MultipleChoiceQuestionsProps = { + questions: Question; + selectedOption: number | null; + isWrongAnswer: boolean; + handleOptionChange: ( + changeEvent: React.ChangeEvent + ) => void; +}; + +function MultipleChoiceQuestions({ + questions: { text, answers }, + selectedOption, + isWrongAnswer, + handleOptionChange +}: MultipleChoiceQuestionsProps): JSX.Element { + const { t } = useTranslation(); + + const feedback = + selectedOption !== null ? answers[selectedOption].feedback : undefined; + + return ( + <> + + +
+ {answers.map(({ answer }, index) => ( + + ))} +
+ {isWrongAnswer && ( + <> + +
+ {feedback ? ( + + ) : ( + t('learn.wrong-answer') + )} +
+ + )} + + + ); +} + +MultipleChoiceQuestions.displayName = 'MultipleChoiceQuestions'; + +export default MultipleChoiceQuestions; diff --git a/client/src/templates/Challenges/components/scene/scene.tsx b/client/src/templates/Challenges/components/scene/scene.tsx index 44d7d467744..79fd884c725 100644 --- a/client/src/templates/Challenges/components/scene/scene.tsx +++ b/client/src/templates/Challenges/components/scene/scene.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState, useRef } from 'react'; //, ReactElement } f import { Col } from '@freecodecamp/ui'; import { useTranslation } from 'react-i18next'; import { FullScene } from '../../../../redux/prop-types'; -import { Loader } from '../../../../components/helpers'; +import { Loader, Spacer } from '../../../../components/helpers'; import ClosedCaptionsIcon from '../../../../assets/icons/closedcaptions'; import { sounds, images, backgrounds, characterAssets } from './scene-assets'; import Character from './character'; @@ -283,6 +283,7 @@ export function Scene({ )} + ); } diff --git a/client/src/templates/Challenges/components/video-player.tsx b/client/src/templates/Challenges/components/video-player.tsx index a875175eb53..1f068d686f2 100644 --- a/client/src/templates/Challenges/components/video-player.tsx +++ b/client/src/templates/Challenges/components/video-player.tsx @@ -1,5 +1,7 @@ import React from 'react'; import YouTube from 'react-youtube'; + +import Loader from '../../../components/helpers/loader'; import envData from '../../../../config/env.json'; import type { BilibiliIds, VideoLocaleIds } from '../../../redux/prop-types'; @@ -47,7 +49,13 @@ function VideoPlayer({ } return ( - <> +
+ {!videoIsLoaded ? ( +
+ +
+ ) : null} + {bilibiliSrc ? (