하루 하나씩 작성하는 TIL #39
주제 : 숙련주차때 만든 지출 관리 시스템에 인증 기능을 추가하고 JSON 서버를 이용해 데이터를 관리해봅시다.
숙련주차과제로 완성하신 코드베이스를 바탕으로 진행을 할 것이지만, 커밋 히스토리를 처음부터 다시 기록하고자 레퍼지토리는 새로 만들도록 하겠습니다.
Props-drilling, Context API, Redux 중 어느 방식을 기반으로 하셔도 무방합니다.
이번 과제에서는 Tanstack-Query 를 이용해 지출에 대한 상태관리를 해볼 것입니다. (’월’ 등 다른 상태관리는 편하게 해주세요.)
~ 6/10 (월) : 프로젝트 셋업 및 로그인, 회원가입 페이지 UI 작업 및 라우터 설정
~ 6/11 (화) : 로그인 화면 API 연결 및 그에 따른 라우터 설정, Header Navigation Bar 컴포넌트 구성
~ 6/12 (수) : 지출 데이터에 createdBy 추가, json-server 셋업, 지출 CRUD 리팩토링
~ 6/13 (목) : 프로젝트 점검 및 배포
작성일 기준 어제인 6/10일 월요일엔 발제 + 팀변경 + 코드카타 및 스탠다드 분반 수업 + 강의 영상으로 인해 프로젝트 세팅을 하지 못하였으므로 화요일인 오늘 저 2가지를 해보고자 한다.
배포된 zip 파일을 다운받은 후
yarn install해준다.
git -rf .git/ 명령어로 클론받은 프로젝트에서 기존 .git/ 폴더 삭제 후 다시 git 셋업해준다.
새로 레포지를 파서 push해준다.
ui작업을 하기 전, api호출이 필요할 수있기 때문에 api 설정을 먼저 해준다.
아래 코드 및 정보는 공식 문서에서 확인 가능하다.
import axios from 'axios';
const BASE_URL = 'https://moneyfulpublicpolicy.co.kr';
// 회원가입 API
export const register = async (userData) => {
const response = await axios.post(`${BASE_URL}/register`, userData);
return response.data;
};
// 로그인 API
export const login = async (credentials) => {
const response = await axios.post(`${BASE_URL}/login`, credentials);
return response.data;
};
// 사용자 정보 확인 API
export const getUserInfo = async (token) => {
const response = await axios.get(`${BASE_URL}/user`, {
headers: { Authorization: `Bearer ${token}` }
});
return response.data;
};
// 프로필 변경 API
export const updateProfile = async (formData, token) => {
const response = await axios.patch(`${BASE_URL}/profile`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
'Authorization': `Bearer ${token}`
}
});
return response.data;
};
전역으로 사용자 인증 정보를 관리하기 위해 설정해준다.
import React, { createContext, useState, useContext, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import axios from 'axios';
import { getUserInfo, login as loginApi } from '../api/auth';
// Context 생성
const AuthContext = createContext();
// Context Provider 설정
export const AuthProvider = ({ children }) => {
const [auth, setAuth] = useState({ isAuthenticated: false, user: null, accessToken: null });
const navigate = useNavigate();
// 로그인 함수
const login = async (credentials) => {
const response = await loginApi(credentials);
setAuth({ isAuthenticated: true, user: response.userId, accessToken: response.accessToken });
localStorage.setItem('accessToken', response.accessToken);
};
// 로그아웃 함수
const logout = () => {
setAuth({ isAuthenticated: false, user: null, accessToken: null });
localStorage.removeItem('accessToken');
navigate('/login');
};
// 인증 상태 확인 함수
const checkAuth = async () => {
const token = localStorage.getItem('accessToken');
if (token) {
try {
const response = await getUserInfo(token);
setAuth({ isAuthenticated: true, user: response.id, accessToken: token });
} catch (error) {
logout();
}
}
};
// 컴포넌트가 마운트될 때 인증 상태 확인
useEffect(() => {
checkAuth();
}, []);
return (
<AuthContext.Provider value={{ auth, login, logout }}>
{children}
</AuthContext.Provider>
);
};
// AuthContext를 사용하는 커스텀 훅
export const useAuth = () => useContext(AuthContext);
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import useAuth from '../hooks/useAuth';
const Login = () => {
const [id, setId] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const navigate = useNavigate();
const { login } = useAuth();
const handleLogin = async (e) => {
e.preventDefault();
try {
await login({ id, password });
navigate('/');
} catch (err) {
setError('로그인에 실패했습니다.');
}
};
return (
<div>
<h2>로그인</h2>
<form onSubmit={handleLogin}>
<div>
<label>아이디:</label>
<input type="text" value={id} onChange={(e) => setId(e.target.value)} />
</div>
<div>
<label>비밀번호:</label>
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
</div>
{error && <p>{error}</p>}
<button type="submit">로그인</button>
</form>
<button onClick={() => navigate('/register')}>회원가입</button>
</div>
);
};
export default Login;
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { register as registerApi } from '../api/auth';
const Register = () => {
const [id, setId] = useState('');
const [password, setPassword] = useState('');
const [nickname, setNickname] = useState('');
const [error, setError] = useState('');
const navigate = useNavigate();
const handleRegister = async (e) => {
e.preventDefault();
try {
await registerApi({ id, password, nickname });
navigate('/login');
} catch (err) {
setError('회원가입에 실패했습니다.');
}
};
return (
<div>
<h2>회원가입</h2>
<form onSubmit={handleRegister}>
<div>
<label>아이디:</label>
<input type="text" value={id} onChange={(e) => setId(e.target.value)} />
</div>
<div>
<label>비밀번호:</label>
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
</div>
<div>
<label>닉네임:</label>
<input type="text" value={nickname} onChange={(e) => setNickname(e.target.value)} />
</div>
{error && <p>{error}</p>}
<button type="submit">회원가입</button>
</form>
<button onClick={() => navigate('/login')}>로그인</button>
</div>
);
};
export default Register;
유효성 검사는 추후에 넣도록 할 예정이다.
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import Login from './pages/Login';
import Register from './pages/Register';
import Home from './pages/Home';
import Profile from './pages/Profile';
import Navbar from './components/Navbar';
import { AuthProvider } from './context/authContext';
const queryClient = new QueryClient();
const App = () => {
return (
<QueryClientProvider client={queryClient}>
<AuthProvider>
<Router>
<Navbar />
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route path="/" element={<Home />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</Router>
</AuthProvider>
</QueryClientProvider>
);
};
export default App;
import { useContext } from 'react';
import { AuthContext } from '../context/authContext';
const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};
export default useAuth;
로그인 api를 호출하여 사용자 인증을 처리해준다.
import React from 'react';
import { Link, useNavigate } from 'react-router-dom';
import useAuth from '../hooks/useAuth';
const Navbar = () => {
const { auth, logout } = useAuth();
const navigate = useNavigate();
const handleLogout = () => {
logout();
navigate('/login');
};
return (
<nav>
<ul>
<li><Link to="/">Home</Link></li>
{auth.isAuthenticated ? (
<>
<li><Link to="/profile">내 프로필</Link></li>
<li><button onClick={handleLogout}>로그아웃</button></li>
</>
) : (
<li><Link to="/login">로그인</Link></li>
)}
</ul>
</nav>
);
};
export default Navbar;