chore: bump version to 2.21.6 in package.json and enhance captcha integration across auth components (#418)

- Updated application version from 2.21.4 to 2.21.6 in package.json.
- Added "verifyingCaptcha" message to auth.json for improved user feedback during captcha verification.
- Enhanced captcha handling in various authentication components, including MagicLinkAuthContainer, PasswordResetRequestContainer, and SignUpContainer, to reflect loading states and improve user experience.
This commit is contained in:
Giancarlo Buomprisco
2025-12-02 21:40:56 +08:00
committed by GitHub
parent a78da16790
commit f6c635aac3
10 changed files with 74 additions and 24 deletions

View File

@@ -10,6 +10,7 @@
"signOut": "Sign out", "signOut": "Sign out",
"signingIn": "Signing in...", "signingIn": "Signing in...",
"signingUp": "Signing up...", "signingUp": "Signing up...",
"verifyingCaptcha": "Verifying...",
"doNotHaveAccountYet": "Do not have an account yet?", "doNotHaveAccountYet": "Do not have an account yet?",
"alreadyHaveAnAccount": "Already have an account?", "alreadyHaveAnAccount": "Already have an account?",
"signUpToAcceptInvite": "Please sign in/up to accept the invite", "signUpToAcceptInvite": "Please sign in/up to accept the invite",

View File

@@ -1,6 +1,6 @@
{ {
"name": "next-supabase-saas-kit-turbo", "name": "next-supabase-saas-kit-turbo",
"version": "2.21.4", "version": "2.21.6",
"private": true, "private": true,
"sideEffects": false, "sideEffects": false,
"engines": { "engines": {

View File

@@ -67,15 +67,20 @@ export function useCaptcha(
[siteKey, nonce, handleTokenChange, handleInstanceChange], [siteKey, nonce, handleTokenChange, handleInstanceChange],
); );
// Ready when captcha is not configured (no siteKey) or token is available
const isReady = !siteKey || token !== '';
return useMemo( return useMemo(
() => ({ () => ({
/** The current captcha token */ /** The current captcha token */
token, token,
/** Whether the captcha is ready (not configured or token available) */
isReady,
/** Reset the captcha (clears token and resets widget) */ /** Reset the captcha (clears token and resets widget) */
reset, reset,
/** The captcha field component to render */ /** The captcha field component to render */
field, field,
}), }),
[token, reset, field], [token, isReady, reset, field],
); );
} }

View File

@@ -49,6 +49,8 @@ export function MagicLinkAuthContainer({
const appEvents = useAppEvents(); const appEvents = useAppEvents();
const { recordAuthMethod } = useLastAuthMethod(); const { recordAuthMethod } = useLastAuthMethod();
const captchaLoading = !captcha.isReady;
const form = useForm({ const form = useForm({
resolver: zodResolver( resolver: zodResolver(
z.object({ z.object({
@@ -131,13 +133,18 @@ export function MagicLinkAuthContainer({
<TermsAndConditionsFormField /> <TermsAndConditionsFormField />
</If> </If>
<Button disabled={signInWithOtpMutation.isPending}> <Button disabled={signInWithOtpMutation.isPending || captchaLoading}>
<If <If condition={captchaLoading}>
condition={signInWithOtpMutation.isPending} <Trans i18nKey={'auth:verifyingCaptcha'} />
fallback={<Trans i18nKey={'auth:sendEmailLink'} />} </If>
>
<If condition={signInWithOtpMutation.isPending && !captchaLoading}>
<Trans i18nKey={'auth:sendingEmailLink'} /> <Trans i18nKey={'auth:sendingEmailLink'} />
</If> </If>
<If condition={!signInWithOtpMutation.isPending && !captchaLoading}>
<Trans i18nKey={'auth:sendEmailLink'} />
</If>
</Button> </Button>
</div> </div>
</form> </form>

View File

@@ -34,6 +34,7 @@ export function PasswordResetRequestContainer(params: {
const { t } = useTranslation('auth'); const { t } = useTranslation('auth');
const resetPasswordMutation = useRequestResetPassword(); const resetPasswordMutation = useRequestResetPassword();
const captcha = useCaptcha({ siteKey: params.captchaSiteKey }); const captcha = useCaptcha({ siteKey: params.captchaSiteKey });
const captchaLoading = !captcha.isReady;
const error = resetPasswordMutation.error; const error = resetPasswordMutation.error;
const success = resetPasswordMutation.data; const success = resetPasswordMutation.data;
@@ -101,8 +102,16 @@ export function PasswordResetRequestContainer(params: {
)} )}
/> />
<Button disabled={resetPasswordMutation.isPending} type="submit"> <Button
<Trans i18nKey={'auth:passwordResetLabel'} /> disabled={resetPasswordMutation.isPending || captchaLoading}
type="submit"
>
<If
condition={captchaLoading}
fallback={<Trans i18nKey={'auth:passwordResetLabel'} />}
>
<Trans i18nKey={'auth:verifyingCaptcha'} />
</If>
</Button> </Button>
</div> </div>
{captcha.field} {captcha.field}

View File

@@ -24,6 +24,7 @@ export function PasswordSignInContainer({
const { recordAuthMethod } = useLastAuthMethod(); const { recordAuthMethod } = useLastAuthMethod();
const isLoading = signInMutation.isPending; const isLoading = signInMutation.isPending;
const isRedirecting = signInMutation.isSuccess; const isRedirecting = signInMutation.isSuccess;
const captchaLoading = !captcha.isReady;
const onSubmit = useCallback( const onSubmit = useCallback(
async (credentials: z.infer<typeof PasswordSignInSchema>) => { async (credentials: z.infer<typeof PasswordSignInSchema>) => {
@@ -59,6 +60,7 @@ export function PasswordSignInContainer({
onSubmit={onSubmit} onSubmit={onSubmit}
loading={isLoading} loading={isLoading}
redirecting={isRedirecting} redirecting={isRedirecting}
captchaLoading={captchaLoading}
/> />
{captcha.field} {captcha.field}

View File

@@ -29,10 +29,12 @@ import { PasswordInput } from './password-input';
export function PasswordSignInForm({ export function PasswordSignInForm({
onSubmit, onSubmit,
captchaLoading = false,
loading = false, loading = false,
redirecting = false, redirecting = false,
}: { }: {
onSubmit: (params: z.infer<typeof PasswordSignInSchema>) => unknown; onSubmit: (params: z.infer<typeof PasswordSignInSchema>) => unknown;
captchaLoading: boolean;
loading: boolean; loading: boolean;
redirecting: boolean; redirecting: boolean;
}) { }) {
@@ -126,6 +128,12 @@ export function PasswordSignInForm({
</span> </span>
</If> </If>
<If condition={captchaLoading}>
<span className={'animate-in fade-in slide-in-from-bottom-24'}>
<Trans i18nKey={'auth:verifyingCaptcha'} />
</span>
</If>
<If condition={!redirecting && !loading}> <If condition={!redirecting && !loading}>
<span className={'animate-out fade-out flex items-center'}> <span className={'animate-out fade-out flex items-center'}>
<Trans i18nKey={'auth:signInWithEmail'} /> <Trans i18nKey={'auth:signInWithEmail'} />

View File

@@ -29,6 +29,7 @@ export function EmailPasswordSignUpContainer({
captchaSiteKey, captchaSiteKey,
}: EmailPasswordSignUpContainerProps) { }: EmailPasswordSignUpContainerProps) {
const captcha = useCaptcha({ siteKey: captchaSiteKey }); const captcha = useCaptcha({ siteKey: captchaSiteKey });
const captchaLoading = !captcha.isReady;
const { const {
signUp: onSignupRequested, signUp: onSignupRequested,
@@ -57,6 +58,7 @@ export function EmailPasswordSignUpContainer({
loading={loading} loading={loading}
defaultValues={defaultValues} defaultValues={defaultValues}
displayTermsCheckbox={displayTermsCheckbox} displayTermsCheckbox={displayTermsCheckbox}
captchaLoading={captchaLoading}
/> />
{captcha.field} {captcha.field}

View File

@@ -33,7 +33,9 @@ interface PasswordSignUpFormProps {
password: string; password: string;
repeatPassword: string; repeatPassword: string;
}) => unknown; }) => unknown;
loading: boolean; loading: boolean;
captchaLoading: boolean;
} }
export function PasswordSignUpForm({ export function PasswordSignUpForm({
@@ -41,6 +43,7 @@ export function PasswordSignUpForm({
displayTermsCheckbox, displayTermsCheckbox,
onSubmit, onSubmit,
loading, loading,
captchaLoading,
}: PasswordSignUpFormProps) { }: PasswordSignUpFormProps) {
const form = useForm({ const form = useForm({
resolver: zodResolver(PasswordSignUpSchema), resolver: zodResolver(PasswordSignUpSchema),
@@ -116,11 +119,17 @@ export function PasswordSignUpForm({
data-test={'auth-submit-button'} data-test={'auth-submit-button'}
className={'w-full'} className={'w-full'}
type="submit" type="submit"
disabled={loading} disabled={loading || captchaLoading}
> >
<If <If condition={captchaLoading}>
condition={loading} <Trans i18nKey={'auth:verifyingCaptcha'} />
fallback={ </If>
<If condition={loading && !captchaLoading}>
<Trans i18nKey={'auth:signingUp'} />
</If>
<If condition={!loading && !captchaLoading}>
<> <>
<Trans i18nKey={'auth:signUpWithEmail'} /> <Trans i18nKey={'auth:signUpWithEmail'} />
@@ -130,9 +139,6 @@ export function PasswordSignUpForm({
} }
/> />
</> </>
}
>
<Trans i18nKey={'auth:signingUp'} />
</If> </If>
</Button> </Button>
</form> </form>

View File

@@ -15,6 +15,7 @@ import {
FormItem, FormItem,
FormMessage, FormMessage,
} from '@kit/ui/form'; } from '@kit/ui/form';
import { If } from '@kit/ui/if';
import { Trans } from '@kit/ui/trans'; import { Trans } from '@kit/ui/trans';
import { useCaptcha } from '../captcha/client'; import { useCaptcha } from '../captcha/client';
@@ -26,6 +27,7 @@ export function ResendAuthLinkForm(props: {
}) { }) {
const captcha = useCaptcha({ siteKey: props.captchaSiteKey }); const captcha = useCaptcha({ siteKey: props.captchaSiteKey });
const resendLink = useResendLink(captcha.token); const resendLink = useResendLink(captcha.token);
const captchaLoading = !captcha.isReady;
const form = useForm({ const form = useForm({
resolver: zodResolver(z.object({ email: z.string().email() })), resolver: zodResolver(z.object({ email: z.string().email() })),
@@ -83,7 +85,15 @@ export function ResendAuthLinkForm(props: {
}} }}
/> />
<Button disabled={resendLink.isPending}> <Button disabled={resendLink.isPending || captchaLoading}>
<If condition={captchaLoading}>
<Trans i18nKey={'auth:verifyingCaptcha'} />
</If>
<If condition={resendLink.isPending && !captchaLoading}>
<Trans i18nKey={'auth:resendingLink'} />
</If>
<Trans i18nKey={'auth:resendLink'} defaults={'Resend Link'} /> <Trans i18nKey={'auth:resendLink'} defaults={'Resend Link'} />
</Button> </Button>
</form> </form>