diff --git a/pmp-frontend-app/package-lock.json b/pmp-frontend-app/package-lock.json index 3f5437c..91fc783 100644 --- a/pmp-frontend-app/package-lock.json +++ b/pmp-frontend-app/package-lock.json @@ -13,6 +13,7 @@ "js-cookie": "^3.0.5", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-google-recaptcha": "^3.1.0", "react-markdown": "^9.0.1", "react-router-dom": "^6.22.3", "remark-gfm": "^4.0.0" @@ -21,6 +22,7 @@ "@types/js-cookie": "^3.0.6", "@types/react": "^18.2.64", "@types/react-dom": "^18.2.21", + "@types/react-google-recaptcha": "^2.1.9", "@typescript-eslint/eslint-plugin": "^7.1.1", "@typescript-eslint/parser": "^7.1.1", "@vitejs/plugin-react": "^4.2.1", @@ -1380,6 +1382,15 @@ "@types/react": "*" } }, + "node_modules/@types/react-google-recaptcha": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@types/react-google-recaptcha/-/react-google-recaptcha-2.1.9.tgz", + "integrity": "sha512-nT31LrBDuoSZJN4QuwtQSF3O89FVHC4jLhM+NtKEmVF5R1e8OY0Jo4//x2Yapn2aNHguwgX5doAq8Zo+Ehd0ug==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/scheduler": { "version": "0.16.8", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", @@ -2859,6 +2870,14 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, "node_modules/html-url-attributes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.0.tgz", @@ -4156,7 +4175,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -4520,6 +4538,16 @@ "node": ">= 0.8.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/property-information": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.4.1.tgz", @@ -4569,6 +4597,18 @@ "node": ">=0.10.0" } }, + "node_modules/react-async-script": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/react-async-script/-/react-async-script-1.2.0.tgz", + "integrity": "sha512-bCpkbm9JiAuMGhkqoAiC0lLkb40DJ0HOEJIku+9JDjxX3Rcs+ztEOG13wbrOskt3n2DTrjshhaQ/iay+SnGg5Q==", + "dependencies": { + "hoist-non-react-statics": "^3.3.0", + "prop-types": "^15.5.0" + }, + "peerDependencies": { + "react": ">=16.4.1" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -4581,6 +4621,23 @@ "react": "^18.2.0" } }, + "node_modules/react-google-recaptcha": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-3.1.0.tgz", + "integrity": "sha512-cYW2/DWas8nEKZGD7SCu9BSuVz8iOcOLHChHyi7upUuVhkpkhYG/6N3KDiTQ3XAiZ2UAZkfvYKMfAHOzBOcGEg==", + "dependencies": { + "prop-types": "^15.5.0", + "react-async-script": "^1.2.0" + }, + "peerDependencies": { + "react": ">=16.4.1" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/react-markdown": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.0.1.tgz", diff --git a/pmp-frontend-app/package.json b/pmp-frontend-app/package.json index 8eac3db..b1a8540 100644 --- a/pmp-frontend-app/package.json +++ b/pmp-frontend-app/package.json @@ -15,6 +15,7 @@ "js-cookie": "^3.0.5", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-google-recaptcha": "^3.1.0", "react-markdown": "^9.0.1", "react-router-dom": "^6.22.3", "remark-gfm": "^4.0.0" @@ -23,6 +24,7 @@ "@types/js-cookie": "^3.0.6", "@types/react": "^18.2.64", "@types/react-dom": "^18.2.21", + "@types/react-google-recaptcha": "^2.1.9", "@typescript-eslint/eslint-plugin": "^7.1.1", "@typescript-eslint/parser": "^7.1.1", "@vitejs/plugin-react": "^4.2.1", diff --git a/pmp-frontend-app/src/components/ContactFormComponent.tsx b/pmp-frontend-app/src/components/ContactFormComponent.tsx index 9504cf3..2321f87 100644 --- a/pmp-frontend-app/src/components/ContactFormComponent.tsx +++ b/pmp-frontend-app/src/components/ContactFormComponent.tsx @@ -1,5 +1,6 @@ -import { ChangeEvent, FormEvent, ReactElement, useState } from "react"; +import { ChangeEvent, ReactElement, useState } from "react"; import React from "react"; +import ReCAPTCHA from "react-google-recaptcha"; export default function ContactFormComponent(): ReactElement { const [inputFields, setInputFields] = useState({ @@ -12,33 +13,41 @@ export default function ContactFormComponent(): ReactElement { email: "", message: "" }); + const [recaptchaPassed, setRecaptchaPassed] = useState(false); - const messageCharLimit = 800; + const messageCharLimit = 1000; - const handleSubmit = (e: FormEvent) => { - e.preventDefault(); - let validForm = true; + function checkFormFilled(): boolean { + let key: keyof typeof inputFields; + for (key in inputFields) { + if (!inputFields[key]) { + return false; + } + } + return true; + } + + function checkValidForm(): boolean { let key: keyof typeof errors; for (key in errors) { if (errors[key]) { - validForm = false; + return false; } } - if (validForm) { - console.log("Valid form:"); - console.log(inputFields); - } - else { - console.log("Non valid form."); - console.log(errors); - } - }; + return true; + } function handleChange(e: ChangeEvent | ChangeEvent) { setInputFields({ ...inputFields, [e.target.name]: e.target.value}); } + function onChangeRecaptcha() { + setRecaptchaPassed(true); + } + + React.useEffect(() =>{ + // form validation let errors_tmp = { name: "", email: "", @@ -60,7 +69,7 @@ export default function ContactFormComponent(): ReactElement { return(
-
+
@@ -97,6 +106,7 @@ export default function ContactFormComponent(): ReactElement {

) : null}
+
@@ -116,8 +126,25 @@ export default function ContactFormComponent(): ReactElement { {errors.message}

}
+ +
+
- + {(checkFormFilled() && checkValidForm()) ? + (recaptchaPassed ? + + : + <> +

Please tick 'I'm not a robot' above the 'Submit' button.

+
Submit
+ + ) + : +
Submit
+ }
diff --git a/pmp-frontend-app/src/pages/ContactPage.tsx b/pmp-frontend-app/src/pages/ContactPage.tsx index 5f769d5..76e505a 100644 --- a/pmp-frontend-app/src/pages/ContactPage.tsx +++ b/pmp-frontend-app/src/pages/ContactPage.tsx @@ -4,6 +4,7 @@ import { BODY_CLASSES, H_1 } from '../constants'; import { ILink } from '../interfaces/types'; import { Link } from 'react-router-dom'; import { ContactPageContent } from '../content/content'; +import ContactFormComponent from '../components/ContactFormComponent'; export default function ContactPage(): ReactElement { TrackPageViewIfEnabled(); @@ -25,7 +26,7 @@ export default function ContactPage(): ReactElement {
Contact
{ContactPageContent.content[0].header}

{ContactPageContent.content[0].body}

- {/**/} +
{ContactPageContent.content[1].header}

{ContactPageContent.content[1].body}