[TIL] 9주차 화요일. React 심화 주차. JSW 인증 서버 회원가입 로그인

Minji Kim·2024년 6월 11일

내배캠TIL

목록 보기
38/73

API URL

https://moneyfulpublicpolicy.co.kr

세팅

기존과 동일, axios 추가

yarn add axios
or
npm install axios

1. src/context/AuthContext.jsx

import React, { createContext, useState, useEffect } from "react";

export const AuthContext = createContext();

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

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

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

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

  return (
    <AuthContext.Provider value={{ isAuthenticated, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

2. src/shared/Router.jsx

import React, { useState, useContext } from "react";
import {
  BrowserRouter as Router,
  Route,
  Routes,
  Navigate,
} from "react-router-dom";
import Home from "../pages/Home.jsx";
import Record from "../pages/Record.jsx";
import Login from "../pages/Login.jsx";
import SignUp from "../pages/SignUp.jsx";
import MyPage from "../pages/MyPage.jsx";
import { AuthContext } from "../context/AuthContext";

// PrivateRoute : 로그인이 필요한 페이지에 접근할 수 있도록 하는 컴포넌트
// 로그인이 되어있지 않은 사용자는 login 페이지로 리다이렉트
const PrivateRoute = ({ element: Element, ...rest }) => {
  const { isAuthenticated } = useContext(AuthContext);
  return isAuthenticated ? <Element {...rest} /> : <Navigate to="/login" />;
};

// PublicRoute : 로그인이 필요없는 페이지에 접근할 수 있도록 하는 컴포넌트
// 로그인이 되어있는 사용자는 mypage로 리다이렉트
const PublicRoute = ({ element: Element, ...rest }) => {
  const { isAuthenticated } = useContext(AuthContext);
  return !isAuthenticated ? <Element {...rest} /> : <Navigate to="/mypage" />;
};

const SharedRouter = () => {
  const [selectedMonth, setSelectedMonth] = useState("");
  return (
    <>
      <Router>
        <Header />
        <Routes>
          <Route
            path="/"
            element={
              <Home
                selectedMonth={selectedMonth}
                setSelectedMonth={setSelectedMonth}
              />
            }
          />
          <Route path="/login" element={<PublicRoute element={Login} />} />
          <Route path="/signup" element={<PublicRoute element={SignUp} />} />
          <Route path="/mypage" element={<PrivateRoute element={MyPage} />} />
          <Route path="/record/:id" element={<Record />} />
        </Routes>
      </Router>
    </>
  );
};

export default SharedRouter;

3. src/App.jsx

import React from "react";
import GlobalStyle from "./styles/GlobalStyle.jsx";
import Router from "./shared/Router.jsx";
import Layout from "./components/layout/Layout.jsx";
import { RecordProvider } from "./contexts/RecordContext.jsx";
import { AuthProvider } from "./contexts/AuthContext.jsx";

const App = () => {
  return (
    <>
      <AuthProvider>
        <RecordProvider>
          <GlobalStyle />
          <Layout>
            <Router />
          </Layout>
        </RecordProvider>
      </AuthProvider>
    </>
  );
};

export default App;

4. Layout.jsx (+헤더) 세팅

import React, { useContext } from "react";
import { Link, useNavigate } from "react-router-dom";
import { AuthContext } from "../context/AuthContext";
import styled from "styled-components";
import Button from "../atoms/Button";
const Layout = ({ children }) => {
  const { isAuthenticated, logout } = useContext(AuthContext);
  const navigate = useNavigate();
  const handleLogout = () => {
    const confirmLogout = window.confirm("정말로 로그아웃 하시겠습니까?");
    if (confirmLogout) {
      logout();
      navigate("/");
    }
  };
  return (
    <>
      <Header>
        <HeaderLeft>
          {" "}
          <Link to="/">HOME</Link>
          <span>내 프로필</span>
        </HeaderLeft>
        <HeaderRight>
          <div>사진</div>
          <span>아이디</span>
          {isAuthenticated ? (
            <Button
              backgroundColor="#ff4d4d"
              color="white"
              contents="로그아웃"
              onClick={handleLogout}
            />
          ) : (
            <>
              <Link to="/login">
                <Button
                  backgroundColor="#8cbaff"
                  color="white"
                  contents="로그인"
                />
              </Link>
              <Link to="/signup">
                <Button
                  backgroundColor="#8cbaff"
                  color="white"
                  contents="회원가입"
                />
              </Link>
            </>
          )}
        </HeaderRight>
      </Header>
      <LayoutDiv>{children}</LayoutDiv>
    </>
  );
};
// styled 코드 생략
export default Layout;

5. src/pages/Login.jsx

import React, { useState, useContext } from "react";
import { useNavigate } from "react-router-dom";
import axios from "axios";
import { AuthContext } from "../context/AuthContext";
import styled from "styled-components";
import StyledContainer from "../styles/StyledContainer";
import Button from "../components/atoms/Button";

const Login = () => {
  const [id, setId] = useState("");
  const [password, setPassword] = useState("");
  const { login } = useContext(AuthContext);
  const navigate = useNavigate();

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      const response = await axios.post(
        "https://moneyfulpublicpolicy.co.kr/login",
        {
          id,
          password,
        }
      );
      const data = response.data;
      if (data.success) {
        login(data.accessToken);
        navigate("/mypage");
      } else {
        alert("Login failed");
      }
    } catch (error) {
      console.error("Login error:", error);
      alert("Login failed");
    }
  };

  return (
    <>
      <LoginPage>
        <LoginForm onSubmit={handleSubmit}>
          <InputDiv>
            <span>로그인 아이디</span>
            <Input
              type="text"
              value={id}
              onChange={(e) => setId(e.target.value)}
              placeholder="ID"
            />
          </InputDiv>
          <InputDiv>
            <span>비밀번호</span>
            <Input
              type="password"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              placeholder="Password"
            />
          </InputDiv>
          <Button
            width="100%"
            backgroundColor="#999"
            color="white"
            margin="0 0 1.6rem 0"
            contents="로그인"
            type="submit"
          />
          <Link to="/signup">
            <Button
              width="100%"
              backgroundColor="#6c757d"
              color="white"
              contents="회원가입"
              type="button"
            />
          </Link>
        </LoginForm>
      </LoginPage>
    </>
  );
};

export default Login;

// styled 코드 생략

6. src/pages/SignUp.jsx

import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import axios from "axios";
import styled from "styled-components";
import StyledContainer from "../styles/StyledContainer";
import Button from "../components/atoms/Button";

const SignUp = () => {
  const [id, setId] = useState("");
  const [password, setPassword] = useState("");
  const [nickname, setNickname] = useState("");
  const navigate = useNavigate();

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      const response = await axios.post(
        "https://moneyfulpublicpolicy.co.kr/register",
        {
          id,
          password,
          nickname,
        }
      );
      const data = response.data;
      if (data.success) {
        navigate("/login");
      } else {
        alert("Signup failed");
      }
    } catch (error) {
      console.error("Signup error:", error);
      alert("Signup failed");
    }
  };

  return (
    <>
      <SignUpPage>
        <SignUpForm onSubmit={handleSubmit}>
          <InputDiv>
            <span>회원가입 아이디</span>
            <Input
              type="text"
              value={id}
              onChange={(e) => setId(e.target.value)}
              placeholder="ID"
            />
          </InputDiv>
          <InputDiv>
            <span>비밀번호</span>
            <Input
              type="password"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              placeholder="Password"
            />
          </InputDiv>
          <InputDiv>
            <span>닉네임</span>
            <Input
              type="text"
              value={nickname}
              onChange={(e) => setNickname(e.target.value)}
              placeholder="Nickname"
            />
          </InputDiv>
          <Button
            width="100%"
            backgroundColor="#999"
            color="white"
            margin="0 0 1.6rem 0"
            contents="회원가입"
            type="submit"
          />
          <Link to="/login">
            <Button
              width="100%"
              backgroundColor="#6c757d"
              color="white"
              contents="로그인"
              type="button"
            />
          </Link>
        </SignUpForm>
      </SignUpPage>
    </>
  );
};

