Refine next-auth 5.0 behavior
This commit is contained in:
parent
5acb257c83
commit
b12c4d3057
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -19,6 +19,7 @@
|
||||
"qaub",
|
||||
"QRSTUVWXYZ",
|
||||
"Reala",
|
||||
"Signin",
|
||||
"skippable",
|
||||
"sonner",
|
||||
"thephotoblog",
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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();
|
||||
};
|
||||
|
||||
@ -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: [
|
||||
|
||||
25
src/components/ErrorNote.tsx
Normal file
25
src/components/ErrorNote.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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}
|
||||
>
|
||||
|
||||
@ -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>}
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user