diff --git a/src/FormExtension/DonationForm/resources/js/TurnstileField.tsx b/src/FormExtension/DonationForm/resources/js/TurnstileField.tsx index eaff615..3ad8c98 100644 --- a/src/FormExtension/DonationForm/resources/js/TurnstileField.tsx +++ b/src/FormExtension/DonationForm/resources/js/TurnstileField.tsx @@ -6,6 +6,24 @@ import {GiveWP, TurnstileFieldSettings} from './types'; declare const window: GiveWP & TurnstileFieldSettings & Window; +/** + * Check if the error is a required field error. + * @since 1.0.0 + */ +const isRequiredError = (error: {message: string, type: string}) => error.message === 'This is a required field' || error.type === 'string.empty'; + +/** + * Error codes that should trigger a retry of the Turnstile challenge. + * @since 1.0.0 + */ +const errorCodeRetry = { + /** + * 11060*: Challenge timed out: The visitor took too long to solve the challenge and the challenge timed out. + * 11062*: Challenge timed out: This error is for visible mode only. The visitor took too long to solve the interactive challenge and the challenge became outdated. + */ + challengeTimeout: ['11060', '11062'] +}; + /** * @since 1.0.0 */ @@ -16,7 +34,8 @@ export default function TurnstileField({ }) { const ref = useRef(null); const { setValue, setError } = window.givewp.form.hooks.useFormContext(); - const { submitCount } = window.givewp.form.hooks.useFormState(); + const { submitCount, errors } = window.givewp.form.hooks.useFormState(); + const fieldName = inputProps.name; const setFormError = useCallback(() => setError('FORM_ERROR', { message: __('You must be a human.', 'give') @@ -24,7 +43,7 @@ export default function TurnstileField({ ); useEffect(() => { - if (fieldError && fieldError !== 'This is a required field') { + if (fieldError && errors && fieldName in errors && isRequiredError(errors[fieldName])) { setFormError(); } }, [fieldError]); @@ -40,13 +59,18 @@ export default function TurnstileField({ { - setFormError(); - } - } + onError={(errorCode: string) => { + console.error({ turnstileError: errorCode }); + + if (errorCodeRetry.challengeTimeout.some(code => errorCode.startsWith(code))) { + ref.current?.reset(); + } else { + setFormError(); + } + }} onExpire={() => ref.current?.reset()} - onSuccess={value => { - setValue(inputProps.name, value); + onSuccess={(value: string) => { + setValue(fieldName, value); }} />