TIL #39 심화주차 개인과제

DO YEON KIM·2024년 6월 11일
1

부트캠프

목록 보기
39/72

하루 하나씩 작성하는 TIL #39


주제 : 숙련주차때 만든 지출 관리 시스템에 인증 기능을 추가하고 JSON 서버를 이용해 데이터를 관리해봅시다.


숙련주차과제로 완성하신 코드베이스를 바탕으로 진행을 할 것이지만, 커밋 히스토리를 처음부터 다시 기록하고자 레퍼지토리는 새로 만들도록 하겠습니다.

Props-drilling, Context API, Redux 중 어느 방식을 기반으로 하셔도 무방합니다.
이번 과제에서는 Tanstack-Query 를 이용해 지출에 대한 상태관리를 해볼 것입니다. (’월’ 등 다른 상태관리는 편하게 해주세요.)


🔥 필수 구현 사항 입니다.
  • 지출 관리 시스템에 회원가입 / 로그인 기능 구현
    • 반드시, 강의에서 제공하는 jwt 인증서버를 사용하도록 합니다.
    • 인증이 되지 않는다면 서비스를 이용 할 수 없도록 해주세요.
  • json-server 를 이용해 지출 데이터에 대한 CRUD 를 구현해 주세요.
    • 지출 데이터에 누가 해당 지출을 생성 했는지가 포함시켜 봅시다.
  • API 호출 시, fetch 대신 axios 를 필수적으로 사용하도록 합니다.
  • 페이지에서 (jsx) 파일에서 API 응답 값을 바로 사용하지 말고, 꼭 Tanstack Query (ReactQuery)를 거쳐서 이용하도록 합니다.
    • 상태 관리를 위해 Props-drilling, Context API, Redux 사용대신 Tanstack Query 를 사용해야 합니다.

⚠️ 과제가 난이도가 결코 낮지 않으니 아래 데드라인을 맞춰보세요. 과제 해설 영상을 6/12(수)에 제공해드립니다.

~ 6/10 (월) : 프로젝트 셋업 및 로그인, 회원가입 페이지 UI 작업 및 라우터 설정

~ 6/11 (화) : 로그인 화면 API 연결 및 그에 따른 라우터 설정, Header Navigation Bar 컴포넌트 구성

~ 6/12 (수) : 지출 데이터에 createdBy 추가, json-server 셋업, 지출 CRUD 리팩토링

~ 6/13 (목) : 프로젝트 점검 및 배포


작성일 기준 어제인 6/10일 월요일엔 발제 + 팀변경 + 코드카타 및 스탠다드 분반 수업 + 강의 영상으로 인해 프로젝트 세팅을 하지 못하였으므로 화요일인 오늘 저 2가지를 해보고자 한다.


1. 프로젝트 셋업

배포된 zip 파일을 다운받은 후

  1. yarn install해준다.

  2. git -rf .git/ 명령어로 클론받은 프로젝트에서 기존 .git/ 폴더 삭제 후 다시 git 셋업해준다.

  3. 새로 레포지를 파서 push해준다.


2. 로그인, 회원 가입 API 설정

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;
};

3. Auth Context 설정

전역으로 사용자 인증 정보를 관리하기 위해 설정해준다.

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);

4. 로그인 페이지 UI 작업

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;

5. 회원가입 페이지

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;

유효성 검사는 추후에 넣도록 할 예정이다.


6. 라우터 설정

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;

7. 로그인 api 연결

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를 호출하여 사용자 인증을 처리해준다.


8. 헤더 네비게이션바 구성

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;
profile
프론트엔드 개발자를 향해서

0개의 댓글