Commit c9894eb8 authored by tom goriunov's avatar tom goriunov Committed by GitHub

ReCaptcha: allow the user to display the widget if an error occurs. (#2669)

Fixes #2653
parent 857b77de
......@@ -8,6 +8,7 @@ import dayjs from 'lib/date/dayjs';
import downloadBlob from 'lib/downloadBlob';
import { Button } from 'toolkit/chakra/button';
import { toaster } from 'toolkit/chakra/toaster';
import { Tooltip } from 'toolkit/chakra/tooltip';
import ReCaptcha from 'ui/shared/reCaptcha/ReCaptcha';
import useReCaptcha from 'ui/shared/reCaptcha/useReCaptcha';
......@@ -63,16 +64,22 @@ const ExportCSV = ({ filters }: Props) => {
return (
<>
<Button
onClick={ handleExportCSV }
variant="outline"
loading={ isLoading }
size="sm"
mr={ 3 }
<Tooltip
content="This feature is not available due to a reCAPTCHA initialization error. Please contact the project team on Discord to report this issue."
disabled={ !recaptcha.isInitError }
>
Export to CSV
</Button>
<ReCaptcha ref={ recaptcha.ref }/>
<Button
onClick={ handleExportCSV }
variant="outline"
loading={ isLoading }
size="sm"
mr={ 3 }
disabled={ recaptcha.isInitError }
>
Export to CSV
</Button>
</Tooltip>
<ReCaptcha { ...recaptcha } hideWarning/>
</>
);
};
......
......@@ -101,14 +101,14 @@ const CsvExportForm = ({ hash, resource, filterType, filterValue, fileNameTempla
{ exportType !== 'holders' && <CsvExportFormField name="from" formApi={ formApi }/> }
{ exportType !== 'holders' && <CsvExportFormField name="to" formApi={ formApi }/> }
</Flex>
<ReCaptcha ref={ recaptcha.ref }/>
<ReCaptcha { ...recaptcha }/>
<Button
variant="solid"
type="submit"
mt={ 8 }
loading={ formState.isSubmitting }
loadingText="Download"
disabled={ Boolean(formState.errors.from || formState.errors.to) }
disabled={ Boolean(formState.errors.from || formState.errors.to || recaptcha.isInitError) }
>
Download
</Button>
......
......@@ -89,14 +89,14 @@ const MyProfileEmail = ({ profileQuery }: Props) => {
isReadOnly={ !config.services.reCaptchaV2.siteKey || Boolean(profileQuery.data?.email) }
defaultValue={ profileQuery.data?.email || undefined }
/>
{ config.services.reCaptchaV2.siteKey && !profileQuery.data?.email && <ReCaptcha ref={ recaptcha.ref }/> }
{ config.services.reCaptchaV2.siteKey && !profileQuery.data?.email && <ReCaptcha { ...recaptcha }/> }
{ config.services.reCaptchaV2.siteKey && !profileQuery.data?.email && (
<Button
mt={ 6 }
size="sm"
variant="outline"
type="submit"
disabled={ formApi.formState.isSubmitting || !hasDirtyFields }
disabled={ formApi.formState.isSubmitting || !hasDirtyFields || recaptcha.isInitError }
loading={ formApi.formState.isSubmitting }
loadingText="Save changes"
>
......
......@@ -129,9 +129,10 @@ const PublicTagsSubmitForm = ({ config, userInfo, onSubmitResult }: Props) => {
/>
</GridItem>
<GridItem colSpan={{ base: 1, lg: 3 }}>
<ReCaptcha ref={ recaptcha.ref }/>
<GridItem colSpan={{ base: 1, lg: 2 }}>
<ReCaptcha { ...recaptcha }/>
</GridItem>
{ !isMobile && <div/> }
<Button
variant="solid"
......@@ -140,6 +141,7 @@ const PublicTagsSubmitForm = ({ config, userInfo, onSubmitResult }: Props) => {
loading={ formApi.formState.isSubmitting }
loadingText="Send request"
w="min-content"
disabled={ recaptcha.isInitError }
>
Send request
</Button>
......
......@@ -51,8 +51,8 @@ const AppErrorTooManyRequests = () => {
<Text color="text.secondary" mt={ 3 }>
You have exceeded the request rate for a given time period. Please reduce the number of requests and try again soon.
</Text>
<ReCaptcha ref={ recaptcha.ref }/>
<Button onClick={ handleSubmit } mt={ 8 }>Try again</Button>
<ReCaptcha { ...recaptcha }/>
<Button onClick={ handleSubmit } disabled={ recaptcha.isInitError } mt={ 8 }>Try again</Button>
</>
);
};
......
......@@ -2,30 +2,61 @@ import React from 'react';
import ReCaptcha from 'react-google-recaptcha';
import config from 'configs/app';
import { Alert } from 'toolkit/chakra/alert';
import { Link } from 'toolkit/chakra/link';
interface Props {
disabledFeatureMessage?: React.ReactNode;
onInitError: () => void;
hideWarning?: boolean;
}
const ReCaptchaInvisible = ({ disabledFeatureMessage }: Props, ref: React.Ref<ReCaptcha>) => {
const ReCaptchaInvisible = ({ onInitError, hideWarning = false }: Props, ref: React.Ref<ReCaptcha>) => {
const [ attempt, setAttempt ] = React.useState(0);
const [ isError, setIsError ] = React.useState(false);
const [ , setIsVisible ] = React.useState(false);
const handleChange = React.useCallback(() => {
setAttempt(attempt + 1);
}, [ attempt ]);
const handleError = React.useCallback(() => {
setIsError(true);
onInitError();
}, [ onInitError ]);
const handleClick = React.useCallback(() => {
const badge = window.document.querySelector('.grecaptcha-badge');
if (badge) {
setIsVisible((prev) => {
const nextValue = !prev;
(badge as HTMLElement).style.visibility = nextValue ? 'visible' : 'hidden';
(badge as HTMLElement).style.right = nextValue ? '14px' : '-1000px';
return nextValue;
});
}
}, [ ]);
if (!config.services.reCaptchaV2.siteKey) {
return disabledFeatureMessage ?? null;
return null;
}
return (
<ReCaptcha
ref={ ref }
key={ attempt }
sitekey={ config.services.reCaptchaV2.siteKey }
size="invisible"
onChange={ handleChange }
/>
<>
<ReCaptcha
ref={ ref }
key={ attempt }
sitekey={ config.services.reCaptchaV2.siteKey }
size="invisible"
onChange={ handleChange }
onErrored={ handleError }
/>
{ isError && !hideWarning && (
<Alert status="warning" whiteSpace="pre-wrap" w="fit-content" mt={ 3 } descriptionProps={{ display: 'block' }}>
This feature is not available due to a reCAPTCHA initialization error. Please contact the project team on Discord to report this issue.
Click <Link onClick={ handleClick } display="inline">here</Link> to show/hide reCAPTCHA widget content.
</Alert>
) }
</>
);
};
......
......@@ -6,6 +6,7 @@ export default function useReCaptcha() {
const rejectCb = React.useRef<((error: Error) => void) | null>(null);
const [ isOpen, setIsOpen ] = React.useState(false);
const [ isInitError, setIsInitError ] = React.useState(false);
const executeAsync: () => Promise<string | null> = React.useCallback(async() => {
setIsOpen(true);
......@@ -22,6 +23,10 @@ export default function useReCaptcha() {
rejectCb.current?.(new Error('ReCaptcha is not solved'));
}, []);
const handleInitError = React.useCallback(() => {
setIsInitError(true);
}, []);
React.useEffect(() => {
if (!isOpen) {
return;
......@@ -35,5 +40,5 @@ export default function useReCaptcha() {
};
}, [ isOpen, handleContainerClick ]);
return React.useMemo(() => ({ ref, executeAsync }), [ ref, executeAsync ]);
return React.useMemo(() => ({ ref, executeAsync, isInitError, onInitError: handleInitError }), [ ref, executeAsync, isInitError, handleInitError ]);
}
......@@ -47,9 +47,9 @@ const AuthModalScreenConnectWallet = ({ onSuccess, onError, isAuth, source, logi
}, [ start ]);
return (
<Center h="100px">
<Spinner size="xl"/>
<ReCaptcha ref={ recaptcha.ref }/>
<Center minH="100px" flexDir="column">
{ !recaptcha.isInitError && <Spinner size="xl"/> }
<ReCaptcha { ...recaptcha }/>
</Center>
);
};
......
......@@ -85,16 +85,16 @@ const AuthModalScreenEmail = ({ onSubmit, isAuth, mixpanelConfig }: Props) => {
bgColor="dialog.bg"
mt={ 6 }
/>
<ReCaptcha { ...recaptcha }/>
<Button
mt={ 6 }
type="submit"
disabled={ formApi.formState.isSubmitting }
disabled={ formApi.formState.isSubmitting || recaptcha.isInitError }
loading={ formApi.formState.isSubmitting }
loadingText="Send a code"
>
Send a code
</Button>
<ReCaptcha ref={ recaptcha.ref }/>
</chakra.form>
</FormProvider>
);
......
......@@ -108,22 +108,22 @@ const AuthModalScreenOtpCode = ({ email, onSuccess, isAuth }: Props) => {
and enter your code below.
</Text>
<AuthModalFieldOtpCode isDisabled={ isCodeSending }/>
<ReCaptcha ref={ recaptcha.ref }/>
<Button
variant="link"
columnGap={ 2 }
mt={ 3 }
disabled={ isCodeSending }
disabled={ isCodeSending || recaptcha.isInitError }
onClick={ handleResendCodeClick }
>
<IconSvg name="repeat" boxSize={ 5 }/>
<Box fontSize="sm">Resend code</Box>
</Button>
<ReCaptcha { ...recaptcha }/>
<Button
mt={ 6 }
type="submit"
loading={ formApi.formState.isSubmitting }
disabled={ formApi.formState.isSubmitting || isCodeSending }
disabled={ formApi.formState.isSubmitting || isCodeSending || recaptcha.isInitError }
loadingText="Submit"
onClick={ formApi.handleSubmit(onFormSubmit) }
>
......
......@@ -170,7 +170,7 @@ const TokenInstanceMetadataFetcher = ({ hash, id }: Props) => {
<Center h="80px">
<Spinner size="lg"/>
</Center>
<ReCaptcha ref={ recaptcha.ref }/>
<ReCaptcha { ...recaptcha } hideWarning/>
</>
) : (
<Alert status="error">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment