Refine next-auth 5.0 behavior

This commit is contained in:
Sam Becker 2023-10-30 22:20:54 -05:00
parent 5acb257c83
commit b12c4d3057
8 changed files with 109 additions and 51 deletions

View File

@ -19,6 +19,7 @@
"qaub",
"QRSTUVWXYZ",
"Reala",
"Signin",
"skippable",
"sonner",
"thephotoblog",

View File

@ -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<HTMLInputElement>(null);
useLayoutEffect(() => {
@ -17,30 +21,33 @@ export default function SignInForm() {
return (
<InfoBlock>
<form
className="space-y-8"
action={signInAction}
>
<div className="space-y-4">
<FieldSetWithStatus
id="email"
inputRef={emailRef}
label="Admin Email"
type="email"
value={email}
onChange={setEmail}
/>
<FieldSetWithStatus
id="password"
label="Admin Password"
type="password"
value={password}
onChange={setPassword}
/>
<form action={action}>
<div className="space-y-8">
{response === CREDENTIALS_SIGN_IN_ERROR &&
<ErrorNote>
Invalid email/password
</ErrorNote>}
<div className="space-y-4">
<FieldSetWithStatus
id="email"
inputRef={emailRef}
label="Admin Email"
type="email"
value={email}
onChange={setEmail}
/>
<FieldSetWithStatus
id="password"
label="Admin Password"
type="password"
value={password}
onChange={setPassword}
/>
</div>
<SubmitButtonWithStatus>
Sign in
</SubmitButtonWithStatus>
</div>
<SubmitButtonWithStatus>
Sign in
</SubmitButtonWithStatus>
</form>
</InfoBlock>
);

View File

@ -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();
};

View File

@ -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: [

View File

@ -0,0 +1,25 @@
import { cc } from '@/utility/css';
import { BiErrorAlt } from 'react-icons/bi';
export default function ErrorNote({
children,
}: {
children: React.ReactNode
}) {
return (
<div className={cc(
'flex items-center gap-3',
'px-3 py-2 border',
'text-red-600 dark:text-red-500/90',
'bg-red-50/50 dark:bg-red-950/50',
'border-red-100 dark:border-red-950',
'rounded-md',
)}>
<BiErrorAlt
size={18}
className="text-red-600/80 dark:text-red-500/70"
/>
{children}
</div>
);
}

View File

@ -8,11 +8,13 @@ import { cc } from '@/utility/css';
interface Props extends HTMLProps<HTMLButtonElement> {
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}
>

View File

@ -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',
)}>
<div className="flex gap-x-4 gap-y-1 flex-wrap flex-grow">
{hasState
? <>
{session?.user === undefined &&
<>Loading ...</>}
{session?.user.email && <>
<div>{session.user.email}</div>
<div
onClick={() => signOut()}
<div className="flex gap-x-4 gap-y-1 flex-wrap items-center flex-grow">
{status === 'loading'
? <>Loading ...</>
: <>
{session?.user?.email && <div>
{session.user.email}
</div>}
{status === 'authenticated' &&
<form action={signOutAction}>
<SubmitButtonWithStatus
className={LINK_STYLE}
naked
>
Sign Out
</SubmitButtonWithStatus>
</form>}
{status === 'unauthenticated' &&
<Link
href="/sign-in"
className={LINK_STYLE}
>
Sign Out
</div>
</>}
</>
: <Link
href="/sign-in"
className={LINK_STYLE}
>
Sign In
</Link>}
Sign In
</Link>}
</>}
</div>
{!isPathSignIn(path) && <ThemeSwitcher />}
</div>}

View File

@ -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