export default SignUp;

// styled 코드 생략

src/pages/MyPage.jsx

import React, { useEffect, useState, useContext } from "react";
import axios from "axios";
import { AuthContext } from "../context/AuthContext";
import { useNavigate } from "react-router-dom";

const MyPage = () => {
  const [userInfo, setUserInfo] = useState(null);
  const [newNickname, setNewNickname] = useState("");
  const { isAuthenticated } = useContext(AuthContext);
  const navigate = useNavigate();

  useEffect(() => {
    if (!isAuthenticated) {
      alert("로그인이 필요합니다.");
      navigate("/login");
    } else {
      const fetchUserInfo = async () => {
        try {
          const token = localStorage.getItem("accessToken");
          const response = await axios.get(
            "https://moneyfulpublicpolicy.co.kr/user",
            {
              headers: {
                Authorization: `Bearer ${token}`,
              },
            }
          );
          setUserInfo(response.data);
        } catch (error) {
          console.error("Failed to fetch user info:", error);
        }
      };
      fetchUserInfo();
    }
  }, [isAuthenticated, navigate]);

  const handleNicknameChange = async (e) => {
    e.preventDefault();
    try {
      const token = localStorage.getItem("accessToken");
      const formData = new FormData();
      formData.append("nickname", newNickname);

      const response = await axios.patch(
        "https://moneyfulpublicpolicy.co.kr/profile",
        formData,
        {
          headers: {
            Authorization: `Bearer ${token}`,
            "Content-Type": "multipart/form-data",
          },
        }
      );

      if (response.data.success) {
        setUserInfo((prevState) => ({
          ...prevState,
          nickname: response.data.nickname,
        }));
        alert("닉네임이 변경되었습니다.");
        setNewNickname("");
      } else {
        alert("닉네임 변경에 실패했습니다.");
      }
    } catch (error) {
      console.error("Failed to update nickname:", error);
      alert("닉네임 변경에 실패했습니다.");
    }
  };

  if (!userInfo) {
    return <div>Loading...</div>;
  }
  return (
    <div>
      <h2>My Page</h2>
      <p>ID: {userInfo.id}</p>
      <p>Nickname: {userInfo.nickname}</p>

      <form onSubmit={handleNicknameChange}>
        <input
          type="text"
          value={newNickname}
          onChange={(e) => setNewNickname(e.target.value)}
          placeholder="새 닉네임"
        />
        <button type="submit">닉네임 변경</button>
      </form>
    </div>
  );
};

export default MyPage;

0개의 댓글