Recent Posts
«   2025/06   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
관리 메뉴

개발자 박가나

[241125 TIL] 본캠프 39일차 ('MBTI' 프로젝트 기능 구현) 본문

내일배움캠프

[241125 TIL] 본캠프 39일차 ('MBTI' 프로젝트 기능 구현)

gnchoco97 2024. 11. 25. 20:31

인증/인가 기능 구현을 위해서 테스트 전용 API(https://moneyfulpublicpolicy.co.kr)를 사용하였다.

 

회원가입

https://moneyfulpublicpolicy.co.kr/register로 post 요청을 보내는 방법으로 회원가입 기능을 구현하였다. 

/* src/api/Auth.js */

import axios from 'axios';

const BASE_URL = 'https://moneyfulpublicpolicy.co.kr';

export const runSignUp = async (data) => {
    try {
        const response = await axios.post(`${BASE_URL}/register`, data);

        return { data: response.data };
    } catch (e) {
        return { error: e };
    }
};
/* src/pages/SignUp.jsx */

import { useNavigate } from 'react-router-dom';
import { useForm } from '../hooks/useForm';
import { runSignUp } from '../api/MoneyfulPublicPolicy';

export default function SignUp() {
    const navigate = useNavigate();

    const { values, handleChange } = useForm({
        id: '',
        password: '',
        nickname: ''
    });

    const handleSignUp = async (e) => {
        e.preventDefault();

        const { data, error } = await runSignUp(values);

        if (error) {
            window.alert(`${error.status} 오류가 발생했습니다.`);
        }
        else if (data.success) {
            window.alert('회원가입에 성공했습니다.');
            navigate('/signin');
        }
        else {
            window.alert('회원가입에 실패했습니다.');
        }
    };
}

 

 

로그인

https://moneyfulpublicpolicy.co.kr/login으로 post 요청을 보내는 방법으로 로그인 기능을 구현하였다. 

  • 로그인 성공 시 localStorage에 accessToken 값 저장
  • localStorage에 accessToken 값이 있으면 로그인한 상태로 판단
/* src/api/Auth.js */

import axios from 'axios';

const BASE_URL = 'https://moneyfulpublicpolicy.co.kr';

export const runSignIn = async (data) => {
    try {
        const response = await axios.post(`${BASE_URL}/login`, data);

        return { data: response.data };
    } catch (e) {
        return { error: e };
    }
};
/* src/pages/SignIn.jsx */

import { useNavigate } from 'react-router-dom';
import { useForm } from '../hooks/useForm';
import { runSignIn } from '../api/MoneyfulPublicPolicy';
import { useAuth } from '../contexts/AuthContext';

export default function SignIn() {
    const navigate = useNavigate();

    const { login } = useAuth();

    const { values, handleChange } = useForm({
        id: '',
        password: ''
    });

    const handleSignIn = async (e) => {
        e.preventDefault();

        const { data, error } = await runSignIn(values);

        if (error) {
            window.alert(`${error.status} 오류가 발생했습니다.`);
        }
        else if (data.success) {
            window.alert('로그인에 성공했습니다.');
            login(data.accessToken);
            navigate('/');
        }
        else {
            window.alert('로그인에 실패했습니다.');
        }
    };
}
/* src/contexts/AuthContext.jsx */

import { useState } from 'react';

const token = localStorage.getItem('accessToken');

export const AuthProvider = ({ children }) => {
    const [isAuthenticated, setIsAuthenticated] = useState(!!token);

    const login = (token) => {
        localStorage.setItem('accessToken', token);
        setIsAuthenticated(true);
    };
};

 

 

로그아웃

localStorage에 저장된 값을 제거하는 방법으로 로그아웃 기능을 구현하였다.

  • 로그아웃 성공 시 localStorage에서 accessToken 값 제거
  • localStorage에 accessToken 값이 없으면 로그인하지 않은 상태(= 로그아웃한 상태)로 판단
/* src/components/Header.jsx */

import { useNavigate } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';

export default function Header() {
    const navigate = useNavigate();

    const { logout } = useAuth();

    const handleSignOut = () => {
        logout();
        navigate('/signin');
    };
}
/* src/contexts/AuthContext.jsx */

import { useState } from 'react';

const token = localStorage.getItem('accessToken');

export const AuthProvider = ({ children }) => {
    const [isAuthenticated, setIsAuthenticated] = useState(!!token);

    const logout = () => {
        localStorage.removeItem('accessToken');
        setIsAuthenticated(false);
    };
};

 

 

프로필 수정

https://moneyfulpublicpolicy.co.kr/profile로 patch 요청을 보내는 방법으로 프로필 수정 기능을 구현하였다.  

/* src/api/Auth.js */

import axios from 'axios';

const BASE_URL = 'https://moneyfulpublicpolicy.co.kr';

export const fetchUser = async (token) => {
    try {
        const response = await axios.get(`${BASE_URL}/user`, {
            headers: {
                Authorization: `Bearer ${token}`
            }
        });

        return { data: response.data };
    } catch (e) {
        return { error: e };
    }
};

export const updateUser = async (token, data) => {
    try {
        const response = await axios.patch(`${BASE_URL}/profile`, data, {
            headers: {
                'Content-Type': 'multipart/form-data',
                Authorization: `Bearer ${token}`
            }
        });

        return { data: response.data };
    } catch (e) {
        return { error: e };
    }
};
/* src/pages/Profile.jsx */

import { useEffect, useState } from 'react';
import { fetchUser, updateUser } from '../api/Auth';

const token = localStorage.getItem('accessToken');

export default function Profile() {
    const [nickname, setNickname] = useState('');

    useEffect(() => {
        const fetchUserData = async () => {
            const { data, error } = await fetchUser(token);

            // 오류 발생
            if (error) {
                window.alert(`${error.status} 오류가 발생했습니다.`);
            }
            // 성공
            else if (data.success) {
                setNickname(data.nickname);
            }
            // 실패
            else {
                window.alert('유저 정보를 불러오지 못했습니다.');
            }
        };

        fetchUserData();
    }, []);

    const handleUpdateUser = async () => {
        const { data, error } = await updateUser(token, { nickname });

        // 오류 발생
        if (error) {
            window.alert(`${error.status} 오류가 발생했습니다.`);
        }
        // 성공
        else if (data.success) {
            window.alert('프로필이 수정되었습니다.');
            window.location.reload();
        }
        // 실패
        else {
            window.alert('유저 정보를 불러오지 못했습니다.');
        }
    };
}

 

 

로그인 여부에 따라 접속 가능한 페이지 제한 (Protected Route)

로그인 여부에 따라 접속 가능한 페이지에 제한을 두었다.

  • 로그인 여부에 상관없이 Home 페이지 접속 가능
  • 로그인을 하지 않은 상태로 Profile / Test / Result 페이지 접속 시 SignIn 페이지로 이동
  • 로그인을 한 상태로 SignUp / SignIn 페이지 접속 시 Home 페이지로 이동
/* src/routes/AuthenticatedRoute.jsx */

import { Navigate, Outlet } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';

export default function AuthenticatedRoute() {
    const { isAuthenticated } = useAuth();

    if (!isAuthenticated) return <Navigate to="/signin" replace />;

    return <Outlet />;
}
/* src/routes/NonAuthenticatedRoute.jsx */

import { Navigate, Outlet } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';

export default function NonAuthenticatedRoute() {
    const { isAuthenticated } = useAuth();

    if (isAuthenticated) return <Navigate to="/" replace />;

    return <Outlet />;
}
/* src/routes/Router.jsx */

import { BrowserRouter, Route, Routes } from 'react-router-dom';
import AuthenticatedRoute from './AuthenticatedRoute';
import NonAuthenticatedRoute from './NonAuthenticatedRoute';

function Router() {
    return (
        <BrowserRouter>
            <Header />
            <Routes>
                <Route path="/" element={<Home />} />

                <Route element={<NonAuthenticatedRoute />}>
                    <Route path="/signin" element={<SignIn />} />
                    <Route path="/signup" element={<SignUp />} />
                </Route>

                <Route element={<AuthenticatedRoute />}>
                    <Route path="/profile" element={<Profile />} />
                    <Route path="/test" element={<Test />} />
                    <Route path="/result" element={<Result />} />
                </Route>
            </Routes>
        </BrowserRouter>
    );
}

export default Router;