Skip to content

Commit

Permalink
Feat(API): 북마크, 로그아웃, 리프레시 API 연동 (#26)
Browse files Browse the repository at this point in the history
* Feat(API): 북마크, 로그아웃, 리프레시 API 연동

- 북마크 페이지, 공부모드, 시험모드에서 북마크를 할 수 있음
- 로그아웃 시 서버에도 로그아웃 요청을 보냄
- 현재 token이 있고 만료 되었을 때 자동으로 갱신을 요청함

* Fix: build error

- type 에러로 인한 build 에러 해결
  • Loading branch information
godzz733 authored Jul 29, 2024
1 parent de8ceb7 commit 7052357
Show file tree
Hide file tree
Showing 26 changed files with 523 additions and 366 deletions.
3 changes: 2 additions & 1 deletion src/api/apis/interceptor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export const refreshTokenInterceptor = async () => {
const url = "/auth/reissue";
const url = process.env.NEXT_PUBLIC_SERVER_URL + "/auth/reissue";
const accessToken = localStorage.getItem("accessToken");
const refreshToken = localStorage.getItem("refreshToken");
// todo : accessToken 또는 refreshToken 없을 경우 로그인 페이지로 이동
Expand All @@ -18,6 +18,7 @@ export const refreshTokenInterceptor = async () => {
if (!token) {
localStorage.removeItem("accessToken");
localStorage.removeItem("refreshToken");
window.location.href = "/login";
return new Error("토큰 재발급 실패");
}
localStorage.setItem("accessToken", token);
Expand Down
1 change: 1 addition & 0 deletions src/api/apis/mainFetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const mainfetch = async <T>(
const accessToken = localStorage.getItem("accessToken");
const refreshToken = localStorage.getItem("refreshToken");
if (!accessToken || !refreshToken) {
window.location.href = "/login";
throw new Error("Access token or refresh token is missing");
}
headers.Authorization = `Bearer ${accessToken}`;
Expand Down
40 changes: 0 additions & 40 deletions src/api/types/apis/problem.ts

This file was deleted.

4 changes: 2 additions & 2 deletions src/api/types/bookmark.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
interface BookMarkProblem {
problemId: string;
problemId: number;
examInfo: ExamInfo;
subject: Subject;
subjectInfo: Subject;
isBookmark: boolean;
description: string;
}
4 changes: 2 additions & 2 deletions src/api/types/certificate.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// 자격증 타입 정의
interface CertificateType {
certificateId: string;
certificateId: number;
name: string;
}

// 자격증 정보 타입 정의
interface CertificateInfo {
certificateId: string;
certificateId: number;
name: string;
exams: ExamInfo[];
subjects: Subject[];
Expand Down
8 changes: 4 additions & 4 deletions src/api/types/problem.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
interface ExamInfo {
examId: string;
examId: number;
description: string;
}

interface Subject {
subjectId: string;
subjectId: number;
sequence: number;
name: string;
}
Expand All @@ -14,9 +14,9 @@ interface Chocice {
}

interface Problem {
problemId: string;
problemId: number;
examInfo: ExamInfo;
subject: Subject;
subjectInfo: Subject;
isBookmark: boolean;
description: string;
choices: Chocice[];
Expand Down
143 changes: 71 additions & 72 deletions src/app/bookmark/components/bookMarkMain.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
"use client";
import { mainfetch } from "@/src/api/apis/mainFetch";
import { NoHoverButton } from "@/src/components/elements/styledElements";
import { globalTheme } from "@/src/components/globalStyle";
import useBookmarks from "@/src/hooks/useBookmarks";
import useCertificateInfo from "@/src/hooks/useCertificateInfo";
import { Box, Button, Grid, SelectChangeEvent, ThemeProvider, Typography } from "@mui/material";
import { useEffect, useState } from "react";
import BookMarkModal from "./bookmarkModal";
import ExamChoice from "./examChoice";
import BookmarkProblemList from "./problemList";
import SubjectChoice from "./subjectChoice";
import { globalTheme } from "@/src/components/globalStyle";
import { NoHoverButton } from "@/src/components/elements/styledElements";

const BookMarkMain = () => {
const { certificateInfo, loading, error } = useCertificateInfo();
const [selectedExam, setSelectedExam] = useState<string>("전체 회차");
const [problems, setProblems] = useState<BookMarkProblem[]>([]);
const [selectedProblems, setSelectedProblems] = useState<string[]>([]);
const [selectedProblems, setSelectedProblems] = useState<number[]>([]);
const [selectedSubjects, setSelectedSubjects] = useState<Subject[]>([]);
const [isModalOpen, setisModalOpen] = useState(false);

const [selectedExamId, setSelectedExamId] = useState<number>(0);
const [selectedSubjectsId, setSelectedSubjectsId] = useState<number[]>([]);
const [page, setPage] = useState<number>(0);
const [isProcessing, setIsProcessing] = useState(false);
const { bookmarkedProblems, totalPage } = useBookmarks({
selectedExamId,
selectedSubjectsId,
page,
});
const handleModalOpen = () => {
setisModalOpen(prev => !prev);
};
Expand All @@ -27,74 +37,49 @@ const BookMarkMain = () => {
const gotoExamMode = () => {
// 시험 모드로 이동하는 함수
};

useEffect(() => {
const t: BookMarkProblem[] = [
{
problemId: "1",
examInfo: {
examId: "1",
description: "2022년 1,2회",
},
subject: {
subjectId: "1",
sequence: 2,
name: "소프트웨어 설계",
},
isBookmark: true,
description: "UML 다이어그램 중 순차 다이어그램에 대한 설명으로 틀린 것은?",
},
{
problemId: "2",
examInfo: {
examId: "1",
description: "2022년 1,2회",
},
subject: {
subjectId: "1",
sequence: 2,
name: "소프트웨어 설계",
},
isBookmark: true,
description: "UML 다이어그램 중 순차 다이어그램에 대한 설명으로 틀린 것은?",
},
{
problemId: "3",
examInfo: {
examId: "1",
description: "2022년 1,2회",
},
subject: {
subjectId: "1",
sequence: 2,
name: "소프트웨어 설계",
},
isBookmark: true,
description: "UML 다이어그램 중 순차 다이어그램에 대한 설명으로 틀린 것은?",
},
];
setProblems(t);
}, []);
const selectProblem = (problemId: string) => {
setProblems(bookmarkedProblems);
}, [bookmarkedProblems]);
const selectProblem = (problemId: number) => {
if (selectedProblems.includes(problemId)) {
setSelectedProblems(selectedProblems.filter(id => id !== problemId));
} else {
setSelectedProblems([...selectedProblems, problemId]);
}
};
/**
*
* @param problemId
* 북마크 삭제 api 추가 예정
*/
const handleBookmark = (problemId: string) => {
// 북마크 삭제 api 추가 예정
const handledProblems = problems.map(problem => {
if (problem.problemId === problemId) {
return { ...problem, isBookmark: !problem.isBookmark };
}
return problem;
});
setProblems(handledProblems);

const handleBookmark = async (problemId: number) => {
if (isProcessing) return;

setIsProcessing(true);

try {
const targetProblem = problems.find(problem => problem.problemId === problemId);
if (!targetProblem) throw new Error("Problem not found");
const method = targetProblem.isBookmark ? "DELETE" : "POST";
const endpoint = "/bookmarks";

await mainfetch(
endpoint,
{
method,
body: {
problemId,
},
},
true
);

const handledProblems = problems.map(problem =>
problem.problemId === problemId ? { ...problem, isBookmark: !problem.isBookmark } : problem
);

setProblems(handledProblems);
} catch (error) {
} finally {
setIsProcessing(false); // 처리 완료
}
};

const selectAllProblems = () => {
Expand All @@ -108,35 +93,47 @@ const BookMarkMain = () => {

const handleExamChoice = (event: SelectChangeEvent) => {
setSelectedExam(event.target.value as string);
const examId = certificateInfo!.exams.find(
exam => exam.description === event.target.value
)!.examId;
setSelectedExamId(examId);
};

useEffect(() => {
if (certificateInfo === undefined) return;
const subjects = certificateInfo.subjects;
setSelectedSubjects(subjects);
setSelectedExam(certificateInfo.exams[0].description);
setSelectedSubjectsId(subjects.map(subject => subject.subjectId));
}, [certificateInfo]);

const handleSubjectChoice = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
setSelectedSubjects(prevSelectedSubjects => {
// subject.name이 value와 일치하는지 확인하는 함수
const isSelected = prevSelectedSubjects.some(subject => subject.name === value);
const getNewSelectedSubjects = () => {
const isSelected = selectedSubjects.some(subject => subject.name === value);

// 만약 isSelected가 true이면 해당 항목을 제거한 배열을 반환
if (isSelected) {
return prevSelectedSubjects.filter(subject => subject.name !== value);
return selectedSubjects.filter(subject => subject.name !== value);
} else {
// isSelected가 false이면 해당 항목을 추가한 배열을 반환
const selectedSubject = certificateInfo?.subjects.find(subject => subject.name === value);
if (selectedSubject) {
return [...prevSelectedSubjects, selectedSubject];
return [...selectedSubjects, selectedSubject];
} else {
// 만약 해당하는 subject.name을 찾지 못했을 경우 기존 배열을 반환
return prevSelectedSubjects;
return selectedSubjects;
}
}
});
};
const newSelectedSubjects = getNewSelectedSubjects();
setSelectedSubjects(newSelectedSubjects);
// const newSelectedSubjectsId = [];
setSelectedSubjectsId(newSelectedSubjects.map(subject => subject.subjectId));
};

const handleChangePage = (page: number) => {
setPage(page);
};

if (loading) {
Expand Down Expand Up @@ -269,6 +266,8 @@ const BookMarkMain = () => {
</Box>
</Box>
<BookmarkProblemList
totalPage={totalPage}
handleChangePage={handleChangePage}
problems={problems}
selectedProblems={selectedProblems}
selectProblem={selectProblem}
Expand Down
Loading

0 comments on commit 7052357

Please sign in to comment.