Merge pull request #339 from G3root/feat-api-error

feat: add alert banner for errors in sigin page
This commit is contained in:
Lucas Smith
2023-09-05 15:36:55 +10:00
committed by GitHub
3 changed files with 49 additions and 9 deletions

View File

@ -1,5 +1,9 @@
'use client'; 'use client';
import { useEffect } from 'react';
import { useSearchParams } from 'next/navigation';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { Loader } from 'lucide-react'; import { Loader } from 'lucide-react';
import { signIn } from 'next-auth/react'; import { signIn } from 'next-auth/react';
@ -7,12 +11,20 @@ import { useForm } from 'react-hook-form';
import { FcGoogle } from 'react-icons/fc'; import { FcGoogle } from 'react-icons/fc';
import { z } from 'zod'; import { z } from 'zod';
import { ErrorCode, isErrorCode } from '@documenso/lib/next-auth/error-codes';
import { cn } from '@documenso/ui/lib/utils'; import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button'; import { Button } from '@documenso/ui/primitives/button';
import { Input } from '@documenso/ui/primitives/input'; import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label'; import { Label } from '@documenso/ui/primitives/label';
import { useToast } from '@documenso/ui/primitives/use-toast'; import { useToast } from '@documenso/ui/primitives/use-toast';
const ErrorMessages = {
[ErrorCode.CREDENTIALS_NOT_FOUND]: 'The email or password provided is incorrect',
[ErrorCode.INCORRECT_EMAIL_PASSWORD]: 'The email or password provided is incorrect',
[ErrorCode.USER_MISSING_PASSWORD]:
'This account appears to be using a social login method, please sign in using that method',
};
export const ZSignInFormSchema = z.object({ export const ZSignInFormSchema = z.object({
email: z.string().email().min(1), email: z.string().email().min(1),
password: z.string().min(6).max(72), password: z.string().min(6).max(72),
@ -26,6 +38,7 @@ export type SignInFormProps = {
export const SignInForm = ({ className }: SignInFormProps) => { export const SignInForm = ({ className }: SignInFormProps) => {
const { toast } = useToast(); const { toast } = useToast();
const searchParams = useSearchParams();
const { const {
register, register,
@ -39,6 +52,27 @@ export const SignInForm = ({ className }: SignInFormProps) => {
resolver: zodResolver(ZSignInFormSchema), resolver: zodResolver(ZSignInFormSchema),
}); });
const errorCode = searchParams?.get('error');
useEffect(() => {
let timeout: NodeJS.Timeout | null = null;
if (isErrorCode(errorCode)) {
timeout = setTimeout(() => {
toast({
variant: 'destructive',
description: ErrorMessages[errorCode] ?? 'An unknown error occurred',
});
}, 0);
}
return () => {
if (timeout) {
clearTimeout(timeout);
}
};
}, [errorCode, toast]);
const onFormSubmit = async ({ email, password }: TSignInFormSchema) => { const onFormSubmit = async ({ email, password }: TSignInFormSchema) => {
try { try {
await signIn('credentials', { await signIn('credentials', {

View File

@ -7,7 +7,7 @@ import GoogleProvider, { GoogleProfile } from 'next-auth/providers/google';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import { getUserByEmail } from '../server-only/user/get-user-by-email'; import { getUserByEmail } from '../server-only/user/get-user-by-email';
import { ErrorCodes } from './error-codes'; import { ErrorCode } from './error-codes';
export const NEXT_AUTH_OPTIONS: AuthOptions = { export const NEXT_AUTH_OPTIONS: AuthOptions = {
adapter: PrismaAdapter(prisma), adapter: PrismaAdapter(prisma),
@ -24,23 +24,23 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
}, },
authorize: async (credentials, _req) => { authorize: async (credentials, _req) => {
if (!credentials) { if (!credentials) {
throw new Error(ErrorCodes.CredentialsNotFound); throw new Error(ErrorCode.CREDENTIALS_NOT_FOUND);
} }
const { email, password } = credentials; const { email, password } = credentials;
const user = await getUserByEmail({ email }).catch(() => { const user = await getUserByEmail({ email }).catch(() => {
throw new Error(ErrorCodes.IncorrectEmailPassword); throw new Error(ErrorCode.INCORRECT_EMAIL_PASSWORD);
}); });
if (!user.password) { if (!user.password) {
throw new Error(ErrorCodes.UserMissingPassword); throw new Error(ErrorCode.USER_MISSING_PASSWORD);
} }
const isPasswordsSame = await compare(password, user.password); const isPasswordsSame = await compare(password, user.password);
if (!isPasswordsSame) { if (!isPasswordsSame) {
throw new Error(ErrorCodes.IncorrectEmailPassword); throw new Error(ErrorCode.INCORRECT_EMAIL_PASSWORD);
} }
return { return {

View File

@ -1,5 +1,11 @@
export const ErrorCodes = { export const isErrorCode = (code: unknown): code is ErrorCode => {
IncorrectEmailPassword: 'incorrect-email-password', return typeof code === 'string' && code in ErrorCode;
UserMissingPassword: 'missing-password', };
CredentialsNotFound: 'credentials-not-found',
export type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
export const ErrorCode = {
INCORRECT_EMAIL_PASSWORD: 'INCORRECT_EMAIL_PASSWORD',
USER_MISSING_PASSWORD: 'USER_MISSING_PASSWORD',
CREDENTIALS_NOT_FOUND: 'CREDENTIALS_NOT_FOUND',
} as const; } as const;