티스토리 뷰
지금 백엔드 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;
상단 네비게이션 바를 구성하며, 로고, 카테고리 메뉴, 로그인/회원가입, 장바구니, 주문 내역, 로그아웃 버튼 등을 표시
백엔드와 연동이 안되어(백엔드 파일 없음) 임시 로그인 코드 구현**
