Skip to main content
Version: 2.x

🛠️ Building Authentication UI

The TanStack Query hooks generated in the previous chapter provide all the data-access building blocks for implementing the UI. Let's first use it to implement the authentication UI.

Signup Page

Create a src/app/signup/page.tsx file with the following content:

/src/app/signup/page.tsx
'use client';

import { signIn } from 'next-auth/react';
import Link from 'next/link';
import { useState, type FormEvent } from 'react';
import { useCreateUser } from '~/lib/hooks';

export default function Signup() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const { mutate: signup, error: signupError } = useCreateUser({
onSuccess: async () => {
// sign-up succeeded, sign in with the credentials
const signInResult = await signIn('credentials', {
redirect: false,
email,
password,
});
if (signInResult?.ok) {
window.location.href = '/';
} else {
console.error('Signin failed:', signInResult?.error);
}
},
});

const _err = signupError as { info?: { code?: string } };
const errMsg = _err
? _err.info?.code === 'P2002' // P2002 is the Prisma error code for unique constraint failure
? 'Email already exists'
: `Unexpected error occurred: ${JSON.stringify(_err)}`
: '';

function onSignup(e: FormEvent<HTMLFormElement>) {
e.preventDefault();
signup({ data: { email, password } });
}

return (
<div className="mx-auto flex h-screen flex-col items-center justify-center">
<div className="mb-10 flex items-center space-x-4">
<h1 className="text-4xl">Welcome to Todo</h1>
</div>
<div className="flex w-full max-w-screen-sm items-center justify-center rounded-lg">
<div className="w-full space-y-8 p-16">
<h2 className="text-3xl font-bold">Create a Free Account</h2>
<form className="mt-8 space-y-6" action="#" onSubmit={onSignup}>
<div>
<label htmlFor="email" className="label">
Your email
</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="input input-bordered w-full"
placeholder="Email address"
required
/>
</div>
<div>
<label htmlFor="password" className="label">
Your password
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
className="input input-bordered w-full"
required
/>
</div>

{errMsg && <p className="text-sm text-red-600">{errMsg}</p>}

<button className="btn btn-primary mt-4" type="submit">
Create account
</button>
<div>
Already have an account?{' '}
<Link href="/signin" className="text-primary">
Login here
</Link>
</div>
</form>
</div>
</div>
</div>
);
}

Next, replace the homepage src/app/page.tsx with the following content to show the current user:

/src/app/page.tsx
'use client';

import { useSession } from "next-auth/react";

export default function Home() {
const { data: session } = useSession();
return (
<div className="flex h-screen w-screen items-center justify-center">
{session?.user && <h1>Welcome {session?.user?.email}</h1>}
</div>
);
}

Our bare minimum signup page is ready now. Start the dev server and give it a try at http://localhost:3000/signup.

npm run dev

Sign-up UI

What's happening here?

We're using the generated useCreateUser mutation hook to create a new user. The hook calls into the CRUD API that we mounted to the "/api/model" endpoint in the previous chapter.

Sign-In Page

The sign-in part is purely made with NextAuth's API. Create a src/app/signin/page.tsx file with the following content:

/src/app/signin/page.tsx
'use client';

import { signIn } from 'next-auth/react';
import Link from 'next/link';
import { useState, type FormEvent } from 'react';

export default function Signup() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [errMsg, setErrMsg] = useState('');

async function onSignin(e: FormEvent<HTMLFormElement>) {
e.preventDefault();
const signInResult = await signIn('credentials', {
redirect: false,
email,
password,
});
if (signInResult?.ok) {
window.location.href = '/';
} else {
setErrMsg(`Signin failed. Please check your email and password.`);
}
}

return (
<div className="mx-auto flex h-screen flex-col items-center justify-center">
<div className="mb-10 flex items-center space-x-4">
<h1 className="text-4xl">Welcome to Todo</h1>
</div>
<div className="flex w-full max-w-screen-sm items-center justify-center rounded-lg">
<div className="w-full space-y-8 p-16">
<h2 className="text-3xl font-bold">Sign in to your account</h2>
<form className="mt-8 space-y-6" action="#" onSubmit={onSignin}>
<div>
<label htmlFor="email" className="label">
Your email
</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="input input-bordered w-full"
placeholder="Email address"
required
/>
</div>
<div>
<label htmlFor="password" className="label">
Your password
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
className="input input-bordered w-full"
required
/>
</div>

{errMsg && <p className="text-sm text-red-600">{errMsg}</p>}

<button className="btn btn-primary mt-4" type="submit">
Create account
</button>
<div>
Not registered?{' '}
<Link href="/signup" className="text-primary">
Create account
</Link>
</div>
</form>
</div>
</div>
</div>
);
}

By now, we've got a fully running signup and sign-in flow. You can try signin in with the account you just created at http://localhost:3000/signin.

Comments
Feel free to ask questions, give feedback, or report issues.

Don't Spam


You can edit/delete your comments by going directly to the discussion, clicking on the 'comments' link below