가장 처음에 구현할 수 있는 기능이 로그인/회원가입이라서 로그인과 회원가입을 팀원님과 나눠서 개발하기 시작했다.
미션코스 때에는 API가 없기도 해서 그냥 로컬 객체에 임의로 사용자 데이터를 만들어놓고 사용자 데이터가 일치하면 로그인이 성공하는 기능을 만들어놨었는데, 이번엔 API를 연결시켜야 하고 토큰도 발급해야해서 어려움이 있었다.
로그인 기능 자체는 미션코스때 해봤던 거라서 어렵지는 않았다. 다만 미션코스때와의 차이점이라면, useState를 하나만 썼다는거? 굳이 email과 password를 분리하지 않고 하나의 handler로 처리했다.
const handleChange = (e) => {
const {name, value} = e.target;
setUser({...user, [name]: value});
}
이 부분인데, 모든 요소들을 바꾸지 않다면 ...user를
사용해서 원하는 요소만 바꿔줄 수 있다.
useState관련해서와 나머지 요소들을 멘토님이 진행하셨던 프로젝트 코드를 보면서 진행했었는데, form의 input에서 value값을 받지 않고 handler로만 처리하셨길래 질문했더니 input에서 value값을 받는것이 좋다고 하셨다.
아래는 내가 작성한 LoginPage.jsx 코드이다.
import { Link, useNavigate } from "react-router-dom";
import { useState, useEffect } from "react";
import { login } from "../components/Auth";
import style from "../components/LoginPage.module.css"
import Footer from "../components/Footer";
const LoginPage = () => {
const navigate = useNavigate();
const [user, setUser] = useState({
email: '',
password: ''
});
const confirmLogin = async (e) => {
e.preventDefault();
try{
const { email, password } = user;
const result = await login({email, password}, navigate);
if(result){
alert("로그인 성공");
navigate('/');
}
} catch (error){
alert("로그인 실패");
}
};
const handleChange = (e) => {
const {name, value} = e.target;
setUser({...user, [name]: value});
}
return(
<div style={{
backgroundColor: "#111",
minHeight: "100vh",
width: "100%",
}}>
<div className={style.box}>
<h1 className={style.title}>SIGN IN</h1>
<div>아직 계정이 없으신가요? <Link className={style.link} to="/signup">가입하기</Link></div>
<form className={style.form} onSubmit={confirmLogin}>
<input className={style.input}
type = "email"
id = "email"
name = "email"
placeholder = "이메일"
value = {user.email}
autoFocus
onChange = {handleChange}
/>
<input className={style.input}
type = "password"
id = "password"
name = "password"
placeholder = "비밀번호"
value = {user.password}
onChange = {handleChange}
/>
<button className={style.button} type="submit">로그인</button>
</form>
</div>
<Footer />
</div>
);
};
나는 초보이고 프로젝트 단위의 작업을 해본게 아니라서 항상 한 파일에 꾸겨넣거나 컴포넌트를 분리하는 이유를 잘 몰랐는데, 개발을 진행하기 전에 다른 사람들이 한 프로젝트 파일들을 훑어보니 컴포넌트들을 많이 나누는 걸 보고 컴포넌트를 나눠야할 필요성에대해 조금 알게 된 것 같다.
컴포넌트를 나눠놓으면 생각보다 재사용을 하게 될 일이 많다는 걸 느꼈다. 아직 로그인만 구현한 내 수준에서는 그렇게까지 필요하지 않는데, 당장 조금만 뒤의 일을 생각해봐도 만들어놓은 코드로 쉽게 구현한다면 훨씬 좋겠다는 생각을 했다.
다른 프로젝트들의 파일을 대충 보니까 '고수'들은 버튼이며, API처리며, 뭐 별거를 다 만들어놨는데 그것들이 한 곳에서만 쓰이지 않고 여러 곳에서 쓰이는 것을 보고 참 대단하다고 생각했다. 나는 아직 그정도 수준이 아니라서 그렇게 만들지는 못했지만, 조금은 재사용하기 위해서 머리를 굴려봐야겠다.
그래서 이번에 로그인 페이지 따로, 로그인을 처리하는 컴포넌트(로그아웃과 다른 API처리도 여기서 진행할듯)를 따로 만들었다.
API를 받아오기 위해서는 fetch나 axios를 사용해야 하는데, 솔직히 둘 다 잘 몰라서 요즘은 axios를 많이 쓰는듯해서 axios를 사용해봤다.
미션코스 때는 내가 직접 회원 아이디와 비밀번호를 체크해서 로그인하는 걸 구현했기 때문에 로그인은 유저 정보를 받아서 계정정보를 내가 체크해야하는 줄 알았다. 그래서 HTTP 메서드가 get일줄 알았다.. 근데 POST였다. 로그인을 하면 계정 정보를 서버에 보내줘야 하니까 POST인 것 같다 맞겠지?
import axios from 'axios';
export const api = axios.create({
baseURL: "주소",
});
export const login = async ({email, password}, navigate) =>{
try {
const res = await api.post("/users/login/", {
email,
password,
});
localStorage.setItem("token", res.data.token);
api.defaults.headers.common["Authorization"] = `Bearer ${res.data.token}`;
console.log("로그인 성공");
navigate("/");
return res.data;
}
catch (error) {
console.error("로그인 실패:", error);
navigate("/login");
throw error;
}
};
axios 인스턴스를 만들어서 다른 컴포넌트에서도 쉽게 사용할 수 있게 만들어보았다.
localStorage에 토큰을 저장하는 형태로 진행했고, 로그인을 성공했을 때 Authorization 헤더 설정을 했다. 아직 로그인시 페이지에서 권한을 받는 걸 진행하지 않아서 잘은 모르지만, 이를 통해 권한을 받는 코드가 조금 더 간단하게 진행될 수 있는 것 같다.
export const logout = (navigate) =>{
localStorage.removeItem("token");
delete api.defaults.headers.common["Authorization"];
navigate("/");
};
로그아웃은 어렵지 않았다.
이렇게 위에서 말했던 것 처럼 다른 컴포넌트에서 재사용할 수 있도록 조금 모아놨다.
Auth.jsx 파일이다.
import axios from 'axios';
export const api = axios.create({
baseURL: "주소",
});
export const login = async ({email, password}, navigate) =>{
try {
const res = await api.post("/users/login/", {
email,
password,
});
localStorage.setItem("token", res.data.token);
api.defaults.headers.common["Authorization"] = `Bearer ${res.data.token}`;
console.log("로그인 성공");
navigate("/");
return res.data;
}
catch (error) {
console.error("로그인 실패:", error);
navigate("/login");
throw error;
}
};
export const logout = (navigate) =>{
localStorage.removeItem("token");
delete api.defaults.headers.common["Authorization"];
navigate("/");
};
export const isAuthenticated = () => {
return !!localStorage.getItem("token"); //토큰이 있으면 로그인 상태
}
디자인에도 꽤나 많은 어려움이 있었다. Figma에서 시키는 대로 하면 될 줄 알았는데 생각보다 Figma에서 주는 정보가 많지 않다.. 물론 도움은 많이 된다.
이번에 멘토님이 module.css를 추천해주셔서 module.css를 사용해봤는데, 왜 추천해주신지 알 것 같다. css가 생각보다 충돌이 많이 일어나는 것 같다. 가끔씩 왜 잘못된지도 모르겠다
그리고 가장 큰 난관중에 하나는 배경색을 꽉 채우는 거였는데, 배경색을 꽉 채우는 방법을 한참 찾다가 이미 깨달으신 팀원분이 알려주셨다. index.html파일에서 margin:0px을 하면 되는 간단한 문제였다. 이에 관해서는 reset css라는 게 있단다. 아직 잘 살펴보지 않았다. 이미 프로젝트를 시작했기에.. 시작할 때 하면 좋다길래 다음번에는 꼭 써봐야할 것 같다.
중앙정렬 하는 법
position: absolute;
left: 50%;
top: 40%;
transform: translate(-50%, -50%);
근데 footer가 있어서 위로 살짝 올라갔음 좋겠어서 top: 40%로 했다. footer는 팀원분이 만들어주셨다.
input과 button의 width 크기를 맞추고 싶어서 box-sizing: border-box를 사용했다. 그리고 margin을 적절히 이용해서 요소끼리의 사이를 만들었다.
LoginPage.module.css
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css");
.box{
display: flex;
width: 294px;
flex-direction: column;
position: absolute;
left: 50%;
top: 40%;
transform: translate(-50%, -50%);
color: #F2F1EF;
font-family: -apple-system,
BlinkMacSystemFont,
"Apple SD Gothic Neo",
"Pretendard Variable",
Pretendard, Roboto,
"Noto Sans KR",
"Segoe UI",
"Malgun Gothic",
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol",
sans-serif;
}
.title{
color: #F2F1EF;
font-family: CHANEY;
font-size: 24px;
font-style: normal;
font-weight: 400;
line-height: 120%; /* 28.8px */
letter-spacing: -0.264px;
}
.link{
color:#FC9A7E;
}
.form {
align-items: flex-start;
flex-direction: column;
margin: 28px 0 0;
}
.input{
margin: 14px 0;
height: 54px;
width: 294px;
color:#B0B0B0;
&:focus{
outline-color:#FDBBA9;
}
box-sizing: border-box;
}
.button{
margin: 28px 0 0;
box-sizing: border-box;
height: 54px;
width: 294px;
padding: 12px 105px;
color: #E7E7E7;
border-radius: 2px;
border: 1px solid var(--Neutral-70, #B0B0B0);
background: var(--Neutral-10, #3A3A3A);
&:hover{
border: 1px solid var(--Ellipse-23-300, #FC9A7E);
background: var(--Neutral-10, #3A3A3A);
color:#FC9A7E;
}
&:active{
border: 1px solid var(--Ellipse-23-500, #FA5628);
background: var(--Neutral-10, #3A3A3A);
color:#FA5628;
}
}

로그인 페이지 자체는 마음에 드는 것 같다. 디자인 파트 팀원님이 만들어주신거랑 최대한 비슷하게 만들어보려고 노력했다. 하지만 이게 가장 쉬웠던 과제일 것 같다는 거.. 앞으로의 프로젝트가 더욱 어려운 것이 많을 것이다. 이번에 이걸 구현하면서 느낀것도 생각했던 것보다 어렵다는 것이어서.. 최대한 더 노력해봐야겠다.