diff --git a/apps/landing/components/auth/Captcha.tsx b/apps/landing/components/auth/Captcha.tsx index 0b28fa97..0dc6c4a7 100644 --- a/apps/landing/components/auth/Captcha.tsx +++ b/apps/landing/components/auth/Captcha.tsx @@ -1,5 +1,6 @@ -import { Turnstile } from '@marsidev/react-turnstile'; -import { useId } from 'react'; +import { Maybe } from '@jetstream/types'; +import { Turnstile, TurnstileInstance } from '@marsidev/react-turnstile'; +import { forwardRef, useId, useImperativeHandle, useRef } from 'react'; import { ENVIRONMENT } from '../../utils/environment'; interface CaptchaProps { @@ -17,9 +18,13 @@ interface CaptchaProps { onFinished: () => void; } -export function Captcha({ action, formError, onLoad, onChange, onFinished }: CaptchaProps) { +// eslint-disable-next-line react/display-name +export const Captcha = forwardRef, CaptchaProps>(({ action, formError, onLoad, onChange, onFinished }, ref) => { + const turnstileRef = useRef(null); const id = useId(); + useImperativeHandle>(ref, () => turnstileRef.current, [turnstileRef]); + // Skip rendering the captcha if we're running in Playwright or if the key is not set // In real environments the server will still validate and prevent access if there isn't a valid token if (!ENVIRONMENT.CAPTCHA_KEY || (window as any)?.playwright) { @@ -31,6 +36,7 @@ export function Captcha({ action, formError, onLoad, onChange, onFinished }: Cap <> { @@ -52,4 +59,4 @@ export function Captcha({ action, formError, onLoad, onChange, onFinished }: Cap )} ); -} +}); diff --git a/apps/landing/components/auth/LoginOrSignUp.tsx b/apps/landing/components/auth/LoginOrSignUp.tsx index b4452e24..b5b62ab7 100644 --- a/apps/landing/components/auth/LoginOrSignUp.tsx +++ b/apps/landing/components/auth/LoginOrSignUp.tsx @@ -1,9 +1,10 @@ /* eslint-disable @next/next/no-img-element */ import { zodResolver } from '@hookform/resolvers/zod'; import { Providers } from '@jetstream/auth/types'; +import { TurnstileInstance } from '@marsidev/react-turnstile'; import Link from 'next/link'; import { useRouter } from 'next/router'; -import { Fragment, useState } from 'react'; +import { Fragment, useRef, useState } from 'react'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { AUTH_PATHS, ENVIRONMENT } from '../../utils/environment'; @@ -53,6 +54,7 @@ export function LoginOrSignUp({ action, providers, csrfToken }: LoginOrSignUpPro const router = useRouter(); const [showPasswordActive, setShowPasswordActive] = useState(false); const [finishedCaptcha, setFinishedCaptcha] = useState(false); + const captchaRef = useRef(null); const { register, @@ -100,6 +102,11 @@ export function LoginOrSignUp({ action, providers, csrfToken }: LoginOrSignUpPro if (!response.ok || error) { router.push(`${router.pathname}?${new URLSearchParams({ error: errorType || 'UNKNOWN_ERROR' })}`); + try { + captchaRef?.current?.reset(); + } catch (ex) { + console.error('Error resetting captcha', ex); + } return; } @@ -258,6 +265,7 @@ export function LoginOrSignUp({ action, providers, csrfToken }: LoginOrSignUpPro setValue('captchaToken', token)} diff --git a/apps/landing/components/auth/PasswordResetInit.tsx b/apps/landing/components/auth/PasswordResetInit.tsx index b158a4b5..1c9de435 100644 --- a/apps/landing/components/auth/PasswordResetInit.tsx +++ b/apps/landing/components/auth/PasswordResetInit.tsx @@ -1,8 +1,9 @@ /* eslint-disable @next/next/no-img-element */ import { zodResolver } from '@hookform/resolvers/zod'; +import { TurnstileInstance } from '@marsidev/react-turnstile'; import Link from 'next/link'; import { useRouter } from 'next/router'; -import { Fragment, useState } from 'react'; +import { Fragment, useRef, useState } from 'react'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { AUTH_PATHS } from '../../utils/environment'; @@ -29,6 +30,7 @@ export function PasswordResetInit({ csrfToken }: PasswordResetInitProps) { const [isSubmitted, setIsSubmitted] = useState(false); const [finishedCaptcha, setFinishedCaptcha] = useState(false); const [error, setError] = useState(); + const captchaRef = useRef(null); const { register, @@ -58,6 +60,11 @@ export function PasswordResetInit({ csrfToken }: PasswordResetInitProps) { }); if (!response.ok) { + try { + captchaRef?.current?.reset(); + } catch (ex) { + console.error('Error resetting captcha', ex); + } throw new Error('Unable to initialize the reset process'); } @@ -127,6 +134,7 @@ export function PasswordResetInit({ csrfToken }: PasswordResetInitProps) { /> setValue('captchaToken', token)}