Refactor auth methods, remove i18n, and update UI

This commit covers a variety of actions that includes the refactoring of the authentication components to accept paths and invite tokens as props instead of a singular callback prop, thereby improving the component's flexibility. This refactor process removes 'withI18n' calls as i18n functionalities are no longer used. The commit also contains several adjustments to the UI components, including the authorization layout, pricing table, and sign-up page. It also includes minor changes to error messages, specifically those related to password resetting. Lastly, several peer dependencies are removed in the 'package.json' files and changes made to the 'browser.client.ts' file providing a significant code cleanup.
This commit is contained in:
giancarlo
2024-03-27 01:19:20 +08:00
parent cabdd832df
commit 8a614bd6fc
29 changed files with 215 additions and 204 deletions

View File

@@ -9,7 +9,7 @@ export function AuthLayoutShell({
className={
'flex h-screen flex-col items-center justify-center space-y-4' +
' dark:lg:bg-background md:space-y-8 lg:space-y-12 lg:bg-gray-50' +
' animate-in fade-in slide-in-from-top-8 duration-1000'
' animate-in fade-in slide-in-from-top-8 zoom-in-95 duration-1000'
}
>
{Logo && <Logo />}

View File

@@ -1,98 +1,132 @@
'use client';
import type { FormEventHandler } from 'react';
import { useCallback } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { CheckIcon } from '@radix-ui/react-icons';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { toast } from 'sonner';
import { z } from 'zod';
import { useSignInWithOtp } from '@kit/supabase/hooks/use-sign-in-with-otp';
import { Alert, AlertDescription } from '@kit/ui/alert';
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 { Input } from '@kit/ui/input';
import { Label } from '@kit/ui/label';
import { Trans } from '@kit/ui/trans';
export function MagicLinkAuthContainer({
inviteCode,
inviteToken,
redirectUrl,
}: {
inviteCode?: string;
inviteToken?: string;
redirectUrl: string;
}) {
const { t } = useTranslation();
const signInWithOtpMutation = useSignInWithOtp();
const onSubmit: FormEventHandler<HTMLFormElement> = useCallback(
(event) => {
event.preventDefault();
const form = useForm({
resolver: zodResolver(
z.object({
email: z.string().email(),
}),
),
defaultValues: {
email: '',
},
});
const target = event.currentTarget;
const data = new FormData(target);
const email = data.get('email') as string;
const queryParams = inviteCode ? `?inviteCode=${inviteCode}` : '';
const onSubmit = ({ email }: { email: string }) => {
const queryParams = inviteToken ? `?invite_token=${inviteToken}` : '';
const emailRedirectTo = [redirectUrl, queryParams].join('');
const emailRedirectTo = [redirectUrl, queryParams].join('');
const promise = signInWithOtpMutation.mutateAsync({
const promise = () =>
signInWithOtpMutation.mutateAsync({
email,
options: {
emailRedirectTo,
},
});
toast.promise(promise, {
loading: t('auth:sendingEmailLink'),
success: t(`auth:sendLinkSuccessToast`),
error: t(`auth:errors.link`),
});
},
[inviteCode, redirectUrl, signInWithOtpMutation, t],
);
toast.promise(promise, {
loading: t('auth:sendingEmailLink'),
success: t(`auth:sendLinkSuccessToast`),
error: t(`auth:errors.link`),
});
};
if (signInWithOtpMutation.data) {
return (
<Alert variant={'success'}>
<AlertDescription>
<CheckIcon className={'h-4'} />
<AlertTitle>
<Trans i18nKey={'auth:sendLinkSuccess'} />
</AlertTitle>
<AlertDescription>
<Trans i18nKey={'auth:sendLinkSuccessDescription'} />
</AlertDescription>
</Alert>
);
}
return (
<form className={'w-full'} onSubmit={onSubmit}>
<If condition={signInWithOtpMutation.error}>
<Alert variant={'destructive'}>
<AlertDescription>
<Trans i18nKey={'auth:errors.link'} />
</AlertDescription>
</Alert>
</If>
<Form {...form}>
<form className={'w-full'} onSubmit={form.handleSubmit(onSubmit)}>
<If condition={signInWithOtpMutation.error}>
<Alert variant={'destructive'}>
<AlertTitle>
<Trans i18nKey={'auth:errors.generic'} />
</AlertTitle>
<div className={'flex flex-col space-y-4'}>
<Label>
<Trans i18nKey={'common:emailAddress'} />
<AlertDescription>
<Trans i18nKey={'auth:errors.link'} />
</AlertDescription>
</Alert>
</If>
<Input
data-test={'email-input'}
required
type="email"
placeholder={t('auth:emailPlaceholder')}
<div className={'flex flex-col space-y-4'}>
<FormField
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans i18nKey={'common:emailAddress'} />
</FormLabel>
<FormControl>
<Input
data-test={'email-input'}
required
type="email"
placeholder={t('auth:emailPlaceholder')}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
name={'email'}
/>
</Label>
<Button disabled={signInWithOtpMutation.isPending}>
<If
condition={signInWithOtpMutation.isPending}
fallback={<Trans i18nKey={'auth:sendEmailLink'} />}
>
<Trans i18nKey={'auth:sendingEmailLink'} />
</If>
</Button>
</div>
</form>
<Button disabled={signInWithOtpMutation.isPending}>
<If
condition={signInWithOtpMutation.isPending}
fallback={<Trans i18nKey={'auth:sendEmailLink'} />}
>
<Trans i18nKey={'auth:sendingEmailLink'} />
</If>
</Button>
</div>
</form>
</Form>
);
}

View File

@@ -13,10 +13,13 @@ import { AuthErrorAlert } from './auth-error-alert';
import { AuthProviderButton } from './auth-provider-button';
export const OauthProviders: React.FC<{
returnUrl?: string;
inviteCode?: string;
inviteToken?: string;
enabledProviders: Provider[];
redirectUrl: string;
paths: {
callback: string;
returnPath: string;
};
}> = (props) => {
const signInWithProviderMutation = useSignInWithProvider();
@@ -57,16 +60,16 @@ export const OauthProviders: React.FC<{
const origin = window.location.origin;
const queryParams = new URLSearchParams();
if (props.returnUrl) {
queryParams.set('next', props.returnUrl);
if (props.paths.returnPath) {
queryParams.set('next', props.paths.returnPath);
}
if (props.inviteCode) {
queryParams.set('inviteCode', props.inviteCode);
if (props.inviteToken) {
queryParams.set('invite_token', props.inviteToken);
}
const redirectPath = [
props.redirectUrl,
props.paths.callback,
queryParams.toString(),
].join('?');

View File

@@ -3,6 +3,11 @@
import Link from 'next/link';
import { zodResolver } from '@hookform/resolvers/zod';
import {
ArrowLeftIcon,
CheckIcon,
ExclamationTriangleIcon,
} from '@radix-ui/react-icons';
import { useForm } from 'react-hook-form';
import type { z } from 'zod';
@@ -112,7 +117,9 @@ export function PasswordResetForm(params: { redirectTo: string }) {
function SuccessState() {
return (
<div className={'flex flex-col space-y-4'}>
<Alert variant={'destructive'}>
<Alert variant={'success'}>
<CheckIcon className={'s-6'} />
<AlertTitle>
<Trans i18nKey={'account:updatePasswordSuccess'} />
</AlertTitle>
@@ -123,8 +130,12 @@ function SuccessState() {
</Alert>
<Link href={'/'}>
<Button variant={'outline'}>
<Trans i18nKey={'common:backToHomePage'} />
<Button variant={'outline'} className={'w-full'}>
<ArrowLeftIcon className={'mr-2 h-4'} />
<span>
<Trans i18nKey={'common:backToHomePage'} />
</span>
</Button>
</Link>
</div>
@@ -135,12 +146,14 @@ function ErrorState(props: { onRetry: () => void }) {
return (
<div className={'flex flex-col space-y-4'}>
<Alert variant={'destructive'}>
<ExclamationTriangleIcon className={'s-6'} />
<AlertTitle>
<Trans i18nKey={'auth:resetPasswordError'} />
<Trans i18nKey={'common:genericError'} />
</AlertTitle>
<AlertDescription>
<Trans i18nKey={'common:genericError'} />
<Trans i18nKey={'auth:resetPasswordError'} />
</AlertDescription>
</Alert>

View File

@@ -26,7 +26,9 @@ const PasswordResetSchema = z.object({
email: z.string().email(),
});
export function PasswordResetRequestContainer(params: { redirectTo: string }) {
export function PasswordResetRequestContainer(params: {
redirectPath: string;
}) {
const { t } = useTranslation('auth');
const resetPasswordMutation = useRequestResetPassword();
const error = resetPasswordMutation.error;
@@ -55,7 +57,8 @@ export function PasswordResetRequestContainer(params: { redirectTo: string }) {
onSubmit={form.handleSubmit(({ email }) => {
return resetPasswordMutation.mutateAsync({
email,
redirectTo: params.redirectTo,
redirectTo: new URL(params.redirectPath, window.location.origin)
.href,
});
})}
className={'w-full'}

View File

@@ -9,7 +9,6 @@ import { isBrowser } from '@supabase/ssr';
import { Divider } from '@kit/ui/divider';
import { If } from '@kit/ui/if';
import { EmailOtpContainer } from './email-otp-container';
import { MagicLinkAuthContainer } from './magic-link-auth-container';
import { OauthProviders } from './oauth-providers';
import { PasswordSignInContainer } from './password-sign-in-container';
@@ -23,7 +22,6 @@ export function SignInMethodsContainer(props: {
providers: {
password: boolean;
magicLink: boolean;
otp: boolean;
oAuth: Provider[];
};
}) {
@@ -48,20 +46,15 @@ export function SignInMethodsContainer(props: {
<MagicLinkAuthContainer redirectUrl={redirectUrl} />
</If>
<If condition={props.providers.otp}>
<EmailOtpContainer
onSignIn={onSignIn}
redirectUrl={redirectUrl}
shouldCreateUser={false}
/>
</If>
<If condition={props.providers.oAuth.length}>
<Divider />
<OauthProviders
enabledProviders={props.providers.oAuth}
redirectUrl={redirectUrl}
paths={{
callback: props.paths.callback,
returnPath: props.paths.home,
}}
/>
</If>
</>

View File

@@ -7,27 +7,27 @@ import { isBrowser } from '@supabase/ssr';
import { Divider } from '@kit/ui/divider';
import { If } from '@kit/ui/if';
import { EmailOtpContainer } from './email-otp-container';
import { MagicLinkAuthContainer } from './magic-link-auth-container';
import { OauthProviders } from './oauth-providers';
import { EmailPasswordSignUpContainer } from './password-sign-up-container';
export function SignUpMethodsContainer(props: {
callbackPath: string;
paths: {
callback: string;
appHome: string;
};
providers: {
password: boolean;
magicLink: boolean;
otp: boolean;
oAuth: Provider[];
};
inviteCode?: string;
inviteToken?: string;
}) {
const redirectUrl = new URL(
props.callbackPath,
isBrowser() ? window?.location.origin : '',
).toString();
const redirectUrl = isBrowser()
? new URL(props.paths.callback, window?.location.origin).toString()
: '';
return (
<>
@@ -37,26 +37,21 @@ export function SignUpMethodsContainer(props: {
<If condition={props.providers.magicLink}>
<MagicLinkAuthContainer
inviteCode={props.inviteCode}
inviteToken={props.inviteToken}
redirectUrl={redirectUrl}
/>
</If>
<If condition={props.providers.otp}>
<EmailOtpContainer
redirectUrl={redirectUrl}
shouldCreateUser={true}
inviteCode={props.inviteCode}
/>
</If>
<If condition={props.providers.oAuth.length}>
<Divider />
<OauthProviders
enabledProviders={props.providers.oAuth}
redirectUrl={redirectUrl}
inviteCode={props.inviteCode}
inviteToken={props.inviteToken}
paths={{
callback: props.paths.callback,
returnPath: props.paths.appHome,
}}
/>
</If>
</>