Preciso de ajuda em um projeto
Aqui está um rascunho de post para o Tabnews, reunindo todo o contexto que trouxemos na conversa. Fique à vontade para ajustar o texto, formatar os trechos de código e links conforme o padrão do site.
🆘 Ajuda para implementar página de login em Next.js + App Router + Zod
Olá, pessoal!
Estou com dificuldades para implementar a página de login em um projeto privado que utiliza Next.js (App Router), Zod para validação e NextAuth/JWT para autenticação. Não posso disponibilizar o repositório (é privado), mas posso compartilhar trechos de código, logs de erro e marcar reuniões para mostrar a estrutura do projeto. Se alguém puder ajudar, agradeço desde já!
🛠️ Stack e Ferramentas
- Next.js 13+ com App Router (
src/app/…
) - TypeScript
- Zod para schemas e validação (loginSchema)
- Prisma + PostgreSQL (schema e client no backend)
- next-auth / JWT customizado para autenticação
- Mantine (UI) + @mantine/notifications
- OAuthLogin (login social)
📄 Código da página de login (frontend)
"use client";
import { useState } from "react";
import { useForm } from "@mantine/form";
import { useRouter } from "next/navigation";
import {
Card, TextInput, PasswordInput,
Button, Text, Anchor, Stack, Group
} from "@mantine/core";
import { notifications } from "@mantine/notifications";
import { IconMail, IconLock } from "@tabler/icons-react";
import { OAuthLogin } from "@/components/auth/OAuthLogin";
interface LoginForm { email: string; password: string; }
export default function LoginPage() {
const [loading, setLoading] = useState(false);
const router = useRouter();
const form = useForm<LoginForm>({
initialValues: { email: "", password: "" },
validate: {
email: (v) => (/^\S+@\S+\.\S+$/.test(v) ? null : "Email inválido"),
password: (v) => (v.length < 6 ? "Mínimo 6 caracteres" : null),
},
});
const handleSubmit = async (values: LoginForm) => {
setLoading(true);
try {
const res = await fetch("/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(values),
});
if (!res.ok) throw new Error(`Status ${res.status}`);
const result = await res.json();
if (result.success) {
notifications.show({ title: "Sucesso", message: "Login realizado", color: "green" });
router.push("/dashboard");
} else {
notifications.show({ title: "Erro", message: result.error || "Credenciais inválidas", color: "red" });
}
} catch (err) {
notifications.show({ title: "Erro", message: "Ocorreu um erro ao fazer login", color: "red" });
} finally {
setLoading(false);
}
};
return (
<Card shadow="sm" padding="lg" radius="md" withBorder>
<form onSubmit={form.onSubmit(handleSubmit)}>
<Stack>
<TextInput required label="Email" placeholder="[email protected]"
leftSection={<IconMail size={16}/>} {...form.getInputProps("email")} />
<PasswordInput required label="Senha" placeholder="Sua senha"
leftSection={<IconLock size={16}/>} {...form.getInputProps("password")} />
<Button type="submit" loading={loading} fullWidth>Entrar</Button>
<Group position="apart">
<Anchor component="button" size="sm">Esqueceu a senha?</Anchor>
<Anchor component="button" size="sm">Criar conta</Anchor>
</Group>
<Text color="dimmed" size="sm" align="center" mt="xs">Ou continue com</Text>
<OAuthLogin />
</Stack>
</form>
</Card>
);
}
🔄 Rota API /api/auth/login
(frontend)
import { NextResponse } from "next/server";
import { loginUser } from "@/../backend/src/api/auth/authService";
export async function POST(request: Request) {
try {
const data = await request.json().catch(() => null);
if (!data?.email || !data?.password) {
return NextResponse.json({ success: false, error: "Requisição inválida" }, { status: 400 });
}
const { email, password } = data;
const result = await loginUser(email, password);
if (!result.success) {
return NextResponse.json({ success: false, error: result.error }, { status: 401 });
}
const resp = NextResponse.json({ success: true });
resp.cookies.set("authorization", result.data!.token!, {
httpOnly: true, secure: process.env.NODE_ENV === "production",
maxAge: 3600, sameSite: "lax", path: "/",
});
return resp;
} catch (error) {
console.error("API Error:", error);
return NextResponse.json({ success: false, error: "Internal Server Error" }, { status: 500 });
}
}
🏗️ Backend Auth Service (Next.js Server Actions)
"use server";
import { prisma } from "../../lib/prisma";
import { compare, hash } from "bcryptjs";
import { sign, verify } from "jsonwebtoken";
import { ActionResult } from "@/core/types/action-result";
import { createUser, uploadAvatar } from "./authHelpers";
const loginSchema = z.object({ email: z.string().email(), password: z.string().min(6) });
export async function loginUser(email: string, password: string)
: Promise<ActionResult<{ success: boolean; token?: string }>> {
console.log("Verificando credenciais para:", email);
const user = await prisma.user.findUnique({ where: { email }, select: { id: true, password: true } });
if (!user) return { success: false, error: "Credenciais inválidas" };
const match = await compare(password, user.password!);
if (!match) return { success: false, error: "Credenciais inválidas" };
const token = sign({ id: user.id }, process.env.JWT_SECRET!, { expiresIn: "1h" });
console.log("Token gerado:", token);
return { success: true, data: { success: true, token } };
}
🔎 Logs de Erro Principais
-
401 Unauthorized via curl:
HTTP/1.1 401 Unauthorized {"success":false,"error":"Credenciais inválidas"}
-
Webpack Warning de casing em
@mantine/notifications
:multiple modules with names that only differ in casing…
-
SyntaxError no
route.ts
:Unexpected end of JSON input at request.json()
-
verifyCredentials sempre retorna
{ success: false }
mesmo com senha correta.
❓ O que já tentei
- Uniformizar o import de
notifications
para o mesmo casing em todos os arquivos - Validar
request.json()
comcatch()
e tratamento de corpo vazio - Padronizar variável de ambiente
JWT_SECRET
no backend - Confirmar hash de senha e schema Prisma (campo
password
non-nullable) - Limpar cache (
.next
,node_modules/.cache
) e reinstalar dependências - Ajustar regex de validação de email no frontend
📅 Próximos Passos
- Reunião remota via Zoom ou Google Meet para apresentar estrutura do projeto.
- Compartilhar telas (não o repositório) e depurar juntos.
- Pair programming para ajustar rota, validação e erros de import.
Se alguém já passou por algo semelhante, entendeu onde posso estar errando ou pode agendar uma call, por favor deixe um comentário abaixo ou me envie DM. Muito obrigado! 😊