diff --git a/api-server/src/server/boot/challenge.js b/api-server/src/server/boot/challenge.js index 4ead5426a9e277..38ac25098e3a73 100644 --- a/api-server/src/server/boot/challenge.js +++ b/api-server/src/server/boot/challenge.js @@ -260,14 +260,17 @@ export function isValidChallengeCompletion(req, res, next) { if (!ObjectID.isValid(id)) { log('isObjectId', id, ObjectID.isValid(id)); + console.debug('isObjectId', id, ObjectID.isValid(id)); return res.status(403).json(isValidChallengeCompletionErrorMsg); } if ('challengeType' in req.body && !isNumeric(String(challengeType))) { log('challengeType', challengeType, isNumeric(challengeType)); + console.debug('challengeType', challengeType, isNumeric(challengeType)); return res.status(403).json(isValidChallengeCompletionErrorMsg); } if ('solution' in req.body && !isURL(solution)) { - log('isObjectId', id, ObjectID.isValid(id)); + log('solution', solution, !isURL(solution)); + console.debug('solution', solution, !isURL(solution)); return res.status(403).json(isValidChallengeCompletionErrorMsg); } return next(); diff --git a/api-server/src/server/middlewares/sessions.js b/api-server/src/server/middlewares/sessions.js index f24141c7038fea..219ed97724a5b8 100644 --- a/api-server/src/server/middlewares/sessions.js +++ b/api-server/src/server/middlewares/sessions.js @@ -5,8 +5,6 @@ const MongoStore = MongoStoreFactory(session); const sessionSecret = process.env.SESSION_SECRET; const url = process.env.MONGODB || process.env.MONGOHQ_URL; -console.log('session DB url', url); - export default function sessionsMiddleware() { return session({ // 900 day session cookie diff --git a/client/src/analytics/index.tsx b/client/src/analytics/index.tsx index 7979177022806b..8362761097bd8e 100644 --- a/client/src/analytics/index.tsx +++ b/client/src/analytics/index.tsx @@ -7,8 +7,10 @@ import envData from '../../../config/env.json'; const { deploymentEnv } = envData; -const analyticsId = - deploymentEnv === 'staging' ? devAnalyticsId : prodAnalyticsId; +const analyticsId: string = + deploymentEnv === 'staging' + ? (devAnalyticsId as string) + : (prodAnalyticsId as string); ReactGA.initialize(analyticsId); diff --git a/client/src/components/layouts/tc-integration.tsx b/client/src/components/layouts/tc-integration.tsx index 136cd7f7acd1a3..6b5d61eca74c67 100755 --- a/client/src/components/layouts/tc-integration.tsx +++ b/client/src/components/layouts/tc-integration.tsx @@ -144,7 +144,7 @@ class TcIntegrationLayout extends Component { // provider. // set the pathname for the 2 flavors of lesson URL - const platformPathPrefix = 'learn/freecodecamp'; + const platformPathPrefix = 'learn/freeCodeCamp'; const learnPrefix = '/learn/'; let updateHost = false; if (url.host === `learn.${fccHost}`) { diff --git a/client/src/declarations.d.ts b/client/src/declarations.d.ts index 53d7d2fe927ce2..5591e3a3edc2f9 100644 --- a/client/src/declarations.d.ts +++ b/client/src/declarations.d.ts @@ -4,6 +4,7 @@ declare module '@freecodecamp/strip-comments'; declare module '@types/react-redux'; declare module '@types/validator'; declare module '@types/lodash-es'; +declare module 'react-gtm-module'; declare module 'react-lazy-load'; declare module '*.svg' { const content: string; diff --git a/client/src/templates/Challenges/projects/backend/Show.tsx b/client/src/templates/Challenges/projects/backend/Show.tsx index f27009fc4523e4..161a266442b699 100644 --- a/client/src/templates/Challenges/projects/backend/Show.tsx +++ b/client/src/templates/Challenges/projects/backend/Show.tsx @@ -21,8 +21,6 @@ import { 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 Output from '../../components/output'; import TestSuite from '../../components/test-suite'; import { @@ -33,12 +31,12 @@ import { initConsole, initTests, isChallengeCompletedSelector, + testsRunningSelector, updateChallengeMeta, - updateSolutionFormValues + updateSolutionFormValues, + submitChallenge } from '../../redux'; -import { getGuideUrl } from '../../utils'; import SolutionForm from '../solution-form'; -import ProjectToolPanel from '../tool-panel'; import '../../components/test-frame.css'; @@ -48,16 +46,19 @@ const mapStateToProps = createSelector( challengeTestsSelector, isChallengeCompletedSelector, isSignedInSelector, + testsRunningSelector, ( output: string[], tests: Test[], isChallengeCompleted: boolean, - isSignedIn: boolean + isSignedIn: boolean, + testsRunningSelector: boolean ) => ({ tests, output, isChallengeCompleted, - isSignedIn + isSignedIn, + testsRunningSelector }) ); @@ -67,7 +68,8 @@ const mapDispatchToActions = { initConsole, initTests, updateChallengeMeta, - updateSolutionFormValues + updateSolutionFormValues, + submitChallenge }; // Types @@ -86,8 +88,10 @@ interface BackEndProps { pageContext: { challengeMeta: ChallengeMeta; }; + submitChallenge: () => void; t: TFunction; tests: Test[]; + testsRunning: boolean; title: string; updateChallengeMeta: (arg0: ChallengeMeta) => void; updateSolutionFormValues: () => void; @@ -175,9 +179,16 @@ class BackEnd extends Component { } handleSubmit(): void { - this.props.executeChallenge({ - showCompletionModal: false - }); + const { tests, submitChallenge } = this.props; + const isChallengeComplete = tests.every(test => test.pass && !test.err); + + if (isChallengeComplete) { + submitChallenge(); + } else { + this.props.executeChallenge({ + showCompletionModal: false + }); + } } render() { @@ -185,14 +196,11 @@ class BackEnd extends Component { data: { challengeNode: { challenge: { - fields: { blockName }, challengeType, - forumTopicId, title, description, instructions, translationPending, - certification, superBlock, block } @@ -205,9 +213,15 @@ class BackEnd extends Component { }, t, tests, + testsRunning, updateSolutionFormValues } = this.props; + const isChallengeComplete = tests.every(test => test.pass && !test.err); + const submitBtnLabel: string = !isChallengeComplete + ? `${t('buttons.run-test')}${testsRunning ? ' ...' : ''}` + : t('buttons.submit-and-go'); + const blockNameTitle = `${t( `intro:${superBlock}.blocks.${block}.title` )} - ${title}`; @@ -243,9 +257,7 @@ class BackEnd extends Component { // eslint-disable-next-line @typescript-eslint/unbound-method 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 712081077fb0a0..0f7a5cc1afda9b 100644 --- a/client/src/templates/Challenges/projects/solution-form.tsx +++ b/client/src/templates/Challenges/projects/solution-form.tsx @@ -16,6 +16,7 @@ interface SubmitProps { } interface FormProps extends WithTranslation { + buttonLabel?: string; challengeType: number; description?: string; onSubmit: (arg0: SubmitProps) => void; @@ -46,7 +47,7 @@ export class SolutionForm extends Component { }; render(): JSX.Element { - const { challengeType, description, t } = this.props; + const { buttonLabel, challengeType, description, t } = this.props; // back end challenges and front end projects use a single form field const solutionField = [ @@ -57,7 +58,7 @@ export class SolutionForm extends Component { { name: 'githubLink', label: t('learn.github-link') } ]; - const buttonCopy = t('learn.submit-and-go'); + const buttonCopy: string = buttonLabel ?? t('learn.submit-and-go'); const options = { types: {