From f0025ea06633eb470e71d6283dfc13caf70ba07f Mon Sep 17 00:00:00 2001 From: tom Date: Mon, 2 Dec 2024 19:40:42 +0100 Subject: [PATCH 01/15] show recaptcha in the site footer --- docs/ENVS.md | 1 + lib/contexts/reCaptcha.tsx | 34 +++++ pages/_app.tsx | 27 ++-- ui/csvExport/CsvExportForm.tsx | 47 ++++--- ui/myProfile/MyProfileEmail.tsx | 7 +- ui/publicTags/submit/PublicTagsSubmitForm.tsx | 121 +++++++++--------- .../custom/AppErrorTooManyRequests.tsx | 7 +- ui/snippets/auth/AuthModal.tsx | 5 +- ui/snippets/footer/Footer.tsx | 22 ++++ .../TokenInstanceMetadataFetcher.tsx | 7 +- 10 files changed, 163 insertions(+), 115 deletions(-) create mode 100644 lib/contexts/reCaptcha.tsx diff --git a/docs/ENVS.md b/docs/ENVS.md index 08614deae3..caf850843f 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -849,3 +849,4 @@ For obtaining the variables values please refer to [reCAPTCHA documentation](htt | --- | --- | --- | --- | --- | --- | --- | | NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY | `string` | Google reCAPTCHA v3 site key | - | - | `` | v1.36.0+ | | NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY | `string` | **DEPRECATED** Google reCAPTCHA v2 site key | - | - | `` | v1.0.x+ | + \ No newline at end of file diff --git a/lib/contexts/reCaptcha.tsx b/lib/contexts/reCaptcha.tsx new file mode 100644 index 0000000000..8bcb2e0104 --- /dev/null +++ b/lib/contexts/reCaptcha.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { GoogleReCaptchaProvider } from 'react-google-recaptcha-v3'; + +import config from 'configs/app'; + +export const RECAPTCHA_CONTAINER_ID = 'recaptcha-container'; + +const CONTAINER_PROPS = { + element: RECAPTCHA_CONTAINER_ID, + parameters: { + badge: 'inline' as const, + }, +}; + +interface Props { + children: React.ReactNode; +} + +const ReCaptchaProvider = ({ children }: Props) => { + if (!config.services.reCaptchaV3.siteKey) { + return children; + } + + return ( + + { children } + + ); +}; + +export default ReCaptchaProvider; diff --git a/pages/_app.tsx b/pages/_app.tsx index bc583e44e8..b2191918ac 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -12,6 +12,7 @@ import useQueryClientConfig from 'lib/api/useQueryClientConfig'; import { AppContextProvider } from 'lib/contexts/app'; import { ChakraProvider } from 'lib/contexts/chakra'; import { MarketplaceContextProvider } from 'lib/contexts/marketplace'; +import ReCaptchaProvider from 'lib/contexts/reCaptcha'; import { RewardsContextProvider } from 'lib/contexts/rewards'; import { ScrollDirectionProvider } from 'lib/contexts/scrollDirection'; import { SettingsContextProvider } from 'lib/contexts/settings'; @@ -66,18 +67,20 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { - - - - - - { getLayout() } - { config.features.rewards.isEnabled && } - - - - - + + + + + + + { getLayout() } + { config.features.rewards.isEnabled && } + + + + + + diff --git a/ui/csvExport/CsvExportForm.tsx b/ui/csvExport/CsvExportForm.tsx index bbde5b3185..0bcfcac8bb 100644 --- a/ui/csvExport/CsvExportForm.tsx +++ b/ui/csvExport/CsvExportForm.tsx @@ -1,6 +1,5 @@ import { Alert, Button, chakra, Flex } from '@chakra-ui/react'; import React from 'react'; -import { GoogleReCaptchaProvider } from 'react-google-recaptcha-v3'; import type { SubmitHandler } from 'react-hook-form'; import { useForm, FormProvider } from 'react-hook-form'; @@ -88,31 +87,29 @@ const CsvExportForm = ({ hash, resource, filterType, filterValue, fileNameTempla } return ( - - - + + + { exportType !== 'holders' && } + { exportType !== 'holders' && } + + + - - - + Download + + + ); }; diff --git a/ui/myProfile/MyProfileEmail.tsx b/ui/myProfile/MyProfileEmail.tsx index 0a2e48d45e..fba8313e3a 100644 --- a/ui/myProfile/MyProfileEmail.tsx +++ b/ui/myProfile/MyProfileEmail.tsx @@ -1,7 +1,6 @@ import { Button, chakra, Heading, useDisclosure } from '@chakra-ui/react'; import type { UseQueryResult } from '@tanstack/react-query'; import React from 'react'; -import { GoogleReCaptchaProvider } from 'react-google-recaptcha-v3'; import type { SubmitHandler } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form'; @@ -85,11 +84,7 @@ const MyProfileEmail = ({ profileQuery }: Props) => { isReadOnly={ !config.services.reCaptchaV3.siteKey || Boolean(profileQuery.data?.email) } defaultValue={ profileQuery.data?.email || undefined } /> - { config.services.reCaptchaV3.siteKey && !profileQuery.data?.email && ( - - - - ) } + { config.services.reCaptchaV3.siteKey && !profileQuery.data?.email && } { config.services.reCaptchaV3.siteKey && !profileQuery.data?.email && ( - - - - + Send request + + + + ); }; diff --git a/ui/shared/AppError/custom/AppErrorTooManyRequests.tsx b/ui/shared/AppError/custom/AppErrorTooManyRequests.tsx index 3600f79ae7..1a30a1c1e7 100644 --- a/ui/shared/AppError/custom/AppErrorTooManyRequests.tsx +++ b/ui/shared/AppError/custom/AppErrorTooManyRequests.tsx @@ -1,6 +1,6 @@ import { Button, Text } from '@chakra-ui/react'; import React from 'react'; -import { GoogleReCaptcha, GoogleReCaptchaProvider } from 'react-google-recaptcha-v3'; +import { GoogleReCaptcha } from 'react-google-recaptcha-v3'; import config from 'configs/app'; import buildUrl from 'lib/api/buildUrl'; @@ -50,18 +50,19 @@ const AppErrorTooManyRequests = () => { } return ( - + <> You have exceeded the request rate for a given time period. Please reduce the number of requests and try again soon. + { /* TODO @tom2drum migrate to common form field component */ } - + ); }; diff --git a/ui/snippets/auth/AuthModal.tsx b/ui/snippets/auth/AuthModal.tsx index 6c532ce1ca..99ecf0cad9 100644 --- a/ui/snippets/auth/AuthModal.tsx +++ b/ui/snippets/auth/AuthModal.tsx @@ -2,7 +2,6 @@ import { Modal, ModalBody, ModalCloseButton, ModalContent, ModalHeader, ModalOve import { useQueryClient } from '@tanstack/react-query'; import { useRouter } from 'next/router'; import React from 'react'; -import { GoogleReCaptchaProvider } from 'react-google-recaptcha-v3'; import type { Screen, ScreenSuccess } from './types'; @@ -183,9 +182,7 @@ const AuthModal = ({ initialScreen, onClose, mixpanelConfig, closeOnError }: Pro - - { content } - + { content } diff --git a/ui/snippets/footer/Footer.tsx b/ui/snippets/footer/Footer.tsx index c205352170..215063e68b 100644 --- a/ui/snippets/footer/Footer.tsx +++ b/ui/snippets/footer/Footer.tsx @@ -8,6 +8,7 @@ import type { CustomLinksGroup } from 'types/footerLinks'; import config from 'configs/app'; import type { ResourceError } from 'lib/api/resources'; import useApiQuery from 'lib/api/useApiQuery'; +import { RECAPTCHA_CONTAINER_ID } from 'lib/contexts/reCaptcha'; import useFetch from 'lib/hooks/useFetch'; import useIssueUrl from 'lib/hooks/useIssueUrl'; import { copy } from 'lib/html-entities'; @@ -170,6 +171,24 @@ const Footer = () => { m: '0 auto', }; + const renderRecaptcha = (gridArea?: GridProps['gridArea']) => { + if (!config.services.reCaptchaV3.siteKey) { + return ; + } + + return ( + + ); + }; + if (config.UI.footer.links) { return ( @@ -177,6 +196,7 @@ const Footer = () => {
{ renderNetworkInfo() } { renderProjectInfo() } + { renderRecaptcha() }
{ lg: ` "network links-top" "info links-bottom" + "recaptcha links-bottom" `, }} > { renderNetworkInfo({ lg: 'network' }) } { renderProjectInfo({ lg: 'info' }) } + { renderRecaptcha({ lg: 'recaptcha' }) } { { config.services.reCaptchaV3.siteKey ? ( <> - + <>
+ { /* TODO @tom2drum migrate to common form field component */ } -
+ { /* ONLY FOR TEST PURPOSES */ } Date: Tue, 3 Dec 2024 15:27:53 +0100 Subject: [PATCH 02/15] replace badge in footer with text --- theme/globals/recaptcha.ts | 2 +- ui/snippets/footer/Footer.tsx | 17 +++++++---------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/theme/globals/recaptcha.ts b/theme/globals/recaptcha.ts index 0db7a295a2..e21ca2bc76 100644 --- a/theme/globals/recaptcha.ts +++ b/theme/globals/recaptcha.ts @@ -1,7 +1,7 @@ const styles = () => { return { '.grecaptcha-badge': { - zIndex: 'toast', + visibility: 'hidden', }, }; }; diff --git a/ui/snippets/footer/Footer.tsx b/ui/snippets/footer/Footer.tsx index 215063e68b..6d087f7077 100644 --- a/ui/snippets/footer/Footer.tsx +++ b/ui/snippets/footer/Footer.tsx @@ -8,7 +8,6 @@ import type { CustomLinksGroup } from 'types/footerLinks'; import config from 'configs/app'; import type { ResourceError } from 'lib/api/resources'; import useApiQuery from 'lib/api/useApiQuery'; -import { RECAPTCHA_CONTAINER_ID } from 'lib/contexts/reCaptcha'; import useFetch from 'lib/hooks/useFetch'; import useIssueUrl from 'lib/hooks/useIssueUrl'; import { copy } from 'lib/html-entities'; @@ -177,15 +176,13 @@ const Footer = () => { } return ( - + + This site is protected by reCAPTCHA and the Google + Privacy Policy + and + Terms of Service + apply. + ); }; From 5e1b474427372973e67f5c860efe384cc5d009f1 Mon Sep 17 00:00:00 2001 From: tom Date: Tue, 3 Dec 2024 17:55:13 +0100 Subject: [PATCH 03/15] add invisible reCaptcha v2 --- package.json | 1 + ui/csvExport/CsvExportForm.tsx | 10 +++++--- .../fields/FormFieldReCaptchaInvisible.tsx | 24 +++++++++++++++++++ .../auth/screens/AuthModalScreenEmail.tsx | 13 ++++++---- yarn.lock | 18 +++++++++++++- 5 files changed, 57 insertions(+), 9 deletions(-) create mode 100644 ui/shared/forms/fields/FormFieldReCaptchaInvisible.tsx diff --git a/package.json b/package.json index 125441b70d..f535e7a013 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "react": "18.3.1", "react-device-detect": "^2.2.3", "react-dom": "18.3.1", + "react-google-recaptcha": "3.1.0", "react-google-recaptcha-v3": "1.10.1", "react-hook-form": "7.52.1", "react-identicons": "^1.2.5", diff --git a/ui/csvExport/CsvExportForm.tsx b/ui/csvExport/CsvExportForm.tsx index 0bcfcac8bb..b9696ca907 100644 --- a/ui/csvExport/CsvExportForm.tsx +++ b/ui/csvExport/CsvExportForm.tsx @@ -1,5 +1,6 @@ import { Alert, Button, chakra, Flex } from '@chakra-ui/react'; import React from 'react'; +import type ReCAPTCHA from 'react-google-recaptcha'; import type { SubmitHandler } from 'react-hook-form'; import { useForm, FormProvider } from 'react-hook-form'; @@ -12,7 +13,7 @@ import type { ResourceName } from 'lib/api/resources'; import dayjs from 'lib/date/dayjs'; import downloadBlob from 'lib/downloadBlob'; import useToast from 'lib/hooks/useToast'; -import FormFieldReCaptcha from 'ui/shared/forms/fields/FormFieldReCaptcha'; +import FormFieldReCaptchaInvisible from 'ui/shared/forms/fields/FormFieldReCaptchaInvisible'; import CsvExportFormField from './CsvExportFormField'; @@ -35,16 +36,19 @@ const CsvExportForm = ({ hash, resource, filterType, filterValue, fileNameTempla }); const { handleSubmit, formState } = formApi; const toast = useToast(); + const recaptchaRef = React.useRef(null); const onFormSubmit: SubmitHandler = React.useCallback(async(data) => { try { + const token = await recaptchaRef.current?.executeAsync(); + const url = buildUrl(resource, { hash } as never, { address_id: hash, from_period: exportType !== 'holders' ? data.from : null, to_period: exportType !== 'holders' ? data.to : null, filter_type: filterType, filter_value: filterValue, - recaptcha_v3_response: data.reCaptcha, + recaptcha_response: token, }); const response = await fetch(url, { @@ -95,7 +99,7 @@ const CsvExportForm = ({ hash, resource, filterType, filterValue, fileNameTempla { exportType !== 'holders' && } { exportType !== 'holders' && } - + + ); diff --git a/yarn.lock b/yarn.lock index af839888d1..ce4e77aecf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14925,7 +14925,7 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@^15.5.0, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -15167,6 +15167,14 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-async-script@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/react-async-script/-/react-async-script-1.2.0.tgz#ab9412a26f0b83f5e2e00de1d2befc9400834b21" + integrity sha512-bCpkbm9JiAuMGhkqoAiC0lLkb40DJ0HOEJIku+9JDjxX3Rcs+ztEOG13wbrOskt3n2DTrjshhaQ/iay+SnGg5Q== + dependencies: + hoist-non-react-statics "^3.3.0" + prop-types "^15.5.0" + react-clientside-effect@^1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.6.tgz#29f9b14e944a376b03fb650eed2a754dd128ea3a" @@ -15229,6 +15237,14 @@ react-google-recaptcha-v3@1.10.1: dependencies: hoist-non-react-statics "^3.3.2" +react-google-recaptcha@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/react-google-recaptcha/-/react-google-recaptcha-3.1.0.tgz#44aaab834495d922b9d93d7d7a7fb2326315b4ab" + integrity sha512-cYW2/DWas8nEKZGD7SCu9BSuVz8iOcOLHChHyi7upUuVhkpkhYG/6N3KDiTQ3XAiZ2UAZkfvYKMfAHOzBOcGEg== + dependencies: + prop-types "^15.5.0" + react-async-script "^1.2.0" + react-hook-form@7.52.1: version "7.52.1" resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.52.1.tgz#ec2c96437b977f8b89ae2d541a70736c66284852" From bcfc3550e708199e1dfce5770569ff7499086aaa Mon Sep 17 00:00:00 2001 From: tom Date: Tue, 3 Dec 2024 18:29:23 +0100 Subject: [PATCH 04/15] handle unsolved reCaptcha case --- theme/globals/recaptcha.ts | 13 +++++++ ui/csvExport/CsvExportForm.tsx | 14 ++++--- ui/shared/forms/fields/useReCaptcha.tsx | 39 +++++++++++++++++++ .../auth/screens/AuthModalScreenEmail.tsx | 10 ++--- 4 files changed, 66 insertions(+), 10 deletions(-) create mode 100644 ui/shared/forms/fields/useReCaptcha.tsx diff --git a/theme/globals/recaptcha.ts b/theme/globals/recaptcha.ts index e21ca2bc76..c551eb4ae2 100644 --- a/theme/globals/recaptcha.ts +++ b/theme/globals/recaptcha.ts @@ -3,6 +3,19 @@ const styles = () => { '.grecaptcha-badge': { visibility: 'hidden', }, + 'div:has(div):has(iframe[title="recaptcha challenge expires in two minutes"])': { + '&::after': { + content: `" "`, + display: 'block', + position: 'fixed', + top: 0, + left: 0, + width: '100vw', + height: '100vh', + zIndex: 100000, + bgColor: 'blackAlpha.300', + }, + }, }; }; diff --git a/ui/csvExport/CsvExportForm.tsx b/ui/csvExport/CsvExportForm.tsx index b9696ca907..82ae94fbf2 100644 --- a/ui/csvExport/CsvExportForm.tsx +++ b/ui/csvExport/CsvExportForm.tsx @@ -1,6 +1,5 @@ import { Alert, Button, chakra, Flex } from '@chakra-ui/react'; import React from 'react'; -import type ReCAPTCHA from 'react-google-recaptcha'; import type { SubmitHandler } from 'react-hook-form'; import { useForm, FormProvider } from 'react-hook-form'; @@ -14,6 +13,7 @@ import dayjs from 'lib/date/dayjs'; import downloadBlob from 'lib/downloadBlob'; import useToast from 'lib/hooks/useToast'; import FormFieldReCaptchaInvisible from 'ui/shared/forms/fields/FormFieldReCaptchaInvisible'; +import useReCaptcha from 'ui/shared/forms/fields/useReCaptcha'; import CsvExportFormField from './CsvExportFormField'; @@ -36,11 +36,15 @@ const CsvExportForm = ({ hash, resource, filterType, filterValue, fileNameTempla }); const { handleSubmit, formState } = formApi; const toast = useToast(); - const recaptchaRef = React.useRef(null); + const recaptcha = useReCaptcha(); const onFormSubmit: SubmitHandler = React.useCallback(async(data) => { try { - const token = await recaptchaRef.current?.executeAsync(); + const token = await recaptcha.executeAsync(); + + if (!token) { + throw new Error('ReCaptcha is not solved'); + } const url = buildUrl(resource, { hash } as never, { address_id: hash, @@ -79,7 +83,7 @@ const CsvExportForm = ({ hash, resource, filterType, filterValue, fileNameTempla }); } - }, [ resource, hash, exportType, filterType, filterValue, fileNameTemplate, toast ]); + }, [ recaptcha, resource, hash, exportType, filterType, filterValue, fileNameTemplate, toast ]); if (!config.services.reCaptchaV3.siteKey) { return ( @@ -99,7 +103,7 @@ const CsvExportForm = ({ hash, resource, filterType, filterValue, fileNameTempla { exportType !== 'holders' && } { exportType !== 'holders' && } - + - + ); From a3c16e988a474750a2a49d093a081863794f6b34 Mon Sep 17 00:00:00 2001 From: tom Date: Tue, 3 Dec 2024 18:53:47 +0100 Subject: [PATCH 05/15] migrate the rest of components to invisible reCaptcha v2 --- ui/myProfile/MyProfileEmail.tsx | 12 ++- ui/publicTags/submit/PublicTagsSubmitForm.tsx | 25 +++--- .../custom/AppErrorTooManyRequests.tsx | 20 ++--- .../auth/screens/AuthModalScreenOtpCode.tsx | 12 +-- .../TokenInstanceMetadataFetcher.tsx | 83 ++++++++++--------- 5 files changed, 80 insertions(+), 72 deletions(-) diff --git a/ui/myProfile/MyProfileEmail.tsx b/ui/myProfile/MyProfileEmail.tsx index fba8313e3a..0b446a85f8 100644 --- a/ui/myProfile/MyProfileEmail.tsx +++ b/ui/myProfile/MyProfileEmail.tsx @@ -13,8 +13,9 @@ import getErrorMessage from 'lib/errors/getErrorMessage'; import getErrorObjPayload from 'lib/errors/getErrorObjPayload'; import useToast from 'lib/hooks/useToast'; import * as mixpanel from 'lib/mixpanel'; -import FormFieldReCaptcha from 'ui/shared/forms/fields/FormFieldReCaptcha'; +import FormFieldReCaptchaInvisible from 'ui/shared/forms/fields/FormFieldReCaptchaInvisible'; import FormFieldText from 'ui/shared/forms/fields/FormFieldText'; +import useReCaptcha from 'ui/shared/forms/fields/useReCaptcha'; import AuthModal from 'ui/snippets/auth/AuthModal'; import MyProfileFieldsEmail from './fields/MyProfileFieldsEmail'; @@ -33,6 +34,7 @@ const MyProfileEmail = ({ profileQuery }: Props) => { const authModal = useDisclosure(); const apiFetch = useApiFetch(); const toast = useToast(); + const recaptcha = useReCaptcha(); const formApi = useForm({ mode: 'onBlur', @@ -44,12 +46,14 @@ const MyProfileEmail = ({ profileQuery }: Props) => { const onFormSubmit: SubmitHandler = React.useCallback(async(formData) => { try { + const token = await recaptcha.executeAsync(); + await apiFetch('auth_send_otp', { fetchParams: { method: 'POST', body: { email: formData.email, - recaptcha_v3_response: formData.reCaptcha, + recaptcha_response: token, }, }, }); @@ -67,7 +71,7 @@ const MyProfileEmail = ({ profileQuery }: Props) => { description: apiError?.message || getErrorMessage(error) || 'Something went wrong', }); } - }, [ apiFetch, authModal, toast ]); + }, [ apiFetch, authModal, toast, recaptcha ]); const hasDirtyFields = Object.keys(formApi.formState.dirtyFields).length > 0; @@ -84,7 +88,7 @@ const MyProfileEmail = ({ profileQuery }: Props) => { isReadOnly={ !config.services.reCaptchaV3.siteKey || Boolean(profileQuery.data?.email) } defaultValue={ profileQuery.data?.email || undefined } /> - { config.services.reCaptchaV3.siteKey && !profileQuery.data?.email && } + { config.services.reCaptchaV3.siteKey && !profileQuery.data?.email && } { config.services.reCaptchaV3.siteKey && !profileQuery.data?.email && ( ); diff --git a/ui/snippets/auth/screens/AuthModalScreenOtpCode.tsx b/ui/snippets/auth/screens/AuthModalScreenOtpCode.tsx index 73e7047c79..5f155440af 100644 --- a/ui/snippets/auth/screens/AuthModalScreenOtpCode.tsx +++ b/ui/snippets/auth/screens/AuthModalScreenOtpCode.tsx @@ -1,6 +1,5 @@ import { chakra, Box, Text, Button } from '@chakra-ui/react'; import React from 'react'; -import { useGoogleReCaptcha } from 'react-google-recaptcha-v3'; import type { SubmitHandler } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form'; @@ -11,6 +10,8 @@ import useApiFetch from 'lib/api/useApiFetch'; import getErrorMessage from 'lib/errors/getErrorMessage'; import getErrorObjPayload from 'lib/errors/getErrorObjPayload'; import useToast from 'lib/hooks/useToast'; +import FormFieldReCaptchaInvisible from 'ui/shared/forms/fields/FormFieldReCaptchaInvisible'; +import useReCaptcha from 'ui/shared/forms/fields/useReCaptcha'; import IconSvg from 'ui/shared/IconSvg'; import AuthModalFieldOtpCode from '../fields/AuthModalFieldOtpCode'; @@ -25,7 +26,7 @@ const AuthModalScreenOtpCode = ({ email, onSuccess, isAuth }: Props) => { const apiFetch = useApiFetch(); const toast = useToast(); - const { executeRecaptcha } = useGoogleReCaptcha(); + const recaptcha = useReCaptcha(); const [ isCodeSending, setIsCodeSending ] = React.useState(false); const formApi = useForm({ @@ -72,11 +73,11 @@ const AuthModalScreenOtpCode = ({ email, onSuccess, isAuth }: Props) => { try { formApi.clearErrors('code'); setIsCodeSending(true); - const token = await executeRecaptcha?.(); + const token = await recaptcha.executeAsync(); await apiFetch('auth_send_otp', { fetchParams: { method: 'POST', - body: { email, recaptcha_v3_response: token }, + body: { email, recaptcha_response: token }, }, }); @@ -96,7 +97,7 @@ const AuthModalScreenOtpCode = ({ email, onSuccess, isAuth }: Props) => { } finally { setIsCodeSending(false); } - }, [ apiFetch, email, executeRecaptcha, formApi, toast ]); + }, [ apiFetch, email, formApi, toast, recaptcha ]); return ( @@ -110,6 +111,7 @@ const AuthModalScreenOtpCode = ({ email, onSuccess, isAuth }: Props) => { and enter your code below. + ); diff --git a/ui/shared/forms/fields/FormFieldReCaptchaInvisible.tsx b/ui/shared/reCaptcha/ReCaptcha.tsx similarity index 71% rename from ui/shared/forms/fields/FormFieldReCaptchaInvisible.tsx rename to ui/shared/reCaptcha/ReCaptcha.tsx index 016b744b1a..83152d7266 100644 --- a/ui/shared/forms/fields/FormFieldReCaptchaInvisible.tsx +++ b/ui/shared/reCaptcha/ReCaptcha.tsx @@ -7,7 +7,7 @@ interface Props { disabledFeatureMessage?: React.ReactNode; } -const FormFieldReCaptchaInvisible = ({ disabledFeatureMessage }: Props, ref: React.Ref) => { +const ReCaptchaInvisible = ({ disabledFeatureMessage }: Props, ref: React.Ref) => { if (!config.services.reCaptchaV2.siteKey) { return disabledFeatureMessage ?? null; } @@ -21,4 +21,4 @@ const FormFieldReCaptchaInvisible = ({ disabledFeatureMessage }: Props, ref: Rea ); }; -export default React.forwardRef(FormFieldReCaptchaInvisible); +export default React.forwardRef(ReCaptchaInvisible); diff --git a/ui/shared/forms/fields/useReCaptcha.tsx b/ui/shared/reCaptcha/useReCaptcha.tsx similarity index 100% rename from ui/shared/forms/fields/useReCaptcha.tsx rename to ui/shared/reCaptcha/useReCaptcha.tsx diff --git a/ui/snippets/auth/screens/AuthModalScreenEmail.tsx b/ui/snippets/auth/screens/AuthModalScreenEmail.tsx index 6845fb6d89..51c959e289 100644 --- a/ui/snippets/auth/screens/AuthModalScreenEmail.tsx +++ b/ui/snippets/auth/screens/AuthModalScreenEmail.tsx @@ -11,8 +11,8 @@ import getErrorObjPayload from 'lib/errors/getErrorObjPayload'; import useToast from 'lib/hooks/useToast'; import * as mixpanel from 'lib/mixpanel'; import FormFieldEmail from 'ui/shared/forms/fields/FormFieldEmail'; -import FormFieldReCaptchaInvisible from 'ui/shared/forms/fields/FormFieldReCaptchaInvisible'; -import useReCaptcha from 'ui/shared/forms/fields/useReCaptcha'; +import ReCaptcha from 'ui/shared/reCaptcha/ReCaptcha'; +import useReCaptcha from 'ui/shared/reCaptcha/useReCaptcha'; interface Props { onSubmit: (screen: Screen) => void; @@ -95,7 +95,7 @@ const AuthModalScreenEmail = ({ onSubmit, isAuth, mixpanelConfig }: Props) => { > Send a code - + ); diff --git a/ui/snippets/auth/screens/AuthModalScreenOtpCode.tsx b/ui/snippets/auth/screens/AuthModalScreenOtpCode.tsx index 5f155440af..87cd2ec9fd 100644 --- a/ui/snippets/auth/screens/AuthModalScreenOtpCode.tsx +++ b/ui/snippets/auth/screens/AuthModalScreenOtpCode.tsx @@ -10,9 +10,9 @@ import useApiFetch from 'lib/api/useApiFetch'; import getErrorMessage from 'lib/errors/getErrorMessage'; import getErrorObjPayload from 'lib/errors/getErrorObjPayload'; import useToast from 'lib/hooks/useToast'; -import FormFieldReCaptchaInvisible from 'ui/shared/forms/fields/FormFieldReCaptchaInvisible'; -import useReCaptcha from 'ui/shared/forms/fields/useReCaptcha'; import IconSvg from 'ui/shared/IconSvg'; +import ReCaptcha from 'ui/shared/reCaptcha/ReCaptcha'; +import useReCaptcha from 'ui/shared/reCaptcha/useReCaptcha'; import AuthModalFieldOtpCode from '../fields/AuthModalFieldOtpCode'; @@ -111,7 +111,7 @@ const AuthModalScreenOtpCode = ({ email, onSuccess, isAuth }: Props) => { and enter your code below. - +