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';