From b12c4d3057e91b189ab4eb8da191a32901a01737 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Mon, 30 Oct 2023 22:20:54 -0500 Subject: [PATCH] Refine next-auth 5.0 behavior --- .vscode/settings.json | 1 + src/auth/SignInForm.tsx | 53 +++++++++++++---------- src/auth/action.ts | 19 +++++--- src/auth/index.ts | 4 ++ src/components/ErrorNote.tsx | 25 +++++++++++ src/components/SubmitButtonWithStatus.tsx | 3 ++ src/site/FooterAuth.tsx | 50 +++++++++++---------- src/site/globals.css | 5 +++ 8 files changed, 109 insertions(+), 51 deletions(-) create mode 100644 src/components/ErrorNote.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index 91af0e49..fc4ff045 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -19,6 +19,7 @@ "qaub", "QRSTUVWXYZ", "Reala", + "Signin", "skippable", "sonner", "thephotoblog", diff --git a/src/auth/SignInForm.tsx b/src/auth/SignInForm.tsx index 8aafd5fe..0cca6c9f 100644 --- a/src/auth/SignInForm.tsx +++ b/src/auth/SignInForm.tsx @@ -5,10 +5,14 @@ import InfoBlock from '@/components/InfoBlock'; import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus'; import { useLayoutEffect, useRef, useState } from 'react'; import { signInAction } from './action'; +import { useFormState } from 'react-dom'; +import ErrorNote from '@/components/ErrorNote'; +import { CREDENTIALS_SIGN_IN_ERROR } from '.'; export default function SignInForm() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); + const [response, action] = useFormState(signInAction, undefined); const emailRef = useRef(null); useLayoutEffect(() => { @@ -17,30 +21,33 @@ export default function SignInForm() { return ( -
-
- - + +
+ {response === CREDENTIALS_SIGN_IN_ERROR && + + Invalid email/password + } +
+ + +
+ + Sign in +
- - Sign in - ); diff --git a/src/auth/action.ts b/src/auth/action.ts index b0558125..734da6e9 100644 --- a/src/auth/action.ts +++ b/src/auth/action.ts @@ -1,12 +1,21 @@ 'use server'; -import { signIn } from '@/auth'; +import { CREDENTIALS_SIGN_IN_ERROR, signIn, signOut } from '@/auth'; -export const signInAction = async (formData: FormData) => { +export const signInAction = async ( + _prevState: string | undefined, + formData: FormData, +) => { try { - signIn('credentials', Object.fromEntries(formData)); + await signIn('credentials', Object.fromEntries(formData)); } catch (error) { - console.log('Cannot sign in user', error); - throw(error); + if ((error as Error).message.includes(CREDENTIALS_SIGN_IN_ERROR)) { + return CREDENTIALS_SIGN_IN_ERROR; + } + throw error; } }; + +export const signOutAction = async () => { + await signOut(); +}; diff --git a/src/auth/index.ts b/src/auth/index.ts index 44c685e8..db3cc98d 100644 --- a/src/auth/index.ts +++ b/src/auth/index.ts @@ -1,9 +1,13 @@ import { isPathProtected } from '@/site/paths'; import NextAuth, { User } from 'next-auth'; import Credentials from 'next-auth/providers/credentials'; + +export const CREDENTIALS_SIGN_IN_ERROR = 'CredentialsSignin'; + export const { handlers: { GET, POST }, signIn, + signOut, auth, } = NextAuth({ providers: [ diff --git a/src/components/ErrorNote.tsx b/src/components/ErrorNote.tsx new file mode 100644 index 00000000..aaca7190 --- /dev/null +++ b/src/components/ErrorNote.tsx @@ -0,0 +1,25 @@ +import { cc } from '@/utility/css'; +import { BiErrorAlt } from 'react-icons/bi'; + +export default function ErrorNote({ + children, +}: { + children: React.ReactNode +}) { + return ( +
+ + {children} +
+ ); +} diff --git a/src/components/SubmitButtonWithStatus.tsx b/src/components/SubmitButtonWithStatus.tsx index 5ec49458..52e767c5 100644 --- a/src/components/SubmitButtonWithStatus.tsx +++ b/src/components/SubmitButtonWithStatus.tsx @@ -8,11 +8,13 @@ import { cc } from '@/utility/css'; interface Props extends HTMLProps { icon?: JSX.Element + naked?: boolean } export default function SubmitButtonWithStatus(props: Props) { const { icon, + naked, children, disabled, className, @@ -29,6 +31,7 @@ export default function SubmitButtonWithStatus(props: Props) { className={cc( className, 'inline-flex items-center gap-2', + naked && 'naked', )} {...buttonProps} > diff --git a/src/site/FooterAuth.tsx b/src/site/FooterAuth.tsx index cab90f4e..3bdef328 100644 --- a/src/site/FooterAuth.tsx +++ b/src/site/FooterAuth.tsx @@ -2,22 +2,23 @@ import { cc } from '@/utility/css'; import Link from 'next/link'; -import { useSession, signOut } from 'next-auth/react'; +import { useSession } from 'next-auth/react'; import ThemeSwitcher from '@/site/ThemeSwitcher'; import SiteGrid from '../components/SiteGrid'; import { usePathname } from 'next/navigation'; import { isPathSignIn } from '@/site/paths'; +import { signOutAction } from '@/auth/action'; +import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus'; const LINK_STYLE = cc( 'cursor-pointer', - 'hover:text-gray-600', + 'hover:text-gray-300', + 'hover:dark:text-gray-600', ); export default function FooterAuth() { const { data: session, status } = useSession(); - const hasState = status !== 'loading'; - const path = usePathname(); return ( @@ -27,27 +28,30 @@ export default function FooterAuth() { 'my-8', 'text-dim', )}> -
- {hasState - ? <> - {session?.user === undefined && - <>Loading ...} - {session?.user.email && <> -
{session.user.email}
-
signOut()} +
+ {status === 'loading' + ? <>Loading ... + : <> + {session?.user?.email &&
+ {session.user.email} +
} + {status === 'authenticated' && +
+ + Sign Out + +
} + {status === 'unauthenticated' && + - Sign Out -
- } - - : - Sign In - } + Sign In + } + }
{!isPathSignIn(path) && }
} diff --git a/src/site/globals.css b/src/site/globals.css index 242dca91..9dc9a043 100644 --- a/src/site/globals.css +++ b/src/site/globals.css @@ -97,6 +97,11 @@ @apply text-medium } + button.naked { + @apply + p-0 min-h-0 + border-none active:bg-transparent shadow-none + } /* Toasts */ .toaster [data-sonner-toast] { @apply