diff --git a/client/i18n/locales/english/translations.json b/client/i18n/locales/english/translations.json index f82fb0dc563..6f72bd74d7d 100644 --- a/client/i18n/locales/english/translations.json +++ b/client/i18n/locales/english/translations.json @@ -872,7 +872,8 @@ "own-work-url": "Remember to submit your own work.", "publicly-visible-url": "Remember to submit a publicly visible app URL.", "ms-learn-link": "Please use a valid Microsoft Learn trophy link.", - "path-url": "You probably want to submit the root path i.e. https://example.com, not https://example.com/path" + "path-url": "You probably want to submit the root path i.e. https://example.com, not https://example.com/path", + "source-code-link-required": "Remember to submit the link to your source code." }, "certification": { "executive": "Executive Director, freeCodeCamp.org", diff --git a/client/src/components/formHelpers/form-fields.tsx b/client/src/components/formHelpers/form-fields.tsx index c867043b5f2..559869332b7 100644 --- a/client/src/components/formHelpers/form-fields.tsx +++ b/client/src/components/formHelpers/form-fields.tsx @@ -14,13 +14,15 @@ import { composeValidators, fCCValidator, httpValidator, - pathValidator + pathValidator, + sourceCodeLinkExistsValidator } from './form-validators'; export type FormOptions = { ignored?: string[]; isEditorLinkAllowed?: boolean; isLocalLinkAllowed?: boolean; + isSourceCodeLinkRequired?: boolean; required?: string[]; types?: { [key: string]: string }; placeholders?: { [key: string]: string }; @@ -38,7 +40,8 @@ function FormFields({ formFields, options }: FormFieldsProps): JSX.Element { required = [], types = {}, isEditorLinkAllowed = false, - isLocalLinkAllowed = false + isLocalLinkAllowed = false, + isSourceCodeLinkRequired = false } = options; const nullOrWarning = ( @@ -64,6 +67,9 @@ function FormFields({ formFields, options }: FormFieldsProps): JSX.Element { validators.push(pathValidator); } } + if (isSourceCodeLinkRequired && name === 'githubLink') { + validators.push(sourceCodeLinkExistsValidator); + } if (!isLocalLinkAllowed) { validators.push(localhostValidator); } diff --git a/client/src/components/formHelpers/form-validators.tsx b/client/src/components/formHelpers/form-validators.tsx index 31be1eb6218..ba21986994e 100644 --- a/client/src/components/formHelpers/form-validators.tsx +++ b/client/src/components/formHelpers/form-validators.tsx @@ -38,6 +38,9 @@ export const httpValidator: Validator = value => export const pathValidator: Validator = value => isPathRoot(value) ? validation.path-url : null; +export const sourceCodeLinkExistsValidator: Validator = value => + value ? null : validation.source-code-link-required; + export function composeValidators(...validators: Validator[]) { return (value: string): ReturnType | null => validators.reduce( diff --git a/client/src/components/formHelpers/form.tsx b/client/src/components/formHelpers/form.tsx index 9d52b4592e6..badf91b473d 100644 --- a/client/src/components/formHelpers/form.tsx +++ b/client/src/components/formHelpers/form.tsx @@ -8,7 +8,8 @@ import { editorValidator, composeValidators, fCCValidator, - httpValidator + httpValidator, + sourceCodeLinkExistsValidator } from './form-validators'; import FormFields, { FormOptions } from './form-fields'; @@ -35,43 +36,59 @@ function validateFormValues( formValues: FormValues, options: FormOptions ): ValidatedValues { - const { isEditorLinkAllowed, isLocalLinkAllowed, types } = options; + const { + isEditorLinkAllowed, + isLocalLinkAllowed, + isSourceCodeLinkRequired, + types + } = options; const validatedValues: ValidatedValues = { values: {}, errors: [], invalidValues: [] }; - const urlValues = Object.entries(formValues).reduce( - (result, [key, value]) => { - // NOTE: pathValidator is not used here, because it is only used as a - // suggestion - should not prevent form submission - const validators = [fCCValidator, httpValidator]; - const isSolutionLink = key !== 'githubLink'; - if (isSolutionLink && !isEditorLinkAllowed) { - validators.push(editorValidator); - } - if (!isLocalLinkAllowed) { - validators.push(localhostValidator); - } - const nullOrWarning = composeValidators(...validators)(value); - if (nullOrWarning) { - validatedValues.invalidValues.push(nullOrWarning); + const formFields = Object.entries(formValues); + // We don't always get a githubLink field in formValues, so we can't simply + // validate that field like the others. We have to handle it separately. + if (isSourceCodeLinkRequired) { + const githubLink = formValues['githubLink']; + if (!githubLink) { + validatedValues.invalidValues.push(sourceCodeLinkExistsValidator('')); + } + } + + const urlValues = formFields.reduce((result, [key, value]) => { + // NOTE: pathValidator is not used here, because it is only used as a + // suggestion - should not prevent form submission + const validators = [fCCValidator, httpValidator]; + const isSolutionLink = key !== 'githubLink'; + if (isSolutionLink && !isEditorLinkAllowed) { + validators.push(editorValidator); + } + if (!isLocalLinkAllowed) { + validators.push(localhostValidator); + } + if (isSourceCodeLinkRequired) { + validators.push(sourceCodeLinkExistsValidator); + } + + const nullOrWarning = composeValidators(...validators)(value); + if (nullOrWarning) { + validatedValues.invalidValues.push(nullOrWarning); + } + if (value && types && types[key] === 'url') { + try { + value = normalizeUrl(value, normalizeOptions); + } catch (err: unknown) { + validatedValues.errors.push({ + error: err as { message?: string }, + value + }); } - if (value && types && types[key] === 'url') { - try { - value = normalizeUrl(value, normalizeOptions); - } catch (err: unknown) { - validatedValues.errors.push({ - error: err as { message?: string }, - value - }); - } - } - return { ...result, [key]: value }; - }, - {} - ); + } + return { ...result, [key]: value }; + }, {}); validatedValues.values = urlValues; return validatedValues; } diff --git a/client/src/templates/Challenges/projects/solution-form.tsx b/client/src/templates/Challenges/projects/solution-form.tsx index e616e6ac613..805ccceb297 100644 --- a/client/src/templates/Challenges/projects/solution-form.tsx +++ b/client/src/templates/Challenges/projects/solution-form.tsx @@ -63,7 +63,8 @@ export class SolutionForm extends Component { }, required: ['solution'], isEditorLinkAllowed: false, - isLocalLinkAllowed: false + isLocalLinkAllowed: false, + isSourceCodeLinkRequired: false }; let formFields = solutionField; @@ -85,7 +86,8 @@ export class SolutionForm extends Component { case challengeTypes.backEndProject: formFields = backEndProjectFields; - options.required.push('githubLink'); + // options.required.push('githubLink'); + options.isSourceCodeLinkRequired = true; options.isLocalLinkAllowed = true; solutionLink = solutionLink + 'https://3000-project-url.gitpod.io/'; solutionFormID = 'back-end-form';