diff --git a/package-lock.json b/package-lock.json index 6447d089..aa940f44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-error-boundary": "^4.0.13", + "react-google-recaptcha-v3": "^1.11.0", "react-helmet": "6.1.0", "react-loading-skeleton": "3.5.0", "react-redux": "7.2.9", @@ -22541,6 +22542,18 @@ } } }, + "node_modules/react-google-recaptcha-v3": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/react-google-recaptcha-v3/-/react-google-recaptcha-v3-1.11.0.tgz", + "integrity": "sha512-kLQqpz/77m8+trpBwzqcxNtvWZYoZ/YO6Vm2cVTHW8hs80BWUfDpC7RDwuAvpswwtSYApWfaSpIDFWAIBNIYxQ==", + "dependencies": { + "hoist-non-react-statics": "^3.3.2" + }, + "peerDependencies": { + "react": "^16.3 || ^17.0 || ^18.0 || ^19.0", + "react-dom": "^17.0 || ^18.0 || ^19.0" + } + }, "node_modules/react-helmet": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz", diff --git a/package.json b/package.json index 42406448..698a60fb 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "react-error-boundary": "^4.0.13", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-google-recaptcha-v3": "^1.11.0", "react-helmet": "6.1.0", "react-loading-skeleton": "3.5.0", "react-redux": "7.2.9", diff --git a/src/MainApp.jsx b/src/MainApp.jsx index 7beaa3d1..08016e52 100755 --- a/src/MainApp.jsx +++ b/src/MainApp.jsx @@ -2,6 +2,7 @@ import React from 'react'; import { getConfig } from '@edx/frontend-platform'; import { AppProvider } from '@edx/frontend-platform/react'; +import { GoogleReCaptchaProvider } from 'react-google-recaptcha-v3'; import { Helmet } from 'react-helmet'; import { Navigate, Route, Routes } from 'react-router-dom'; @@ -34,31 +35,35 @@ registerIcons(); const MainApp = () => ( - - - - - } /> - } - /> - - } - /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - + + + + + {getConfig().ZENDESK_KEY && } + + } /> + } + /> + + } + /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + ); diff --git a/src/register/RegistrationPage.jsx b/src/register/RegistrationPage.jsx index 50236e80..b1992183 100644 --- a/src/register/RegistrationPage.jsx +++ b/src/register/RegistrationPage.jsx @@ -8,6 +8,7 @@ import { useIntl } from '@edx/frontend-platform/i18n'; import { Form, Spinner, StatefulButton } from '@openedx/paragon'; import classNames from 'classnames'; import PropTypes from 'prop-types'; +import { useGoogleReCaptcha } from 'react-google-recaptcha-v3'; import { Helmet } from 'react-helmet'; import Skeleton from 'react-loading-skeleton'; @@ -60,6 +61,7 @@ import { trackRegistrationPageViewed, trackRegistrationSuccess } from '../tracki const RegistrationPage = (props) => { const { formatMessage } = useIntl(); const dispatch = useDispatch(); + const { executeRecaptcha } = useGoogleReCaptcha(); const registrationEmbedded = isHostAvailableInQueryParams(); const platformName = getConfig().SITE_NAME; @@ -106,6 +108,8 @@ const RegistrationPage = (props) => { const [formStartTime, setFormStartTime] = useState(null); // temporary error state for embedded experience because we don't want to show errors on blur const [temporaryErrors, setTemporaryErrors] = useState({ ...backedUpFormData.errors }); + const [captchaError, setCaptchaError] = useState(''); + const intl = useIntl(); const { cta, host } = queryParams; const buttonLabel = cta @@ -230,7 +234,8 @@ const RegistrationPage = (props) => { } }; - const registerUser = () => { + const registerUser = async () => { + let recaptchaToken = ''; const totalRegistrationTime = (Date.now() - formStartTime) / 1000; let payload = { ...formFields, app_name: APP_NAME }; @@ -259,16 +264,35 @@ const RegistrationPage = (props) => { return; } + if (executeRecaptcha) { + try { + recaptchaToken = await executeRecaptcha('submit_post'); + if (!recaptchaToken) { + setCaptchaError(intl.formatMessage(messages['discussions.captcha.verification.label'])); + return; + } + } catch (error) { + setCaptchaError(intl.formatMessage(messages['discussions.captcha.verification.label'])); + return; + } + setCaptchaError(''); + } // Preparing payload for submission - payload = prepareRegistrationPayload( - payload, - configurableFormFields, - flags.showMarketingEmailOptInCheckbox, - totalRegistrationTime, - queryParams); + if (recaptchaToken) { + payload = prepareRegistrationPayload( + payload, + configurableFormFields, + flags.showMarketingEmailOptInCheckbox, + totalRegistrationTime, + queryParams); - // making register call - dispatch(registerNewUser(payload)); + const updatedpayload = { + ...payload, + captcha_token: recaptchaToken, + }; + // making register call + dispatch(registerNewUser(updatedpayload)); + } }; const handleSubmit = (e) => { @@ -393,8 +417,12 @@ const RegistrationPage = (props) => { setFormFields={setConfigurableFormFields} autoSubmitRegisterForm={autoSubmitRegForm} fieldDescriptions={fieldDescriptions} - countriesCodesList={countriesCodesList} /> + {captchaError && ( +
+ {captchaError} +
+ )}