Adicionando usuários Reais
Ok, até agora a gente estava “brincando” com o NextAuth. Fizemos um usuário falso e tudo mais… Mas e se a gente quiser um usuário real?
Um wrapper para o Prisma
É uma boa prática instanciarmos o PrismaClient apenas uma vez especialmente como estamos lidando com hot reloading no React.
Vamos criar então um arquivo lib/db.ts
.
import { PrismaClient } from '@prisma/client';
declare global { var prisma: PrismaClient | undefined;}
// Se `prisma` não existe em `globalThis` (ou seja, é undefined), crie um novo PrismaClient// Caso contrário, use o `prisma` existente em `globalThis`// Isso garante que apenas uma instância do PrismaClient seja criadaconst db = globalThis.prisma || new PrismaClient();
// Se não estivermos no ambiente de produção, atribua a instância do PrismaClient a `globalThis.prisma`// Isso é útil para desenvolvimento e testes, mas não é necessário em produçãoif (process.env.NODE_ENV !== 'production') { globalThis.prisma = db;}
// Exporte a instância do PrismaClient para uso em outros arquivosexport default db;
A rota - e uma Server Action - de cadastro
Talvez você não tenha percebido, mas já deixamos um formulário pronto de cadastro na rota /register
.
Por enquanto ele não faz absolutamente nada.
Vamos então criar uma action de cadastro.
Vamos utilizar uma lib chamada bcrypt-ts
. Instale ela primeiro.
'use server';
import db from '@/lib/db';import { hashSync } from 'bcrypt-ts';import { redirect } from 'next/navigation';
export default async function register(formData: FormData) { const entries = Array.from(formData.entries()); const { name, email, password } = Object.fromEntries(entries) as { name: string; email: string; password: string; };
// Verifique se algum campo está vazio if (!name || !email || !password) { throw new Error('Preencha todos os campos'); }
// Verifique se o usuário já existe const userExists = await db.user.findUnique({ where: { email }, });
if (userExists) { throw new Error('Usuário já existe'); }
await db.user.create({ data: { name, email, password: hashSync(password, 10), }, });
redirect('/');}
Agora precisamos fazer o link entre a nossa action e o form:
import register from '../_actions/register';
export default function RegisterForm() { return ( <> <Card className="mx-auto max-w-96"> <CardHeader> <CardTitle className="flex items-center justify-center gap-2"> <UserIcon className="w-6 h-6" /> Cadastre-se </CardTitle> <CardDescription>Crie uma conta gratuitamente</CardDescription> </CardHeader> <CardContent> <form action={register} className="text-left "> <div className="space-y-6">
Lidando com erros
Veja que estamos lançando todo e qualquer erro da nossa aplicação. Vamos usar a página error.tsx
do Next.js para mostrarmos o erro na tela.
Vamos fazer 2 componentes:
error.tsx
_components/error-card.tsx
onde vamos deixar o layout do card de erro.
Vamos criar a página de erro:
'use client';
import ErrorCard from './_components/error-card';
export default function RegisterError({ error, reset,}: { error: Error & { digest?: string }; reset: () => void;}) { return <ErrorCard errorMessage={error.message} reset={reset} />;}
E agora o componente para deixar o nosso erro agradável visualmente:
'use client';
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from '@/components/ui/card';import { Button, buttonVariants } from '@/components/ui/button';
import Link from 'next/link';import { cn } from '@/lib/utils';import { CircleX } from 'lucide-react';
export default function ErrorCard({ errorMessage, reset,}: { errorMessage: string; reset: () => void;}) { return ( <> <Card className="mx-auto max-w-96 border-red-700"> <CardHeader> <CardTitle className="flex items-center justify-center gap-2 text-red-300"> <CircleX /> Ops... </CardTitle> <CardDescription>Ocorreu um erro</CardDescription> </CardHeader> <CardContent className="underline">{errorMessage}</CardContent> <CardFooter className="flex justify-center"> <Button variant={'outline'} onClick={reset}> Tentar novamente </Button> </CardFooter> </Card> <Link className={cn(buttonVariants({ variant: 'link' }), 'mt-8')} href="/" > Voltar para Home </Link> </> );}
Agora, como último passo, precisamos proteger a rota de cadastro. Isso é, ninguém que está logado poderá acessá-la.
Protegendo a rota /register
de usuários logados
Agora a mágica do NextAuth começa a acontecer. Veja como é simples sabermos em qualquer rota se o usuário está logado ou não.
Vamos proteger a rota /register
para permitir apenas usuários deslogados:
import { auth } from '@/auth';import RegisterForm from './_components/register-form';import { redirect } from 'next/navigation';
export default async function LoginPage() { const session = await auth(); if (session) redirect('/');
return <RegisterForm />;}
Duas linhas de código e… voilá!
Tente logar novamente (/api/auth/signin
-> clicando no botão) e depois tente acessar a rota de cadastro: você deverá ser redirecionado para a home!
Ok, mas ainda assim não temos um sistema de login “vida real”, porque basta clicar no botão para logarmos. Precisamos de um sistema que realmente faça a comparação de email e senha