티스토리 뷰

지금 백엔드 api연결이 안되어서 위에 메뉴들과 비밀번호 초기화가 넘어가지 않는 상황.

임시테스트 용으로 코드를 바꿔봄

 

 

메뉴 설정

// useCategory.ts 수업 내용

/*import { useState, useEffect } from 'react';
import { Category } from '../models/category.model';
import { fetchCategory } from '../api/category.api';

export const useCategory = () => {
  const [category, setCategory] = useState<Category[]>([]);

  useEffect(() => {
    fetchCategory().then((category) => {
      console.log(" 받은 카테고리", category); // 

      if (!category) return;

      const categoryWithAll = [
        {
          id: null,
          name: "전체",
        },
        ...category,
      ];

      setCategory(categoryWithAll);
    });
  }, []);

  return { category };
};*/
// 하드코딩으로 임시 추가

import { useState, useEffect } from 'react';
import { Category } from '../models/category.model';
import { fetchCategory } from '../api/category.api';

export const useCategory = () => {
  const [category, setCategory] = useState<Category[]>([]);

  useEffect(() => {
    // 여기에 임시로 하드코딩된 카테고리 리스트를 사용
    const hardcodedCategories = [
      { id: null, name: "전체" },
      { id: 1, name: "동화" },
      { id: 2, name: "소설" },
      { id: 3, name: "사회" },
    ];

    setCategory(hardcodedCategories);  // 카테고리 상태 업데이트
  }, []);

  return { category };
};

>>이렇게 나옴

비밀번호 초기화

//  ResetPassword.tsx 원래 코드
/*import { useState } from "react";
import { useForm } from "react-hook-form";
import { Link, useNavigate } from "react-router-dom";
import { resetPassword, resetRequest } from "../api/auth.api";
import Button from "../components/common/Button";
import InputText from "../components/common/inputText";
import Title from "../components/common/Title";
import { useAlert } from "../hooks/useAlert";
import { SignupStyle } from "./Signup";

export interface SignupProps {
    email: string;
    password: string;
}

function ResetPassword() {
    const navigate = useNavigate();
    const showAlert = useAlert();
    const [resetRequested, setResetRequested] = useState(false);

    const { 
        register, 
        handleSubmit, 
        formState: {errors} 
    } = useForm<SignupProps>();

    const onSubmit = (data: SignupProps) => {
        if (resetRequested) {
            // 초기화
            resetPassword(data).then(() => {
                showAlert("비밀번호가 초기화되었습니다.");
                navigate("/login");
            });
        } else {
            // 요청
            resetRequest(data).then(() => {
                setResetRequested(true);
            });
        }
    };

    return (
        <>
            <Title size='large'>비밀번호 초기화</Title>
            <SignupStyle>
                <form onSubmit={handleSubmit(onSubmit)}>
                    <fieldset>
                        <InputText 
                            placeholder="이메일" 
                            inputType="email"
                            {...register("email", {required: true})}
                        />
                        {errors.email && <p className="error-text">이메일을 입력해주세요.</p>}
                    </fieldset>
                    {resetRequested && (
                        <fieldset>
                            <InputText 
                                placeholder="비밀번호" 
                                inputType="password"
                                {...register("password", {required: true})}
                            />
                            {errors.password && <p className="error-text">비밀번호를을 입력해주세요.</p>}
                        </fieldset>
                    )}
                    <fieldset>
                        <Button type="submit" size="medium" scheme="primary">
                            {resetRequested ? "비밀번호 초기화" : "초기화 요청"}
                        </Button>
                    </fieldset>
                    <div className="info">
                        <Link to="/reset">비밀번호 초기화</Link>
                    </div>
                </form>
            </SignupStyle>
        </>
    );
}

export default ResetPassword;*/
// 📄 테스트용 ResetPassword.tsx
// 백엔드 연동 없이 비밀번호 초기화 흐름(UI 전환)만 확인할 수 있는 코드

import { useState } from "react";
import { useForm } from "react-hook-form";
import { Link, useNavigate } from "react-router-dom";
import Button from "../components/common/Button";
import InputText from "../components/common/inputText";
import Title from "../components/common/Title";
import { useAlert } from "../hooks/useAlert";
import { SignupStyle } from "./Signup";

