diff --git a/src/api/apis/handleBookmark.ts b/src/api/apis/handleBookmark.ts new file mode 100644 index 0000000..0145d1a --- /dev/null +++ b/src/api/apis/handleBookmark.ts @@ -0,0 +1,63 @@ +import { mainfetch } from "./mainFetch"; + +const handleBookmarkModule = async ( + targetProblem: Problem | ProblemViewType | BookMarkProblem | ProblemDetailType, + isProcessing: boolean, + setIsProcessing: React.Dispatch>, + callbackArray?: React.Dispatch>, + callbackObject?: React.Dispatch> +) => { + if (isProcessing) return; + + setIsProcessing(true); + if (callbackArray) { + callbackArray((problems: any) => { + return problems.map((problem: any) => + problem.problemId === targetProblem.problemId + ? { ...problem, isBookmark: !targetProblem.isBookmark } + : problem + ); + }); + } else if (callbackObject) { + callbackObject((targetProblem: any) => { + return { ...targetProblem, isBookmark: !targetProblem.isBookmark }; + }); + } + try { + const method = targetProblem.isBookmark ? "DELETE" : "POST"; + const endpoint = "/bookmarks"; + + const res = await mainfetch( + endpoint, + { + method, + body: { + problemId: targetProblem.problemId, + }, + }, + true + ); + + if (!res.ok) { + if (callbackArray) { + callbackArray((problems: any) => { + const handledProblems = problems.map((problem: any) => + problem.problemId === targetProblem.problemId + ? { ...problem, isBookmark: targetProblem.isBookmark } + : problem + ); + return handledProblems; + }); + } else if (callbackObject) { + callbackObject((targetProblem: any) => { + return { ...targetProblem, isBookmark: targetProblem.isBookmark }; + }); + } + } + } catch (error) { + } finally { + setIsProcessing(false); // 처리 완료 + } +}; + +export default handleBookmarkModule; diff --git a/src/api/apis/interceptor.ts b/src/api/apis/interceptor.ts index 2599162..aaff2bf 100644 --- a/src/api/apis/interceptor.ts +++ b/src/api/apis/interceptor.ts @@ -10,7 +10,7 @@ export const refreshTokenInterceptor = async () => { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, }, - body: refreshToken, + body: JSON.stringify({ refreshToken }), }) .then(res => res.json()) .then(data => data.accessToken) diff --git a/src/api/types/bookmark.ts b/src/api/types/bookmark.ts index a4ccd27..6cdb294 100644 --- a/src/api/types/bookmark.ts +++ b/src/api/types/bookmark.ts @@ -5,3 +5,11 @@ interface BookMarkProblem { isBookmark: boolean; description: string; } + +interface handleBookmarkProps { + targetProblem: Problem; + isProcessing: boolean; + setIsProcessing: React.Dispatch>; + callbackArray?: React.Dispatch>; + callbackObject?: React.Dispatch>; +} diff --git a/src/app/bookmark/components/bookMarkMain.tsx b/src/app/bookmark/components/bookMarkMain.tsx index 09247e6..e7c7b90 100644 --- a/src/app/bookmark/components/bookMarkMain.tsx +++ b/src/app/bookmark/components/bookMarkMain.tsx @@ -5,11 +5,12 @@ 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 { useCallback, useEffect, useState } from "react"; import BookMarkModal from "./bookmarkModal"; import ExamChoice from "./examChoice"; import BookmarkProblemList from "./problemList"; import SubjectChoice from "./subjectChoice"; +import handleBookmarkModule from "@/src/api/apis/handleBookmark"; const MAX_SELECTED_PROBLEMS = 100; @@ -94,38 +95,12 @@ const BookMarkMain = () => { } }; - 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 handleBookmark = useCallback( + (problem: BookMarkProblem) => { + handleBookmarkModule(problem, isProcessing, setIsProcessing, setProblems); + }, + [isProcessing, setIsProcessing, setProblems] + ); const selectAllProblems = () => { const allProblems = problems.map(problem => problem.problemId); diff --git a/src/app/bookmark/components/examChoice.tsx b/src/app/bookmark/components/examChoice.tsx index 6f122f5..5204867 100644 --- a/src/app/bookmark/components/examChoice.tsx +++ b/src/app/bookmark/components/examChoice.tsx @@ -2,7 +2,6 @@ import { globalTheme } from "@/src/components/globalStyle"; import { Box, FormControl, - InputLabel, MenuItem, Select, SelectChangeEvent, @@ -58,11 +57,11 @@ const ExamChoice: React.FC = ({ > {exams.map((exam, index) => ( { } }; - 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 handleBookmark = useCallback( + (problem: BookMarkProblem) => { + handleBookmarkModule(problem, isProcessing, setIsProcessing, setProblems); + }, + [isProcessing, setIsProcessing, setProblems] + ); const selectAllProblems = () => { const allProblems = problems.map(problem => problem.problemId); diff --git a/src/app/bookmark/components/problemList.tsx b/src/app/bookmark/components/problemList.tsx index 5faa87e..52a1633 100644 --- a/src/app/bookmark/components/problemList.tsx +++ b/src/app/bookmark/components/problemList.tsx @@ -12,7 +12,7 @@ interface BookmarkProblemListProps { problems: BookMarkProblem[]; selectedProblems: number[]; selectProblem: (problemId: number) => void; - handleBookmark: (problemId: number) => void; + handleBookmark: (problem: BookMarkProblem) => void; totalPage: number; handleChangePage: (page: number) => void; } @@ -122,7 +122,7 @@ const BookmarkProblemList: React.FC = ({ { - handleBookmark(problem.problemId); + handleBookmark(problem); }} > {problem.isBookmark ? ( diff --git a/src/app/exam/components/examMainUI.tsx b/src/app/exam/components/examMainUI.tsx index 9e2d294..f4c0fd5 100644 --- a/src/app/exam/components/examMainUI.tsx +++ b/src/app/exam/components/examMainUI.tsx @@ -22,6 +22,7 @@ import SmallOmrUI from "../components/smallOmrUI"; import ExamFooterUI from "./examFooterUI"; import ExamInfoUI from "./examInfoUI"; import SubmitResultUI from "./SubmitResultUI"; +import handleBookmarkModule from "@/src/api/apis/handleBookmark"; interface ExamMainUIProps { getProblems: ProblemViewType[]; @@ -62,6 +63,11 @@ const ExamMainUI: React.FC = ({ setProblem(problems[problemNumber - 1]); }, [problemNumber, problems]); + useEffect(() => { + if (!problem) return; + setProblem(problems[problemNumber - 1]); + }, [problems]); + const nextProblem = () => { if (problemNumber === problems.length) { return; @@ -143,44 +149,10 @@ const ExamMainUI: React.FC = ({ }; const handleBookmark = useCallback( - async (problemId: number) => { - if (isProcessing) return; - if (localStorage.getItem("accessToken") === null) { - 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 - ); - - setProblems(prevProblems => - prevProblems.map(problem => - problem.problemId === problemId - ? { ...problem, isBookmark: !problem.isBookmark } - : problem - ) - ); - } catch (error) { - } finally { - setIsProcessing(false); - } + (problem: ProblemViewType) => { + handleBookmarkModule(problem, isProcessing, setIsProcessing, setProblems); }, - [isProcessing, problems, mainfetch] + [isProcessing, setIsProcessing, setProblems] ); const theme = useTheme(); diff --git a/src/app/exam/components/problemUI.tsx b/src/app/exam/components/problemUI.tsx index bda6352..07d907d 100644 --- a/src/app/exam/components/problemUI.tsx +++ b/src/app/exam/components/problemUI.tsx @@ -15,7 +15,7 @@ const ProblemUI: React.FC<{ problem: ProblemViewType; chooseAnswer: (number: number) => void; isSm: boolean; - handleBookmark: (problemId: number) => void; + handleBookmark: (problem: ProblemViewType) => void; }> = memo(({ problem, chooseAnswer, isSm, handleBookmark }) => { const [colors, setColors] = useState(["white", "white", "white", "white", "white"]); const changeColor = () => { @@ -59,7 +59,7 @@ const ProblemUI: React.FC<{ marginRight: 1, }} onClick={() => { - handleBookmark(problem.problemId); + handleBookmark(problem); }} > {problem.isBookmark ? ( diff --git a/src/app/problem/[certificate]/[problemid]/page.tsx b/src/app/problem/[certificate]/[problemid]/page.tsx index cc6fb13..6b8faf7 100644 --- a/src/app/problem/[certificate]/[problemid]/page.tsx +++ b/src/app/problem/[certificate]/[problemid]/page.tsx @@ -76,7 +76,7 @@ const ProblemMainPage = ({ > >} + setProblem={setProblem as Dispatch>} goToSimilarProblem={goToSimilarProblem} /> diff --git a/src/app/problem/organism/problemMain.tsx b/src/app/problem/organism/problemMain.tsx index bd207b9..3c96b61 100644 --- a/src/app/problem/organism/problemMain.tsx +++ b/src/app/problem/organism/problemMain.tsx @@ -1,13 +1,14 @@ import { mainfetch } from "@/src/api/apis/mainFetch"; import { Box, Grid, Tab, Tabs, Typography, useMediaQuery, useTheme } from "@mui/material"; -import { useState } from "react"; +import { useCallback, useState } from "react"; import SolutionUI from "../molecule/solutionUI"; import ProblemUI from "./problemUI"; import SimilarProblemList from "./similarProblemList"; +import handleBookmarkModule from "@/src/api/apis/handleBookmark"; interface ProblemMainProps { problem: ProblemDetailType; - setProblem: React.Dispatch>; + setProblem: React.Dispatch>; goToSimilarProblem: (problemId: number) => void; } const ProblemMain = ({ problem, setProblem, goToSimilarProblem }: ProblemMainProps) => { @@ -25,36 +26,18 @@ const ProblemMain = ({ problem, setProblem, goToSimilarProblem }: ProblemMainPro setProblem(prev => ({ ...prev, chooseNumber: answer })); }; - const handleBookmark = async (problemId: number) => { - if (isProcessing) return; - if (localStorage.getItem("accessToken") === null) { - return; - } - - setIsProcessing(true); - - const prevIsBookmark = problem.isBookmark; - try { - const method = problem.isBookmark ? "DELETE" : "POST"; - const endpoint = "/bookmarks"; - - await mainfetch( - endpoint, - { - method, - body: { - problemId, - }, - }, - true + const handleBookmark = useCallback( + (problem: BookMarkProblem) => { + handleBookmarkModule( + problem, + isProcessing, + setIsProcessing, + undefined, + setProblem ); - setProblem(prev => ({ ...prev, isBookmark: !prevIsBookmark })); - } catch (error) { - setProblem(prev => ({ ...prev, isBookmark: prevIsBookmark })); - } finally { - setIsProcessing(false); - } - }; + }, + [isProcessing, setIsProcessing, setProblem] + ); return ( >; + setProblem: React.Dispatch>; chooseAnswer: (number: number) => void; isSm: boolean; - handleBookmark: (problemId: number) => void; + handleBookmark: (problem: ProblemDetailType) => void; }> = memo(({ problem, chooseAnswer, isSm, handleBookmark }) => { const [colors, setColors] = useState(["white", "white", "white", "white", "white"]); const changeColor = () => { @@ -63,7 +63,7 @@ const ProblemUI: React.FC<{ marginRight: 1, }} onClick={() => { - handleBookmark(problem.problemId); + handleBookmark(problem); }} > {problem.isBookmark ? ( diff --git a/src/app/search/[problemId]/page.tsx b/src/app/search/[problemId]/page.tsx deleted file mode 100644 index 47135b7..0000000 --- a/src/app/search/[problemId]/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -const ProblemPage = () => { - return <>; -}; - -export default ProblemPage; diff --git a/src/app/search/hooks/useSearchTextHooks.tsx b/src/app/search/hooks/useSearchTextHooks.tsx index 4e2f50d..653022f 100644 --- a/src/app/search/hooks/useSearchTextHooks.tsx +++ b/src/app/search/hooks/useSearchTextHooks.tsx @@ -1,116 +1,54 @@ import { useCallback, useEffect, useState } from "react"; import { debounce } from "@/src/api/apis/debounce"; import { mainfetch } from "@/src/api/apis/mainFetch"; -const tem = [ - { - problemId: 1, - examInfo: { - examId: 1, - description: "2020년 1,2회차", - }, - subjectInfo: { - subjectId: 1, - sequence: 1, - name: "소프트웨어 설계", - }, - isBookmark: false, - description: - "검토회의 전에 요구사항 명세서를 미리 배포하여 사전 검토한 후 짧은 검토 회의를 통해 오류를 조기에 검출하는데 목적을 두는 요구 사항 검토 방법은?", - }, - { - problemId: 2, - examInfo: { - examId: 1, - description: "2020년 1,2회차", - }, - subjectInfo: { - subjectId: 1, - sequence: 1, - name: "소프트웨어 설계", - }, - isBookmark: true, - description: "코드 설계에서 일정한 일련번호를 부여하는 방식의 코드는?", - }, - { - problemId: 3, - examInfo: { - examId: 1, - description: "2020년 1,2회차", - }, - subjectInfo: { - subjectId: 1, - sequence: 1, - name: "소프트웨어 설계", - }, - isBookmark: false, - description: "객체지향 프로그램에서 데이터를 추상화하는 단위는?", - }, -]; +import useCertificates from "@/src/hooks/useCertificates"; +import { SelectChangeEvent } from "@mui/material"; +import handleBookmarkModule from "@/src/api/apis/handleBookmark"; const useSearchTextHooks = () => { const [text, setText] = useState(""); - const [searchResults, setSearchResults] = useState(tem); + const [searchResults, setSearchResults] = useState([]); const [isLastResult, setIsLastResult] = useState(false); - const [isBookmarkLoading, setIsBookmarkLoading] = useState(false); + const [isProcessing, setIsProcessing] = useState(false); const [isLoading, setIsLoading] = useState(false); + const { certificates } = useCertificates(); + const [selectedCertificate, setSelectedCertificate] = useState(); + useEffect(() => { + if (certificates.length > 0) { + setSelectedCertificate(certificates[0]); + } + }, [certificates]); + + const handleCertificateSelect = (event: SelectChangeEvent) => { + setSelectedCertificate(certificates.find(cert => cert.name === event.target.value)!); + }; const handleChangeText = (event: React.ChangeEvent) => { setText(event.target.value); }; const getMoreData = useCallback(() => { if (searchResults.length === 0 || isLastResult) return; - setSearchResults(prevResults => [...prevResults, ...tem]); - // getData(text, searchResults[searchResults.length - 1].problemId); + getData(text, searchResults[searchResults.length - 1].problemId); }, [searchResults, isLastResult]); const handleBookmark = useCallback( - async (problemId: number) => { - if (isBookmarkLoading) return; - let method: "DELETE" | "POST" = "POST"; - setIsBookmarkLoading(true); - - const newSearchResults = searchResults.map(problem => { - if (problem.problemId === problemId) { - method = problem.isBookmark === true ? "DELETE" : "POST"; - } - return problem.problemId === problemId - ? { ...problem, isBookmark: !problem.isBookmark } - : problem; - }); - setSearchResults(newSearchResults); - - try { - const response = await mainfetch( - "/bookmarks", - { - method, - body: { problemId: problemId }, - }, - true - ); - - if (!response.ok) { - throw new Error("북마크 요청 실패"); - } - } catch (error) { - setSearchResults(prevResults => - prevResults.map(problem => - problem.problemId === problemId - ? { ...problem, isBookmark: !problem.isBookmark } - : problem - ) - ); - } finally { - setIsBookmarkLoading(false); - } + (problem: BookMarkProblem) => { + handleBookmarkModule( + problem, + isProcessing, + setIsProcessing, + setSearchResults + ); }, - [isBookmarkLoading] + [isProcessing, setIsProcessing, setSearchResults] ); const getData = useCallback( async (searchText: string, lastId?: number) => { - if (isLoading) return; + if (isLoading || !selectedCertificate?.certificateId) return; setIsLoading(true); try { const response = await mainfetch( - `/problems/search?title=${searchText}&last-id=${lastId ? lastId : ""}`, + `/problems/search?title=${searchText}${ + lastId ? `&last-id=${lastId}` : "" + }&certificate-id=${selectedCertificate?.certificateId}`, { method: "GET", }, @@ -131,22 +69,31 @@ const useSearchTextHooks = () => { setIsLoading(false); } }, - [isLoading] + [isLoading, selectedCertificate] ); const debouncedSearch = useCallback( debounce(async (searchText: string, lastId?: number) => { getData(searchText); }, 300), - [] + [selectedCertificate] ); useEffect(() => { if (text) { debouncedSearch(text); } - }, [text, debouncedSearch]); + }, [text, debouncedSearch, selectedCertificate]); - return { text, handleChangeText, searchResults, handleBookmark, getMoreData }; + return { + text, + handleChangeText, + searchResults, + handleBookmark, + getMoreData, + handleCertificateSelect, + selectedCertificate, + certificates, + }; }; export default useSearchTextHooks; diff --git a/src/app/search/molecule/certificateSelect.tsx b/src/app/search/molecule/certificateSelect.tsx new file mode 100644 index 0000000..4f19577 --- /dev/null +++ b/src/app/search/molecule/certificateSelect.tsx @@ -0,0 +1,139 @@ +import { Box, FormControl, MenuItem, Select, SelectChangeEvent, Typography } from "@mui/material"; +import { useEffect, useRef, useState } from "react"; + +interface CertificateSelectProps { + certificates: CertificateType[]; + handleCertificateSelect: (event: SelectChangeEvent) => void; + selectedCertificate: CertificateType; +} + +const CertificateSelect = ({ + certificates, + handleCertificateSelect, + selectedCertificate, +}: CertificateSelectProps) => { + // 자격증 선택 박스 너비 조절 + const boxRef = useRef(null); + const [boxWidth, setBoxWidth] = useState(null); + + useEffect(() => { + if (boxRef.current) { + const resizeObserver = new ResizeObserver(entries => { + for (let entry of entries) { + setBoxWidth(entry.contentRect.width); + } + }); + resizeObserver.observe(boxRef.current); + + return () => resizeObserver.disconnect(); + } + }, []); + + return ( + + + + + + ); +}; +export default CertificateSelect; diff --git a/src/app/search/molecule/searchResultProblem.tsx b/src/app/search/molecule/searchResultProblem.tsx index 2b14b9b..fd693fc 100644 --- a/src/app/search/molecule/searchResultProblem.tsx +++ b/src/app/search/molecule/searchResultProblem.tsx @@ -8,7 +8,7 @@ import remarkMath from "remark-math"; interface SearchResultProblemProps { problem: BookMarkProblem; - handleBookmark: (problemId: number) => void; + handleBookmark: (problem: BookMarkProblem) => void; gotoDetailPage: (problemId: number) => void; } @@ -92,7 +92,7 @@ const SearchResultProblem = ({ { - handleBookmark(problem.problemId); + handleBookmark(problem); }} > {problem.isBookmark ? ( diff --git a/src/app/search/organism/searchResultUI.tsx b/src/app/search/organism/searchResultUI.tsx index a9d2839..5ca782e 100644 --- a/src/app/search/organism/searchResultUI.tsx +++ b/src/app/search/organism/searchResultUI.tsx @@ -6,8 +6,9 @@ import { useRouter } from "next/navigation"; interface SearchResultUIProps { text: string; searchResults: BookMarkProblem[]; - handleBookmark: (problemId: number) => void; + handleBookmark: (problem: BookMarkProblem) => void; getMoreData: () => void; + selectedCertificate: CertificateType; } const SearchResultUI = ({ @@ -15,6 +16,7 @@ const SearchResultUI = ({ searchResults, handleBookmark, getMoreData, + selectedCertificate, }: SearchResultUIProps) => { const observerRef = useRef(null); const targetRef = useRef(null); @@ -22,9 +24,9 @@ const SearchResultUI = ({ // todo : 검색하는 자격증 따라서 problemId 앞에 자격증 이름 붙이기 const gotoDetailPage = useCallback( (problemId: number) => { - router.push(`/problem/정보처리기사/${problemId}`); + router.push(`/problem/${selectedCertificate?.name}/${problemId}`); }, - [router] + [router, selectedCertificate] ); useEffect(() => { const options = { diff --git a/src/app/search/page.tsx b/src/app/search/page.tsx index 947d954..671f34e 100644 --- a/src/app/search/page.tsx +++ b/src/app/search/page.tsx @@ -2,15 +2,40 @@ import Appbar from "@/src/components/Appbar"; import Footer from "@/src/components/Footer"; import { globalTheme } from "@/src/components/globalStyle"; -import { Box, ThemeProvider, Typography } from "@mui/material"; +import useCertificates from "@/src/hooks/useCertificates"; +import { Box, CircularProgress, SelectChangeEvent, ThemeProvider, Typography } from "@mui/material"; +import { useEffect, useState } from "react"; import SearchInputBox from "./atom/searchInputBox"; import useSearchTextHooks from "./hooks/useSearchTextHooks"; +import CertificateSelect from "./molecule/certificateSelect"; import SearchResultUI from "./organism/searchResultUI"; -import { useRouter } from "next/navigation"; const SearchMainPage = () => { - const { text, handleChangeText, searchResults, handleBookmark, getMoreData } = - useSearchTextHooks(); + const { + text, + handleChangeText, + searchResults, + handleBookmark, + getMoreData, + handleCertificateSelect, + selectedCertificate, + certificates, + } = useSearchTextHooks(); + + if (!selectedCertificate) { + return ( + + + + ); + } return ( @@ -51,16 +76,42 @@ const SearchMainPage = () => { > 문제를 검색해보세요 - - + + + + {searchResults.length === 0 ? ( { searchResults={searchResults} handleBookmark={handleBookmark} getMoreData={getMoreData} + selectedCertificate={selectedCertificate} /> )} diff --git a/src/app/study/components/problemUI.tsx b/src/app/study/components/problemUI.tsx index 7eac9c6..df8eccc 100644 --- a/src/app/study/components/problemUI.tsx +++ b/src/app/study/components/problemUI.tsx @@ -4,20 +4,18 @@ import BookMarkLineIcon from "@/public/icons/bookmark-line.svg"; import SirenLineIcon from "@/public/icons/siren-line.svg"; import { Box, Container, Typography } from "@mui/material"; import { memo, useEffect, useState } from "react"; -import Markdown from "react-markdown"; -import rehypeKatex from "rehype-katex"; -import rehypeRaw from "rehype-raw"; -import remarkMath from "remark-math"; import ProblemChoiceUI from "./problemChoiceUI"; import ProblemMarkdown from "./problemMarkdown"; +import handleBookmark from "@/src/api/apis/handleBookmark"; const ProblemUI: React.FC<{ problem: ProblemViewType; chooseAnswer: (number: number) => void; isSm: boolean; - handleBookmark: (problemId: number) => void; + handleBookmark: (problem: ProblemViewType) => void; }> = memo(({ problem, chooseAnswer, isSm, handleBookmark }) => { const [colors, setColors] = useState(["white", "white", "white", "white", "white"]); + const [isProcessing, setIsProcessing] = useState(false); const changeColor = () => { if (problem.chooseNumber === -1) { setColors(["white", "white", "white", "white", "white"]); @@ -38,12 +36,6 @@ const ProblemUI: React.FC<{ }); } }; - /** - * @todo 북마크 기능 - */ - const bookmarking = () => { - problem.isBookmark = !problem.isBookmark; - }; /** * @todo 신고하기 기능 */ @@ -74,7 +66,7 @@ const ProblemUI: React.FC<{ marginRight: 1, }} onClick={() => { - handleBookmark(problem.problemId); + handleBookmark(problem); }} > {problem.isBookmark ? ( diff --git a/src/app/study/components/studyMainUI.tsx b/src/app/study/components/studyMainUI.tsx index 1c8231e..4659256 100644 --- a/src/app/study/components/studyMainUI.tsx +++ b/src/app/study/components/studyMainUI.tsx @@ -1,5 +1,6 @@ "use client"; +import handleBookmarkModule from "@/src/api/apis/handleBookmark"; import { mainfetch } from "@/src/api/apis/mainFetch"; import { globalTheme } from "@/src/components/globalStyle"; import { @@ -69,6 +70,11 @@ const StudyMainUI: React.FC = ({ setTabValue(0); }, [problemNumber, problems]); + useEffect(() => { + if (!problem) return; + setProblem(problems[problemNumber - 1]); + }, [problems]); + const nextProblem = () => { if (problemNumber === problems.length) { return; @@ -162,60 +168,11 @@ const StudyMainUI: React.FC = ({ }); }; - const handleViewTheory = () => { - setProblem(prev => { - if (!prev) return null; - return { ...prev, viewTheory: !prev.viewTheory }; - }); - setProblems(prev => { - return prev.map(problem => { - if (problem.problemId === problems[problemNumber - 1].problemId) { - return { ...problem, viewTheory: !problem.viewTheory }; - } - return problem; - }); - }); - }; - const handleBookmark = useCallback( - async (problemId: number) => { - if (isProcessing) return; - if (localStorage.getItem("accessToken") === null) { - 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 - ); - - setProblems(prevProblems => - prevProblems.map(problem => - problem.problemId === problemId - ? { ...problem, isBookmark: !problem.isBookmark } - : problem - ) - ); - } catch (error) { - } finally { - setIsProcessing(false); - } + (problem: ProblemViewType) => { + handleBookmarkModule(problem, isProcessing, setIsProcessing, setProblems); }, - [isProcessing, problems, mainfetch] + [isProcessing, setIsProcessing, setProblems] ); const theme = useTheme(); diff --git a/src/hooks/useCertificates.ts b/src/hooks/useCertificates.ts index 75b142a..e91359e 100644 --- a/src/hooks/useCertificates.ts +++ b/src/hooks/useCertificates.ts @@ -2,7 +2,7 @@ import { useState, useEffect } from "react"; import { mainfetch } from "../api/apis/mainFetch"; const useCertificates = () => { - const [certificates, setCertificates] = useState([]); + const [certificates, setCertificates] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -14,6 +14,7 @@ const useCertificates = () => { setCertificates(data); } catch (err: any) { setError(err.message); + throw new Error(err.message); } finally { setLoading(false); }