{children}
diff --git a/client/src/templates/Challenges/components/prism-formatted.tsx b/client/src/templates/Challenges/components/prism-formatted.tsx
index b71e92603eb234..dfb54222afe8fb 100644
--- a/client/src/templates/Challenges/components/prism-formatted.tsx
+++ b/client/src/templates/Challenges/components/prism-formatted.tsx
@@ -4,17 +4,53 @@ import React, { useRef, useEffect } from 'react';
interface PrismFormattedProps {
className?: string;
text: string;
+ lineNumbers?: boolean;
+ darkTheme?: boolean;
}
-function PrismFormatted({ className, text }: PrismFormattedProps): JSX.Element {
+/**
+ * Add css formatting classes to the
elements based on showLineNumbers and darkTheme params
+ * @param container
+ * @param showLineNumbers
+ * @param darkTheme
+ */
+const addFormattingClassesForPres = (
+ container: HTMLElement,
+ showLineNumbers = true,
+ darkTheme = true
+) => {
+ const codeBlocks: HTMLElement[] = [].slice.call(
+ container.querySelectorAll('[class*="language-"]')
+ );
+ // we want to formatt the element, not the , get parent if current element is not PRE
+ const preElements: HTMLPreElement[] = codeBlocks.map(
+ c => (c.nodeName === 'PRE' ? c : c.parentElement) as HTMLPreElement
+ );
+
+ for (const pre of preElements) {
+ pre.classList.toggle('line-numbers', showLineNumbers);
+ pre.classList.toggle('dark-palette', darkTheme);
+ }
+};
+
+function PrismFormatted({
+ className,
+ text,
+ ...props
+}: PrismFormattedProps): JSX.Element {
const instructionsRef = useRef(null);
useEffect(() => {
// Just in case 'current' has not been created, though it should have been.
if (instructionsRef.current) {
+ addFormattingClassesForPres(
+ instructionsRef.current,
+ props.lineNumbers,
+ props.darkTheme
+ );
Prism.highlightAllUnder(instructionsRef.current);
}
- }, []);
+ }, [props.darkTheme, props.lineNumbers]);
return (
- {showToolPanel && (
+ {showToolPanel && tests.length > 10 && (
)}
- {challengeType !== challengeTypes.multifileCertProject && (
+ {/* {challengeType !== challengeTypes.multifileCertProject && (
{isMobile ? t('buttons.reset') : t('buttons.reset-code')}
- )}
+ )} */}
);
}
diff --git a/client/src/templates/Challenges/projects/backend/Show.tsx b/client/src/templates/Challenges/projects/backend/Show.tsx
index 3da304fb3b7866..f27009fc4523e4 100644
--- a/client/src/templates/Challenges/projects/backend/Show.tsx
+++ b/client/src/templates/Challenges/projects/backend/Show.tsx
@@ -174,13 +174,9 @@ class BackEnd extends Component {
challengeMounted(challengeMeta.id);
}
- handleSubmit({
- showCompletionModal
- }: {
- showCompletionModal: boolean;
- }): void {
+ handleSubmit(): void {
this.props.executeChallenge({
- showCompletionModal
+ showCompletionModal: false
});
}
diff --git a/client/src/templates/Challenges/projects/frontend/Show.tsx b/client/src/templates/Challenges/projects/frontend/Show.tsx
index f7c85a0802a909..a7e8dccf6dd29a 100644
--- a/client/src/templates/Challenges/projects/frontend/Show.tsx
+++ b/client/src/templates/Challenges/projects/frontend/Show.tsx
@@ -14,18 +14,14 @@ import { ChallengeNode, ChallengeMeta } 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 HelpModal from '../../components/help-modal';
import {
challengeMounted,
isChallengeCompletedSelector,
+ submitChallenge,
updateChallengeMeta,
- openModal,
updateSolutionFormValues
} from '../../redux';
-import { getGuideUrl } from '../../utils';
import SolutionForm from '../solution-form';
-import ProjectToolPanel from '../tool-panel';
// Redux Setup
const mapStateToProps = createSelector(
@@ -38,10 +34,10 @@ const mapStateToProps = createSelector(
const mapDispatchToProps = (dispatch: Dispatch) =>
bindActionCreators(
{
+ submitChallenge,
updateChallengeMeta,
challengeMounted,
- updateSolutionFormValues,
- openCompletionModal: () => openModal('completion')
+ updateSolutionFormValues
},
dispatch
);
@@ -51,7 +47,7 @@ interface ProjectProps {
challengeMounted: (arg0: string) => void;
data: { challengeNode: ChallengeNode };
isChallengeCompleted: boolean;
- openCompletionModal: () => void;
+ submitChallenge: () => void;
pageContext: {
challengeMeta: ChallengeMeta;
};
@@ -60,13 +56,24 @@ interface ProjectProps {
updateSolutionFormValues: () => void;
}
+interface ProjectState {
+ completed: boolean;
+ hasErrors: boolean;
+}
+
// Component
-class Project extends Component {
+class Project extends Component {
static displayName: string;
private _container: HTMLElement | null = null;
constructor(props: ProjectProps) {
super(props);
+
+ this.state = {
+ completed: false,
+ hasErrors: false
+ };
+
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
@@ -119,13 +126,12 @@ class Project extends Component {
}
}
- handleSubmit({
- showCompletionModal
- }: {
- showCompletionModal: boolean;
- }): void {
- if (showCompletionModal) {
- this.props.openCompletionModal();
+ handleSubmit({ completed }: { completed: boolean }): void {
+ this.setState({ completed, hasErrors: !completed });
+
+ const { submitChallenge } = this.props;
+ if (completed) {
+ submitChallenge();
}
}
@@ -135,13 +141,10 @@ class Project extends Component {
challengeNode: {
challenge: {
challengeType,
- fields: { blockName },
- forumTopicId,
title,
description,
instructions,
superBlock,
- certification,
block,
translationPending
}
@@ -192,19 +195,8 @@ class Project extends Component {
onSubmit={this.handleSubmit}
updateSolutionForm={updateSolutionFormValues}
/>
-
-
-
-
diff --git a/client/src/templates/Challenges/projects/solution-form.tsx b/client/src/templates/Challenges/projects/solution-form.tsx
index b5a43cce74986a..712081077fb0a0 100644
--- a/client/src/templates/Challenges/projects/solution-form.tsx
+++ b/client/src/templates/Challenges/projects/solution-form.tsx
@@ -12,7 +12,7 @@ import {
import { Form, ValidatedValues } from '../../../components/formHelpers';
interface SubmitProps {
- showCompletionModal: boolean;
+ completed: boolean;
}
interface FormProps extends WithTranslation {
@@ -38,9 +38,9 @@ export class SolutionForm extends Component {
// updates values on store
this.props.updateSolutionForm(validatedValues.values);
if (validatedValues.invalidValues.length === 0) {
- this.props.onSubmit({ showCompletionModal: true });
+ this.props.onSubmit({ completed: true });
} else {
- this.props.onSubmit({ showCompletionModal: false });
+ this.props.onSubmit({ completed: false });
}
}
};
@@ -57,7 +57,7 @@ export class SolutionForm extends Component {
{ name: 'githubLink', label: t('learn.github-link') }
];
- const buttonCopy = t('learn.i-completed');
+ const buttonCopy = t('learn.submit-and-go');
const options = {
types: {
diff --git a/client/src/templates/Challenges/redux/execute-challenge-saga.js b/client/src/templates/Challenges/redux/execute-challenge-saga.js
index 6eb99c87a32b24..0798391b84f34f 100644
--- a/client/src/templates/Challenges/redux/execute-challenge-saga.js
+++ b/client/src/templates/Challenges/redux/execute-challenge-saga.js
@@ -45,7 +45,7 @@ import {
updateLogs,
logsToConsole,
updateTests,
- openModal,
+ // openModal,
isBuildEnabledSelector,
disableBuildOnError,
updateTestsRunning
@@ -137,7 +137,8 @@ export function* executeChallengeSaga({ payload }) {
playTone('tests-failed');
}
if (challengeComplete && payload?.showCompletionModal) {
- yield put(openModal('completion'));
+ // TOPCODER: do not open modal
+ // yield put(openModal('completion'));
}
yield put(updateConsole(i18next.t('learn.tests-completed')));
yield put(logsToConsole(i18next.t('learn.console-output')));
diff --git a/client/src/templates/Challenges/video/Show.tsx b/client/src/templates/Challenges/video/Show.tsx
index ecf7bda07b9190..c97cc31f1056a1 100644
--- a/client/src/templates/Challenges/video/Show.tsx
+++ b/client/src/templates/Challenges/video/Show.tsx
@@ -11,6 +11,8 @@ import type { Dispatch } from 'redux';
import { createSelector } from 'reselect';
// Local Utilities
+import Fail from '../../../assets/icons/test-fail';
+import GreenPass from '../../../assets/icons/test-pass';
import Loader from '../../../components/helpers/loader';
import Spacer from '../../../components/helpers/spacer';
import LearnLayout from '../../../components/layouts/learn';
@@ -18,14 +20,13 @@ import { ChallengeNode, ChallengeMeta } from '../../../redux/prop-types';
import ChallengeDescription from '../components/Challenge-Description';
import Hotkeys from '../components/Hotkeys';
import VideoPlayer from '../components/VideoPlayer';
-import ChallengeTitle from '../components/challenge-title';
import CompletionModal from '../components/completion-modal';
import PrismFormatted from '../components/prism-formatted';
import {
isChallengeCompletedSelector,
challengeMounted,
updateChallengeMeta,
- openModal,
+ submitChallenge,
updateSolutionFormValues
} from '../redux';
@@ -42,10 +43,10 @@ const mapStateToProps = createSelector(
const mapDispatchToProps = (dispatch: Dispatch) =>
bindActionCreators(
{
+ submitChallenge,
updateChallengeMeta,
challengeMounted,
- updateSolutionFormValues,
- openCompletionModal: () => openModal('completion')
+ updateSolutionFormValues
},
dispatch
);
@@ -56,16 +57,17 @@ interface ShowVideoProps {
data: { challengeNode: ChallengeNode };
description: string;
isChallengeCompleted: boolean;
- openCompletionModal: () => void;
pageContext: {
challengeMeta: ChallengeMeta;
};
+ submitChallenge: () => void;
t: TFunction;
updateChallengeMeta: (arg0: ChallengeMeta) => void;
updateSolutionFormValues: () => void;
}
interface ShowVideoState {
+ completed: boolean;
subtitles: string;
downloadURL: string | null;
selectedOption: number | null;
@@ -87,7 +89,8 @@ class ShowVideo extends Component {
selectedOption: null,
answer: 1,
showWrong: false,
- videoIsLoaded: false
+ videoIsLoaded: false,
+ completed: false
};
this.handleSubmit = this.handleSubmit.bind(this);
@@ -143,12 +146,12 @@ class ShowVideo extends Component {
}
}
- handleSubmit(solution: number, openCompletionModal: () => void) {
+ handleSubmit(solution: number) {
if (solution - 1 === this.state.selectedOption) {
this.setState({
+ completed: true,
showWrong: false
});
- openCompletionModal();
} else {
this.setState({
showWrong: true
@@ -182,7 +185,6 @@ class ShowVideo extends Component {
superBlock,
certification,
block,
- translationPending,
videoId,
videoLocaleIds,
bilibiliIds,
@@ -190,22 +192,20 @@ class ShowVideo extends Component {
}
}
},
- openCompletionModal,
pageContext: {
challengeMeta: { nextChallengePath, prevChallengePath }
},
t,
- isChallengeCompleted
+ submitChallenge
} = this.props;
const blockNameTitle = `${t(
`intro:${superBlock}.blocks.${block}.title`
)} - ${title}`;
+
return (
{
- this.handleSubmit(solution, openCompletionModal);
- }}
+ executeChallenge={() => this.handleSubmit(solution)}
innerRef={(c: HTMLElement | null) => (this._container = c)}
nextChallengePath={nextChallengePath}
prevChallengePath={prevChallengePath}
@@ -216,16 +216,6 @@ class ShowVideo extends Component {
/>
-
-
- {title}
-
-
{!this.state.videoIsLoaded ? (
@@ -243,9 +233,20 @@ class ShowVideo extends Component {
/>
+
+ Question
+
+
-
+
@@ -276,28 +277,41 @@ class ShowVideo extends Component {
-
- {this.state.showWrong ? (
-
{t('learn.wrong-answer')}
- ) : (
+
+ {this.state.showWrong && (
+
+
+ {t('learn.wrong-answer')}
+
+ )}
+ {this.state.completed && (
+
+
+ Great Job!
+
+ )}
+
+ {!this.state.completed && !this.state.showWrong && (
{t('learn.check-answer')}
)}
-
-
- this.handleSubmit(solution, openCompletionModal)
- }
- >
- {t('buttons.check-answer')}
-
+ {this.state.completed ? (
+
+ {t('buttons.submit-and-go')}
+
+ ) : (
+
this.handleSubmit(solution)}
+ >
+ {t('buttons.check-answer')}
+
+ )}
label {
margin: 0;
- align-items: center;
+ align-items: flex-start;
overflow-x: auto;
scrollbar-width: thin;
scrollbar-color: var(--quaternary-background) var(--secondary-background);
@@ -54,22 +54,18 @@
}
.video-quiz-option-label {
- padding: 20px;
+ padding: 10px 0;
cursor: pointer;
display: flex;
font-weight: normal;
- border-left: 4px solid var(--tertiary-background);
- border-right: 4px solid var(--tertiary-background);
- border-top: 2px solid var(--tertiary-background);
- border-bottom: 2px solid var(--tertiary-background);
}
.video-quiz-option-label:first-child {
- border-top: 4px solid var(--tertiary-background);
+ padding-top: 0;
}
.video-quiz-option-label:last-child {
- border-bottom: 4px solid var(--tertiary-background);
+ padding-bottom: 0;
}
.video-quiz-input-hidden {
@@ -78,7 +74,7 @@
}
.video-quiz-input-visible {
- margin-right: 15px;
+ margin-right: 8px;
position: relative;
top: 2px;
display: inline-block;
@@ -87,8 +83,12 @@
max-width: 20px;
max-height: 20px;
border-radius: 50%;
- background-color: var(--secondary-background);
- border: 2px solid var(--primary-color);
+ background-color: var(--tc-white);
+ border: 1px solid var(--tc-black-60);
+}
+
+.video-quiz-input-visible:not(:empty) {
+ border-color: var(--tc-turq-160);
}
.video-quiz-selected-input {
@@ -97,7 +97,7 @@
position: absolute;
top: 50%;
left: 50%;
- background-color: var(--primary-color);
+ background-color: var(--tc-turq-160);
border-radius: 50%;
transform: translate(-50%, -50%);
}
@@ -114,3 +114,47 @@
/* remove default prism background */
background: none;
}
+
+.challenge-instructions {
+ color: var(--tc-black-100);
+}
+
+.challenge-instructions:not(:empty) {
+ margin-bottom: 18px;
+}
+
+.video-description .line-numbers > p:first-child {
+ font-family: 'Roboto';
+ font-weight: bold;
+ line-height: 26px;
+ font-size: 20px;
+ color: var(--tc-black-100);
+}
+
+.video-quiz-cta-text {
+ font-size: 16px;
+ line-height: 24px;
+ font-family: 'Roboto';
+ color: var(--tc-black-100);
+ margin-bottom: 24px;
+}
+
+.video-quiz-cta-text > span {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.video-section-label {
+ font-size: 18px;
+ font-weight: 600;
+ font-family: 'Barlow', 'sans-serif';
+ line-height: 22px;
+ color: var(--tc-black-100);
+ text-transform: uppercase;
+
+ border-top: 1px solid var(--tc-black-10);
+ margin-top: 32px;
+ padding-top: 18px;
+ margin-bottom: 24px;
+}
diff --git a/config/analytics-settings.js b/config/analytics-settings.js
index 106725cc3b67eb..b2b6bad8f3abbf 100644
--- a/config/analytics-settings.js
+++ b/config/analytics-settings.js
@@ -1,2 +1,8 @@
-exports.prodAnalyticsId = 'UA-55446531-10';
-exports.devAnalyticsId = 'UA-55446531-19';
+exports.prodAnalyticsId = null;
+exports.devAnalyticsId = null;
+
+exports.prodSegmentId = '8fCbi94o3ruUUGxRRGxWu194t6iVq9LH';
+exports.devSegmentId = null;
+
+exports.prodTagManagerId = 'GTM-MXXQHG8';
+exports.devTagManagerId = 'GTM-W7B537Z';
diff --git a/curriculum/challenges/english/03-front-end-development-libraries/front-end-development-libraries-projects/build-a-25-5-clock.md b/curriculum/challenges/english/03-front-end-development-libraries/front-end-development-libraries-projects/build-a-25-5-clock.md
index 61119b0bca25d8..c22604450334d3 100644
--- a/curriculum/challenges/english/03-front-end-development-libraries/front-end-development-libraries-projects/build-a-25-5-clock.md
+++ b/curriculum/challenges/english/03-front-end-development-libraries/front-end-development-libraries-projects/build-a-25-5-clock.md
@@ -70,10 +70,11 @@ You can use any mix of HTML, JavaScript, CSS, Bootstrap, SASS, React, Redux, and
**User Story #28:** The audio element with id of `beep` must stop playing and be rewound to the beginning when the element with the id of `reset` is clicked.
-You can build your project by using this CodePen template and clicking `Save` to create your own pen. Or you can use this CDN link to run the tests in any environment you like: `https://cdn.freecodecamp.org/testable-projects-fcc/v1/bundle.js`
+You can build your project by using this CodePen template and clicking `Save` to create your own pen. Or you can use this CDN link to run the tests in any environment you like: `https://cdn.freecodecamp.org/testable-projects-fcc/v1/bundle.js`
Once you're done, submit the URL to your working project with all its tests passing.
+
# --solutions--
```js
diff --git a/package-lock.json b/package-lock.json
index f7aba780c362f1..78de8153449668 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -562,6 +562,7 @@
"react": "16.14.0",
"react-dom": "16.14.0",
"react-final-form": "6.5.9",
+ "react-gtm-module": "^2.0.11",
"react-ga": "3.3.1",
"react-helmet": "6.1.0",
"react-hotkeys": "2.0.0",
@@ -43375,6 +43376,11 @@
"react": "^15.6.2 || ^16.0 || ^17 || ^18"
}
},
+ "node_modules/react-gtm-module": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/react-gtm-module/-/react-gtm-module-2.0.11.tgz",
+ "integrity": "sha512-8gyj4TTxeP7eEyc2QKawEuQoAZdjKvMY4pgWfycGmqGByhs17fR+zEBs0JUDq4US/l+vbTl+6zvUIx27iDo/Vw=="
+ },
"node_modules/react-helmet": {
"version": "6.1.0",
"license": "MIT",
@@ -57190,6 +57196,7 @@
"react": "16.14.0",
"react-dom": "16.14.0",
"react-final-form": "6.5.9",
+ "react-gtm-module": "^2.0.11",
"react-ga": "3.3.1",
"react-helmet": "6.1.0",
"react-hotkeys": "2.0.0",
@@ -84488,6 +84495,11 @@
"integrity": "sha512-4Vc0W5EvXAXUN/wWyxvsAKDLLgtJ3oLmhYYssx+YzphJpejtOst6cbIHCIyF50Fdxuf5DDKqRYny24yJ2y7GFQ==",
"requires": {}
},
+ "react-gtm-module": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/react-gtm-module/-/react-gtm-module-2.0.11.tgz",
+ "integrity": "sha512-8gyj4TTxeP7eEyc2QKawEuQoAZdjKvMY4pgWfycGmqGByhs17fR+zEBs0JUDq4US/l+vbTl+6zvUIx27iDo/Vw=="
+ },
"react-helmet": {
"version": "6.1.0",
"requires": {
diff --git a/tools/challenge-editor/api/configs/superBlockList.ts b/tools/challenge-editor/api/configs/superBlockList.ts
index cb9b1c82bb7086..ffebc33c1f9939 100644
--- a/tools/challenge-editor/api/configs/superBlockList.ts
+++ b/tools/challenge-editor/api/configs/superBlockList.ts
@@ -48,7 +48,7 @@ export const superBlockList = [
path: '13-relational-databases'
},
{
- name: '(New) Responsive Web Design',
+ name: 'Responsive Web Design',
path: '14-responsive-web-design-22'
},
{