// 입력받을 데이터 타입 지정
export interface SignupProps {
  email: string;
  password: string;
}

function ResetPassword() {
  const navigate = useNavigate();         // 페이지 이동용 훅
  const showAlert = useAlert();           // 커스텀 alert 사용
  const [resetRequested, setResetRequested] = useState(false); // 초기화 요청 여부 상태

  // react-hook-form 훅 설정
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<SignupProps>();

  // 폼 제출 시 동작
  const onSubmit = (data: SignupProps) => {
    console.log("폼 제출 데이터:", data); // 제출된 값 확인용 로그

    // 초기화 요청 단계: 비밀번호 입력 필드를 보여줌
    if (!resetRequested) {
      setResetRequested(true);  // 초기화 요청 단계로 변경
      return;
    }

    // 비밀번호 입력 후 초기화 완료 처리
    showAlert("비밀번호가 초기화되었습니다.");
    navigate("/login"); // 로그인 페이지로 이동
  };

  return (
    <>
      <Title size="large">비밀번호 초기화</Title> {/* 제목 */}
      <SignupStyle>
        <form onSubmit={handleSubmit(onSubmit)}>
          {/* 이메일 입력 필드 */}
          <fieldset>
            <InputText
              placeholder="이메일"
              inputType="email"
              {...register("email", { required: true })}
            />
            {errors.email && (
              <p className="error-text">이메일을 입력해주세요.</p>
            )}
          </fieldset>

          {/* 초기화 요청 이후에만 비밀번호 필드가 표시됨 */}
          {resetRequested && (
            <fieldset>
              <InputText
                placeholder="비밀번호"
                inputType="password"
                {...register("password", { required: true })}
              />
              {errors.password && (
                <p className="error-text">비밀번호를 입력해주세요.</p>
              )}
            </fieldset>
          )}

          {/* 버튼: 상태에 따라 텍스트 변경 */}
          <fieldset>
            <Button type="submit" size="medium" scheme="primary">
              {resetRequested ? "비밀번호 초기화" : "초기화 요청"}
            </Button>
          </fieldset>

          {/* 보조 링크 */}
          <div className="info">
            <Link to="/reset">비밀번호 초기화</Link>
          </div>
        </form>
      </SignupStyle>
    </>
  );
}

export default ResetPassword;

 

 

//백엔드 연동 동적 UI개발(5)

연동이 안되어있기 때문에 임시 코드를 하여 로그인이 되도록 해야함

 

로그인 구현

https://www.npmjs.com/package/zustand

 

zustand

🐻 Bear necessities for state management in React. Latest version: 5.0.4, last published: 9 days ago. Start using zustand in your project by running `npm i zustand`. There are 3750 other projects in the npm registry using zustand.

www.npmjs.com

npm install zustand --save

 

 

//authStore.ts

// 로그인 상태 관리하는 코드 (Zustand 사용)

import { create } from "zustand";

// 상태에 들어갈 값과 함수들 정의
interface StoreState {
  isLoggedIn: boolean;                // 지금 로그인 돼 있는지
  storeLogin: (token: string) => void; // 로그인할 때 실행하는 함수
  storeLogout: () => void;             // 로그아웃할 때 실행하는 함수
}

// 로컬스토리지에서 토큰 가져오기 (로그인 유지용)
export const getToken = () => {
  return localStorage.getItem("token");
};

// 로컬스토리지에 토큰 저장 (로그인 성공 시 사용)
const setToken = (token: string) => {
  localStorage.setItem("token", token);
};

// 로컬스토리지에서 토큰 삭제 (로그아웃 시 사용)
export const removeToken = () => {
  localStorage.removeItem("token");
};

