Web Devlopment/Next-Blog Project
#7. Login
키모형
2025. 4. 5. 13:43
반응형
회원가입 절차와 거의 동일함.
1. Navigation 컴포넌트에 로그인 링크 추가
/src/components/Navigation.jsx
"use client"
import NavLink from "./NavLink"
export default function Navigation() {
return (
<nav>
<NavLink label="Home" href="/" />
<div>
<NavLink label="Register" href="/register" />
<NavLink label="Login" href="/login" />
<NavLink label="Dashboard" href="/dashboard" />
</div>
</nav>
)
}
2. login 페이지 추가
/src/app/(auth)/login/page.jsx
"use client"
import { useActionState } from "react";
import { login } from "@/actions/auth"; // 해당 action페이지에 login 처리 부분 추가예정
import Link from "next/link";
export default function Login() {
// useActionState는 서버 액션을 클라이언트에서 사용할 수 있도록 해주는 훅입니다.
// 우선 action을 빈함수로 막아 놓고 /actions/auth.jsx 수정함
//const [state, action, isPending] = useActionState(()=>{}, undefined);
const [state, action, isPending] = useActionState(login, undefined);
return (
<div className="container w-1/2">
<h1 className="title">Login</h1>
<form action={action} className="space-y-4">
<div>
<label htmlFor="email">Email</label>
<input type="text" name="email" defaultValue={state?.email} required />
{state?.errors?.email && (
<p className="error">{state.errors.email}</p>
)}
</div>
<div>
<label htmlFor="password">Password</label>
<input type="password" name="password" required />
{state?.errors?.password && (
<p className="error">{state.errors.password}</p>
)}
</div>
<div className="flex items-end gap-4">
<button disabled={isPending} className="btn-primary w-full">
{isPending ? "Loading..." : "Login"}
</button>
</div>
<div className="flex items-center justify-center gap-4">
<Link href="/register" className="text-link">
or register here
</Link>
</div>
</form>
</div>
);
}
3. Action에 login 처리 함수 추가
/src/actions/auth.jsx 에 함수 추가
// 기존내용아래에 login 액션 함수 추가
// 로그인 요청을 처리하는 서버 액션입니다.
export async function login(state, formData) {
// 유효성 검사 규칙은 src/lib/rules.js에 정의되어 있습니다.
const validatedFields = LoginFormSchema.safeParse({
email: formData.get("email"),
password: formData.get("password"),
});
// 유효성 검사에 실패한 경우, 에러 메시지를 반환합니다.
// 에러 메시지는 src/lib/rules.js에 정의된 규칙에 따라 다르게 표시됩니다.
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
email: formData.get("email"),
};
}
// 유효성 검사 통과 후 DB에 회원가입 정보를 저장합니다.
const {email, password} = validatedFields.data;
// console.log(email, password);
// next_blog_db DB에서 users collection(테이블)을 가져옵니다.
// users collection이 존재하지 않는 경우, 에러 메시지를 반환합니다.
const userCollection = await getCollection("users");
if (!userCollection) {
return { errors: { email: "Server error!" } };
}
// user email이 존재하지 않는 경우, 에러 메시지를 반환합니다.
const exitingUser = await userCollection.findOne({ email });
if (!exitingUser) {
return { errors: { email: "User not found." } };
}
// 비밀번호를 비교하여 일치하지 않는 경우, 에러 메시지를 반환합니다.
const isPasswordValid = await bcrypt.compare(password, exitingUser.password);
if (!isPasswordValid) {
return {
errors: { password: "Invalid password." },
email: formData.get("email")
};
}
// Create Session
await createSession(exitingUser._id.toString());
// Rediect to login page after successful registration.
redirect("/dashboard");
}
위 액션에 필요한 로그인 폼데이터에 대한 Validate 함수 추가
/src/lib/rules.js
// 로그인시 이메일, 비밀번호를 검사합니다.
export const LoginFormSchema = z
.object({
email: z.string().email({ message: "Please enter a valid email." }).trim(),
password: z
.string()
.min(1, { message: "Not be empty" })
.trim()
});
반응형