Files
myeasycms-v2/packages/features/auth/src/components/magic-link-auth-container.tsx
Giancarlo Buomprisco 7ebff31475 Next.js Supabase V3 (#463)
Version 3 of the kit:
- Radix UI replaced with Base UI (using the Shadcn UI patterns)
- next-intl replaces react-i18next
- enhanceAction deprecated; usage moved to next-safe-action
- main layout now wrapped with [locale] path segment
- Teams only mode
- Layout updates
- Zod v4
- Next.js 16.2
- Typescript 6
- All other dependencies updated
- Removed deprecated Edge CSRF
- Dynamic Github Action runner
2026-03-24 13:40:38 +08:00

189 lines
4.6 KiB
TypeScript

'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import { Check, TriangleAlert } from 'lucide-react';
import { useTranslations } from 'next-intl';
import { useForm } from 'react-hook-form';
import * as z from 'zod';
import { useAppEvents } from '@kit/shared/events';
import { useSignInWithOtp } from '@kit/supabase/hooks/use-sign-in-with-otp';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
import { Button } from '@kit/ui/button';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@kit/ui/form';
import { If } from '@kit/ui/if';
import { toast } from '@kit/ui/sonner';
import { Trans } from '@kit/ui/trans';
import { useCaptcha } from '../captcha/client';
import { useLastAuthMethod } from '../hooks/use-last-auth-method';
import { EmailInput } from './email-input';
import { TermsAndConditionsFormField } from './terms-and-conditions-form-field';
export function MagicLinkAuthContainer({
redirectUrl,
shouldCreateUser,
defaultValues,
displayTermsCheckbox,
captchaSiteKey,
}: {
redirectUrl: string;
shouldCreateUser: boolean;
displayTermsCheckbox?: boolean;
captchaSiteKey?: string;
defaultValues?: {
email: string;
};
}) {
const captcha = useCaptcha({ siteKey: captchaSiteKey });
const t = useTranslations();
const signInWithOtpMutation = useSignInWithOtp();
const appEvents = useAppEvents();
const { recordAuthMethod } = useLastAuthMethod();
const captchaLoading = !captcha.isReady;
const form = useForm({
resolver: zodResolver(
z.object({
email: z.string().email(),
}),
),
defaultValues: {
email: defaultValues?.email ?? '',
},
});
const onSubmit = ({ email }: { email: string }) => {
const url = new URL(redirectUrl);
const emailRedirectTo = url.href;
const promise = async () => {
await signInWithOtpMutation.mutateAsync({
email,
options: {
emailRedirectTo,
captchaToken: captcha.token,
shouldCreateUser,
},
});
recordAuthMethod('magic_link', { email });
if (shouldCreateUser) {
appEvents.emit({
type: 'user.signedUp',
payload: {
method: 'magiclink',
},
});
}
};
toast.promise(promise, {
loading: t('auth.sendingEmailLink'),
success: t(`auth.sendLinkSuccessToast`),
error: t(`auth.errors.linkTitle`),
});
captcha.reset();
};
if (signInWithOtpMutation.data) {
return <SuccessAlert />;
}
return (
<Form {...form}>
<form className={'w-full'} onSubmit={form.handleSubmit(onSubmit)}>
<div className={'flex flex-col space-y-4'}>
<If condition={signInWithOtpMutation.error}>
<ErrorAlert />
</If>
{captcha.field}
<FormField
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans i18nKey={'common.emailAddress'} />
</FormLabel>
<FormControl>
<EmailInput data-test="email-input" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
name={'email'}
/>
<If condition={displayTermsCheckbox}>
<TermsAndConditionsFormField />
</If>
<Button
type="submit"
disabled={signInWithOtpMutation.isPending || captchaLoading}
>
<If condition={captchaLoading}>
<Trans i18nKey={'auth.verifyingCaptcha'} />
</If>
<If condition={signInWithOtpMutation.isPending && !captchaLoading}>
<Trans i18nKey={'auth.sendingEmailLink'} />
</If>
<If condition={!signInWithOtpMutation.isPending && !captchaLoading}>
<Trans i18nKey={'auth.sendEmailLink'} />
</If>
</Button>
</div>
</form>
</Form>
);
}
function SuccessAlert() {
return (
<Alert variant={'success'}>
<Check className={'h-4'} />
<AlertTitle>
<Trans i18nKey={'auth.sendLinkSuccess'} />
</AlertTitle>
<AlertDescription>
<Trans i18nKey={'auth.sendLinkSuccessDescription'} />
</AlertDescription>
</Alert>
);
}
function ErrorAlert() {
return (
<Alert variant={'destructive'}>
<TriangleAlert className={'h-4'} />
<AlertTitle>
<Trans i18nKey={'auth.errors.linkTitle'} />
</AlertTitle>
<AlertDescription>
<Trans i18nKey={'auth.errors.linkDescription'} />
</AlertDescription>
</Alert>
);
}