// Zustand로 로그인 상태 저장소 만들기
export const useAuthStore = create<StoreState>((set) => ({
  // 처음 시작할 때 로그인 상태 확인 (토큰 있으면 로그인 상태 true)
  isLoggedIn: getToken() ? true : false,

  // 로그인 함수: 상태 true로 바꾸고 토큰 저장
  storeLogin: (token: string) => {
    set({ isLoggedIn: true });
    setToken(token);
  },

  // 로그아웃 함수: 상태 false로 바꾸고 토큰 삭제
  storeLogout: () => {
    set({ isLoggedIn: false });
    removeToken();
  },
}));

 

  • 이 파일은 Zustand를 사용해서 로그인 상태를 관리하는 스토어
  • 주로 전역에서 로그인 상태(isLoggedIn)를 확인하거나 변경할 때 사용
//auth.api.ts

import { SignupProps } from "../pages/Signup";
import { httpClient } from "./http";

export const signup = async(userData: SignupProps) => {
    const response = await httpClient.post("/users/join", userData);
    return response.data;
}

export const resetRequest = async(data: SignupProps) => {
    const response = await httpClient.post("/users/reset", data);
    return response.data;
}

export const resetPassword = async(data: SignupProps) => {
    const response = await httpClient.put("/users/reset", data);
    return response.data;
}

interface LoginResponse {
    token: string;
}

export const login = async(data: SignupProps) => {
    const response = await httpClient.post<LoginResponse>("/users/login", data);
    return response.data;
}

 

회원가입, 비밀번호 재설정, 로그인과 같은 인증 관련 API 요청 관리

 

// Login 페이지: 로그인 폼과 로그인 처리 로직 구현

import Title from "../components/common/Title";
import InputText from "../components/common/inputText";
import { Link, useNavigate } from "react-router-dom";
import Button from "../components/common/Button";
import { useForm } from "react-hook-form"; // 폼 관리 라이브러리
import { login } from "../api/auth.api"; // 로그인 API 호출 함수
import { useAlert } from "../hooks/useAlert"; // 알림창 띄우는 커스텀 훅
import { SignupProps, SignupStyle } from "./Signup"; // SignupProps는 재사용 중 (LoginProps로 따로 만드는 게 더 깔끔함)
import { useAuthStore } from "../store/authStore"; // Zustand 상태 관리 훅

const Login = () => {
  const navigate = useNavigate(); // 페이지 이동
  const showAlert = useAlert();   // 알림창 띄우기
  const { storeLogin } = useAuthStore(); // 로그인 상태 저장 함수

  const {
    register,      // 폼 입력값 관리
    handleSubmit,  // 폼 제출 처리
    formState: { errors }, // 입력 오류 상태
  } = useForm<SignupProps>(); // 폼에서 사용할 데이터 타입 지정 (현재는 SignupProps 재사용)

  // 로그인 버튼 눌렀을 때 실행되는 함수
  const onSubmit = (data: SignupProps) => {
    login(data).then(
      (res) => {
        storeLogin(res.token); // 로그인 성공 → 토큰 저장
        showAlert("로그인에 성공하였습니다"); // 성공 알림
        navigate("/"); // 메인 페이지로 이동
      },
      () => {
        showAlert("로그인에 실패하였습니다."); // 실패 알림
      }
    );
  };

  return (
    <>
      <Title size="large">로그인</Title>
      <SignupStyle> {/* 스타일 컴포넌트 이름은 SignupStyle이지만 Login 전용 스타일로 분리하는 게 좋음 */}
        <form onSubmit={handleSubmit(onSubmit)}>
          <fieldset>
            <InputText
              placeholder="이메일"
              inputType="email"
              {...register("email", { required: true })} // 이메일 입력값 관리 + 필수값 설정
            />
            {errors.email && <p className="error-text">이메일을 입력하세요</p>}
          </fieldset>
          <fieldset>
            <InputText
              placeholder="비밀번호"
              inputType="password"
              {...register("password", { required: true })} // 비밀번호 입력값 관리 + 필수값 설정
            />
            {errors.password && <p className="error-text">비밀번호를 입력하세요</p>}
          </fieldset>
          <fieldset>
            <Button type="submit" size="medium" scheme="primary">
              로그인
            </Button>
          </fieldset>
          <div className="info">
            <Link to="/reset">비밀번호 초기화</Link> {/* 비밀번호 초기화 페이지로 이동 */}
          </div>
        </form>
      </SignupStyle>
    </>
  );
};

