2025. 1. 28. 19:43ㆍ프로젝트
우선 기본적으로 회원가입과 로그인 부분의 프론트 부분을 살펴 보겠습니다.
우선 회원가입 프론트 부분입니다.
import React, { useState } from "react";
import { useRecoilState } from "recoil";
import { useNavigate } from "react-router-dom";
import { formState } from "../../state/formState";
import SignupStep0 from "../../components/Login/SignupStep0";
import SignupStep1 from "../../components/Login/SignupStep1";
import SignupStep2 from "../../components/Login/SignupStep2";
import SignupStep3 from "../../components/Login/SignupStep3";
import { useResetRecoilState } from "recoil";
import { useEffect } from "react";
function Signup() {
const [step, setStep] = useState(0); // 현재 단계
const navigate = useNavigate();
const [formData, setFormData] = useRecoilState(formState);
const resetFormState = useResetRecoilState(formState);
useEffect(() => {
// 컴포넌트가 언마운트될 때 formState 초기화
return () => {
resetFormState();
};
}, [resetFormState]);
const nextStep = () => setStep(step + 1);
const prevStep = () => setStep(step - 1);
const handleSubmit = async () => {
if (!formData.emailVerified) {
alert("이메일 인증이 필요합니다.");
return;
}
if (!formData.member_type) {
alert("회원 유형을 선택해 주세요.");
return;
}
const endpoint =
formData.member_type === "user"
? "http://localhost:3000/register"
: "http://localhost:3000/register/expert";
const payload = {
...formData[formData.member_type], // 선택된 회원 유형의 데이터만 포함
email: formData.email,
password: formData.password,
role: formData.member_type, // 백엔드에서 role을 명확하게 전달하기 위해 추가
};
console.log("Payload being sent:", payload);
try {
const response = await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
const data = await response.json();
console.log("Response received:", data);
if (response.ok) {
alert(data.message || "회원가입 성공");
navigate("/");
} else {
alert(data.message || "회원가입 실패");
}
} catch (error) {
console.error("Error during signup:", error.message);
alert("회원가입 요청 중 오류가 발생했습니다.");
}
};
const renderStep = () => {
switch (step) {
case 0:
return <SignupStep0 nextStep={nextStep} />;
case 1:
return <SignupStep1 nextStep={nextStep} />;
case 2:
return <SignupStep2 nextStep={nextStep} prevStep={prevStep} />;
case 3:
return <SignupStep3 prevStep={prevStep} handleSubmit={handleSubmit} />;
default:
return null;
}
};
return (
<div className="min-h-screen flex flex-col justify-center items-center bg-gray-100">
{renderStep()}
</div>
);
}
export default Signup;
리코일 코드 ( formstate )
import { atom } from "recoil";
export const formState = atom({
key: "formState",
default: {
agreement: false, // 추가된 필드
member_type: "", // "user" 또는 "expert"
email: "", // 추가된 필드
password: "", // 추가된 필드
emailVerified: false, // 추가된 필드
user: {
institution_name: "", // 추가된 필드
institution_address: "", // 추가된 필드
representative_name: "", // 추가된 필드
phone_number: "", // ✅ 반드시 phone_number로 유지
},
expert: {
name: "", // 추가된 필드
institution_name: "", // 추가된 필드
ofcps: "", // 추가된 필드
phone_number: "", // 추가된 필드
major_carrea: "", // 추가된 필드
},
name: "",
min_subjects: "",
max_subjects: "",
purpose: "",
is_private: "포함", // 기본값 설정
is_unique: "미포함", // 기본값 설정
is_resident: "포함", // 기본값 설정
reason: "동의", // 기본값 설정
},
});
const [step, setStep] = useState(0); // 현재 단계
const navigate = useNavigate();
const [formData, setFormData] = useRecoilState(formState);
const resetFormState = useResetRecoilState(formState);
현재 회원가입 단계(0~3) 스탭으로 나눠서 회원가입을 구성하였습니다.
useState로 관리하며 setStep으로 단계 이동.
formData: Recoil의 formState를 사용해 입력 데이터를 관리합니다.
전역적으로 사용 가능하며, 다른 컴포넌트에서도 접근 및 수정 가능.
resetFormState: 회원가입 과정에서 상태를 초기화하기 위해 Recoil의 useResetRecoilState 사용합니다.
useEffect(() => {
// 컴포넌트가 언마운트될 때 formState 초기화
return () => {
resetFormState();
};
}, [resetFormState]);
컴포넌트가 언마운트될 때(Signup 페이지에서 떠날 때) formState를 초기화합니다.
목적: 다음 회원가입 요청 시 이전 데이터가 남아있지 않도록 상태를 리셋합니다.
const handleSubmit = async () => {
if (!formData.emailVerified) {
alert("이메일 인증이 필요합니다.");
return;
}
if (!formData.member_type) {
alert("회원 유형을 선택해 주세요.");
return;
}
const endpoint =
formData.member_type === "user"
? "http://localhost:3000/register"
: "http://localhost:3000/register/expert";
const payload = {
...formData[formData.member_type],
email: formData.email,
password: formData.password,
role: formData.member_type,
};
console.log("Payload being sent:", payload);
try {
const response = await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
const data = await response.json();
console.log("Response received:", data);
if (response.ok) {
alert(data.message || "회원가입 성공");
navigate("/");
} else {
alert(data.message || "회원가입 실패");
}
} catch (error) {
console.error("Error during signup:", error.message);
alert("회원가입 요청 중 오류가 발생했습니다.");
}
};
(1) 입력 검증
emailVerified: 이메일 인증이 완료되지 않았으면 경고 메시지 출력 후 종료.
member_type: "사용자" 또는 "전문가" 유형이 선택되지 않았으면 경고 메시지 출력 후 종료.
(2) 백엔드 API 요청 준비
endpoint: 회원 유형(user or expert)에 따라 다른 API URL 선택: 사용자: /register ,전문가: /register/expert
payload: 사용자 입력 데이터를 포함한 요청 본문(JSON).
(3) API 요청 fetch 사용: POST 요청으로 회원가입 데이터를 서버에 전송.
성공 응답 처리: 서버 응답이 성공적이면 성공 메시지를 표시하고 메인 페이지(/)로 이동.
실패 응답 처리: 실패 메시지를 표시하며 오류를 로그로 출력.
로그인 부분
import React, { useState } from "react";
import axios from "axios";
import { useNavigate } from "react-router-dom";
import { useSetRecoilState } from "recoil";
import {
authState,
expertAuthState,
superUserAuthState,
} from "../../state/authState";
function Login() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [userType, setUserType] = useState("user"); // "user", "expert", "superuser"
const [errorMessage, setErrorMessage] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
const navigate = useNavigate();
const setAuthState = useSetRecoilState(authState);
const setExpertAuthState = useSetRecoilState(expertAuthState);
const setSuperUserAuthState = useSetRecoilState(superUserAuthState);
const handleLogin = async () => {
if (!email || !password) {
setErrorMessage("이메일과 비밀번호를 입력해 주세요.");
return;
}
setIsSubmitting(true);
const endpoint =
userType === "user"
? "http://localhost:3000/login"
: userType === "expert"
? "http://localhost:3000/login/expert"
: "http://localhost:3000/login/superuser";
try {
console.log("🚀 [LOGIN] 요청 전송:", endpoint, { email, password });
const response = await axios.post(
endpoint,
{ email, password },
{ withCredentials: true }
);
console.log("✅ [LOGIN] 응답 데이터:", response.data);
const { id, member_type, ...userData } = response.data.data;
// 사용자 유형별로 상태 업데이트
if (member_type === "superuser") {
setSuperUserAuthState({
isLoggedIn: true,
user: { id, member_type, ...userData },
});
navigate("/superuserpage");
} else if (member_type === "expert") {
setExpertAuthState({
isLoggedIn: true,
user: { id, member_type, ...userData },
});
navigate("/system-management");
} else {
setAuthState({
isLoggedIn: true,
user: { id, member_type, ...userData },
});
navigate("/dashboard");
}
} catch (error) {
console.error("❌ [LOGIN] 오류:", error.response?.data || error.message);
setErrorMessage(
error.response?.data?.msg || "로그인 요청 중 문제가 발생했습니다."
);
} finally {
setIsSubmitting(false);
}
};
return (
<div className="min-h-screen flex flex-col justify-center items-center bg-gray-100">
<div className="bg-white p-6 rounded-lg shadow-md w-3/4 max-w-md">
<h1 className="text-2xl font-bold mb-6">로그인</h1>
<div className="space-y-4">
{/* 회원 유형 선택 */}
<div>
<label className="block text-gray-700 font-medium mb-2">
회원 유형
</label>
<select
value={userType}
onChange={(e) => setUserType(e.target.value)}
className="w-full p-3 border border-gray-300 rounded-md"
>
<option value="user">일반회원</option>
<option value="expert">관리자</option>
<option value="superuser">슈퍼유저</option>
</select>
</div>
{/* 이메일 입력 */}
<div>
<label className="block text-gray-700 font-medium">이메일</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full p-3 border border-gray-300 rounded-md"
placeholder="이메일을 입력하세요"
/>
</div>
{/* 비밀번호 입력 */}
<div>
<label className="block text-gray-700 font-medium">비밀번호</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full p-3 border border-gray-300 rounded-md"
placeholder="비밀번호를 입력하세요"
/>
</div>
{/* 오류 메시지 */}
{errorMessage && (
<p className="text-red-500 text-center">{errorMessage}</p>
)}
{/* 로그인 버튼 */}
<button
onClick={handleLogin}
className={`w-full px-4 py-3 font-bold rounded-md ${
isSubmitting
? "bg-gray-300 text-gray-700 cursor-not-allowed"
: "bg-blue-600 text-white hover:bg-blue-700"
}`}
disabled={isSubmitting}
>
{isSubmitting ? "로그인 중..." : "로그인"}
</button>
</div>
</div>
</div>
);
}
export default Login;
import { atom } from "recoil";
// 일반 사용자 로그인 상태
export const authState = atom({
key: "authState",
default: {
isLoggedIn: false, // 로그인 상태
user: null, // 로그인된 사용자 정보
},
});
// 관리자 로그인 상태
export const expertAuthState = atom({
key: "expertAuthState",
default: {
isLoggedIn: false, // 관리자 로그인 여부
user: null, // 로그인된 관리자 정보
},
});
// 슈퍼유저 로그인 상태
export const superUserAuthState = atom({
key: "superUserAuthState",
default: {
isLoggedIn: false, // 슈퍼유저 로그인 여부
user: null, // 로그인된 슈퍼유저 정보
},
});
이 코드는 Recoil을 사용해 구현된 로그인 페이지입니다. 사용자는 이메일, 비밀번호, 그리고 회원 유형(일반회원, 관리자, 슈퍼유저)을 입력하여 로그인할 수 있습니다. 각 입력값을 검증하며, 성공적인 로그인이 이루어지면 사용자 유형에 따라 적절한 페이지로 이동합니다.
1. 컴포넌트 상태 및 초기화
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [userType, setUserType] = useState("user"); // 기본값: 일반회원
const [errorMessage, setErrorMessage] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
email: 사용자가 입력한 이메일 주소입니다.
password: 사용자가 입력한 비밀번호입니다.
userType: 선택한 회원 유형 (user, expert, superuser). 기본값은 user입니다.
errorMessage: 로그인 실패 시 오류 메시지를 표시하기 위한 상태입니다.
isSubmitting: 로그인 요청 중인지 나타내는 상태. 요청 중에는 버튼이 비활성화되어 중복 클릭을 방지합니다.
2. Recoil 상태 관리
const setAuthState = useSetRecoilState(authState);
const setExpertAuthState = useSetRecoilState(expertAuthState);
const setSuperUserAuthState = useSetRecoilState(superUserAuthState);
authState: 일반회원 로그인 상태를 관리합니다.
expertAuthState: 관리자 로그인 상태를 관리합니다.
superUserAuthState: 슈퍼유저 로그인 상태를 관리합니다.
로그인 성공 시, 각 회원 유형에 따라 적절한 상태를 업데이트하여 전역 상태를 관리합니다.
3. handleLogin 함수
const handleLogin = async () => {
if (!email || !password) {
setErrorMessage("이메일과 비밀번호를 입력해 주세요.");
return;
}
setIsSubmitting(true);
입력값 검증
이메일과 비밀번호가 비어 있으면 오류 메시지를 표시하고 종료.
3.1. 요청할 API 엔드포인트 결정
const endpoint =
userType === "user"
? "http://localhost:3000/login"
: userType === "expert"
? "http://localhost:3000/login/expert"
: "http://localhost:3000/login/superuser";
선택한 회원 유형에 따라 로그인 요청을 보낼 API 엔드포인트를 결정합니다.
일반회원은 /login
관리자는 /login/expert
슈퍼유저는 /login/superuser
3.2. 로그인 요청
try {
console.log("🚀 [LOGIN] 요청 전송:", endpoint, { email, password });
const response = await axios.post(
endpoint,
{ email, password },
{ withCredentials: true }
);
console.log("✅ [LOGIN] 응답 데이터:", response.data);
xios.post: 이메일과 비밀번호를 요청 본문으로 전달합니다.
withCredentials: true: 브라우저가 서버에 세션 쿠키를 포함하도록 설정합니다.
로그 요청 정보: 요청이 전송되기 전과 응답을 받은 후 디버깅 정보를 출력합니다.
3.3. 로그인 성공 처리
const { id, member_type, ...userData } = response.data.data;
// 사용자 유형별로 상태 업데이트
if (member_type === "superuser") {
setSuperUserAuthState({
isLoggedIn: true,
user: { id, member_type, ...userData },
});
navigate("/superuserpage");
} else if (member_type === "expert") {
setExpertAuthState({
isLoggedIn: true,
user: { id, member_type, ...userData },
});
navigate("/system-management");
} else {
setAuthState({
isLoggedIn: true,
user: { id, member_type, ...userData },
});
navigate("/dashboard");
}
회원 유형별 상태 업데이트 및 페이지 이동합니다.
슈퍼유저(superuser): /superuserpage로 이동.
관리자(expert): /system-management로 이동.
일반회원(user): /dashboard로 이동.
4.1. 회원 유형 선택
<select
value={userType}
onChange={(e) => setUserType(e.target.value)}
className="w-full p-3 border border-gray-300 rounded-md"
>
<option value="user">일반회원</option>
<option value="expert">관리자</option>
<option value="superuser">슈퍼유저</option>
</select>
드롭다운 메뉴로 회원 유형을 선택합니다.
선택한 값은 userType 상태에 저장합니다.
4.2. 이메일 및 비밀번호 입력
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full p-3 border border-gray-300 rounded-md"
placeholder="이메일을 입력하세요"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full p-3 border border-gray-300 rounded-md"
placeholder="비밀번호를 입력하세요"
/>
사용자가 입력한 이메일과 비밀번호를 각각 email, password 상태에 저장합니다.
4.3. 로그인 버튼
<button
onClick={handleLogin}
className={`w-full px-4 py-3 font-bold rounded-md ${
isSubmitting
? "bg-gray-300 text-gray-700 cursor-not-allowed"
: "bg-blue-600 text-white hover:bg-blue-700"
}`}
disabled={isSubmitting}
>
{isSubmitting ? "로그인 중..." : "로그인"}
</button>
'프로젝트' 카테고리의 다른 글
개인정보-컴플라이언스-웹애플리케이션(11) - (자가진단 설문) 프론트 코드 (0) | 2025.01.29 |
---|---|
개인정보-컴플라이언스-웹애플리케이션(10) - (시스템) 프론트 코드 (0) | 2025.01.28 |
개인정보-컴플라이언스-웹애플리케이션(8) - (피드백) 백엔드 코드 (0) | 2025.01.26 |
개인정보-컴플라이언스-웹애플리케이션(7) - (자가진단 설문 결과) 백엔드 코드 (0) | 2025.01.26 |
개인정보-컴플라이언스-웹애플리케이션(6) - (자가진단 설문) 백엔드 코드 (0) | 2025.01.26 |