export default Login;

 

  • Login.tsx는 React Hook Form으로 이메일과 비밀번호 입력을 받고, 로그인 API를 호출하는 컴포넌트다.
  • 로그인 성공 시 Zustand의 storeLogin을 통해 토큰을 저장하고 로그인 상태를 업데이트한다.
  • 이후 알림을 띄우고 메인 페이지(/)로 이동하며, 실패 시 실패 알림을 표시한다.
//Header.tsx

import { styled } from "styled-components";
import ThemeSwicher from "../header/ThemeSwicher";
import logo from "../../assets/images/logo.png";
import { FaSignInAlt, FaRegUser } from "react-icons/fa";
import { Link } from "react-router-dom";
import { useEffect, useState } from "react";
import { Category } from "../../models/category.model";
import { fetchCategory } from "../../api/category.api";
import { useCategory } from "../../hooks/useCategory";
import { useAuthStore } from "../../store/authStore";

// Header 컴포넌트: 상단 로고, 카테고리, 로그인/회원가입, 장바구니 표시

function Header() {
  const { category } = useCategory();
  const { isLoggedIn, storeLogout, storeLogin } = useAuthStore();

  // =================== [테스트용 코드 - 개발 후 삭제 요망] ===================
  // 페이지 로드 시 강제로 로그인 상태로 만듦 (백엔드 없이 UI 확인용)
  useEffect(() => {
    storeLogin("test-token");
  }, []);
  // =================== [테스트용 코드 끝] ===================

  return (
    <HeaderStyle>
      <h1 className="logo">
        <Link to="/">
          <img src={logo} alt="book store" />
        </Link>
      </h1>
      <nav className="category">
        <ul>
          {category.map((item) => (
            <li key={item.id}>
              <Link to={item.id === null ? "/books" : `/books?category_id=${item.id}`}>
                {item.name}
              </Link>
            </li>
          ))}
        </ul>
      </nav>
      <nav className="auth">
        {isLoggedIn && (
          <ul>
            <li>
              <Link to="/cart">장바구니</Link>
            </li>
            <li>
              <Link to="/orderlist">주문 내역</Link>
            </li>
            <li>
              <button onClick={storeLogout}>로그아웃</button>
            </li>
          </ul>
        )}
        {!isLoggedIn && (
          <ul>
            <li>
              <Link to="/login">
                <FaSignInAlt />
                로그인
              </Link>
            </li>
            <li>
              <Link to="/signup">
                <FaRegUser />
                회원가입
              </Link>
            </li>
            {/* =================== [테스트용 임시 로그인 버튼] =================== */}
            <li>
              <button onClick={() => storeLogin("test-token")}>
                임시 로그인 (테스트용)
              </button>
            </li>
            {/* =================== [테스트용 코드 끝] =================== */}
          </ul>
        )}
      </nav>
    </HeaderStyle>
  );
}

// Header 스타일 정의 (styled-components)
const HeaderStyle = styled.header`
  width: 100%;
  margin: 0 auto;
  max-width: ${({ theme }) => theme.layout.width.large};
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 20px 0;
  border-bottom: 1px solid ${({ theme }) => theme.color.background};

  .logo img {
    width: 200px;
  }

  .category ul {
    display: flex;
    gap: 32px;
    li a {
      font-size: 1.5rem;
      font-weight: 600;
      text-decoration: none;
      color: ${({ theme }) => theme.color.text};
      &:hover {
        color: ${({ theme }) => theme.color.primary};
      }
    }
  }

  .auth 
  ul {
    display: flex;
    gap: 16px;
    li {
    a, button{
      font-size: 1.5rem;
      font-weight: 600;
      text-decoration: none;
      display: flex;
      align-items: center;
      line-height: 1;
      background: none;
      border: 0;
      curser: pointer;
      svg {
        margin-right: 6px;
      }
    }
  }
`;

export default Header;

상단 네비게이션 바를 구성하며, 로고, 카테고리 메뉴, 로그인/회원가입, 장바구니, 주문 내역, 로그아웃 버튼 등을 표시

백엔드와 연동이 안되어(백엔드 파일 없음) 임시 로그인 코드 구현**

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/12   »
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 31
글 보관함