diff --git a/src/api/apis/mainFetch.ts b/src/api/apis/mainFetch.ts index 301d9d5..19f098c 100644 --- a/src/api/apis/mainFetch.ts +++ b/src/api/apis/mainFetch.ts @@ -48,10 +48,5 @@ export const mainfetch = async ( response = await fetch(url, { ...fetchOptions, headers }); } - if (!response.ok) { - const errorText = await response.text(); - throw new Error(`Error: ${response.status} - ${errorText}`); - } - return response; }; diff --git a/src/app/bookmark/components/bookMarkMain.tsx b/src/app/bookmark/components/bookMarkMain.tsx index d295add..09247e6 100644 --- a/src/app/bookmark/components/bookMarkMain.tsx +++ b/src/app/bookmark/components/bookMarkMain.tsx @@ -1,6 +1,6 @@ "use client"; import { mainfetch } from "@/src/api/apis/mainFetch"; -import { NoHoverButton } from "@/src/components/elements/styledElements"; +import { MiddleBoxColumn, NoHoverButton } from "@/src/components/elements/styledElements"; import { globalTheme } from "@/src/components/globalStyle"; import useBookmarks from "@/src/hooks/useBookmarks"; import useCertificateInfo from "@/src/hooks/useCertificateInfo"; @@ -11,8 +11,9 @@ import ExamChoice from "./examChoice"; import BookmarkProblemList from "./problemList"; import SubjectChoice from "./subjectChoice"; +const MAX_SELECTED_PROBLEMS = 100; + const BookMarkMain = () => { - const { certificateInfo, loading, error } = useCertificateInfo(); const [selectedExam, setSelectedExam] = useState("전체 회차"); const [problems, setProblems] = useState([]); const [selectedProblems, setSelectedProblems] = useState([]); @@ -22,7 +23,7 @@ const BookMarkMain = () => { const [selectedSubjectsId, setSelectedSubjectsId] = useState([]); const [page, setPage] = useState(0); const [isProcessing, setIsProcessing] = useState(false); - const { bookmarkedProblems, totalPage } = useBookmarks({ + const { bookmarkedProblems, totalPage, isCertified, certificateInfo, loading } = useBookmarks({ selectedExamId, selectedSubjectsId, page, @@ -31,20 +32,64 @@ const BookMarkMain = () => { setisModalOpen(prev => !prev); }; - const gotoStudyMode = () => { - // 공부 모드로 이동하는 함수 + const gotoStudyMode = async () => { + if (selectedProblems.length === 0) { + return; + } + const getProblmes = async () => { + const problems = await mainfetch( + "/problems/set/query", + { + method: "POST", + body: { + problemIds: selectedProblems, + }, + }, + true + ); + if (!problems.ok) { + new Error("문제를 불러오는데 실패했습니다."); + } + const data = await problems.json(); + localStorage.setItem("bookmarkProblems", JSON.stringify(data)); + }; + await getProblmes(); + const path = `/study/bookmark`; + window.location.href = path; }; - const gotoExamMode = () => { - // 시험 모드로 이동하는 함수 + const gotoExamMode = async () => { + if (selectedProblems.length === 0) { + return; + } + const getProblmes = async () => { + const problems = await mainfetch( + "/problems/set/query", + { + method: "POST", + body: { + problemIds: selectedProblems, + }, + }, + true + ); + if (!problems.ok) { + new Error("문제를 불러오는데 실패했습니다."); + } + const data = await problems.json(); + localStorage.setItem("bookmarkProblems", JSON.stringify(data)); + }; + await getProblmes(); + const path = `/exam/bookmark`; + window.location.href = path; }; - useEffect(() => { setProblems(bookmarkedProblems); }, [bookmarkedProblems]); + const selectProblem = (problemId: number) => { if (selectedProblems.includes(problemId)) { setSelectedProblems(selectedProblems.filter(id => id !== problemId)); - } else { + } else if (selectedProblems.length < MAX_SELECTED_PROBLEMS) { setSelectedProblems([...selectedProblems, problemId]); } }; @@ -84,7 +129,7 @@ const BookMarkMain = () => { const selectAllProblems = () => { const allProblems = problems.map(problem => problem.problemId); - setSelectedProblems(allProblems); + setSelectedProblems(allProblems.slice(0, MAX_SELECTED_PROBLEMS)); }; const deselectAllProblems = () => { @@ -140,6 +185,37 @@ const BookMarkMain = () => { return
로딩중...
; } + if (!isCertified) { + window.location.href = "/mypage"; + return ( + + + + 먼저 자격증을 선택해주세요 + + + + ); + } + return ( { borderLeft: "1px solid var(--c-gray2)", }} > - - + + 북마크 { const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down(960)); + useEffect(() => { + localStorage.setItem("focusTap", "북마크"); + }); return ( <> diff --git a/src/app/bookmark/components/mobileBookMarkMain.tsx b/src/app/bookmark/components/mobileBookMarkMain.tsx index 2c6b1d4..70aa318 100644 --- a/src/app/bookmark/components/mobileBookMarkMain.tsx +++ b/src/app/bookmark/components/mobileBookMarkMain.tsx @@ -1,24 +1,19 @@ "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, - Collapse, - Pagination, - SelectChangeEvent, - ThemeProvider, - Typography, -} from "@mui/material"; +import { Box, Button, Collapse, SelectChangeEvent, ThemeProvider, Typography } from "@mui/material"; import { useEffect, useState } from "react"; import BookMarkModal from "./bookmarkModal"; +import BookMarkSlider from "./bookMarkSlider"; import ExamChoice from "./examChoice"; import BookmarkProblemList from "./problemList"; import SubjectChoice from "./subjectChoice"; -import BookMarkSlider from "./bookMarkSlider"; -import { globalTheme } from "@/src/components/globalStyle"; -import { NoHoverButton } from "@/src/components/elements/styledElements"; -import useBookmarks from "@/src/hooks/useBookmarks"; -import { mainfetch } from "@/src/api/apis/mainFetch"; + +const MAX_SELECTED_PROBLEMS = 100; + const MobileBookMarkMain = () => { const [isSliderOpen, setIsSliderOpen] = useState(false); const handleSliderOpen = () => { @@ -43,11 +38,55 @@ const MobileBookMarkMain = () => { setisModalOpen(prev => !prev); }; - const gotoStudyMode = () => { - // 공부 모드로 이동하는 함수 + const gotoStudyMode = async () => { + if (selectedProblems.length === 0) { + return; + } + const getProblmes = async () => { + const problems = await mainfetch( + "/problems/set/query", + { + method: "POST", + body: { + problemIds: selectedProblems, + }, + }, + true + ); + if (!problems.ok) { + new Error("문제를 불러오는데 실패했습니다."); + } + const data = await problems.json(); + localStorage.setItem("bookmarkProblems", JSON.stringify(data)); + }; + await getProblmes(); + const path = `/study/bookmark`; + window.location.href = path; }; - const gotoExamMode = () => { - // 시험 모드로 이동하는 함수 + const gotoExamMode = async () => { + if (selectedProblems.length === 0) { + return; + } + const getProblmes = async () => { + const problems = await mainfetch( + "/problems/set/query", + { + method: "POST", + body: { + problemIds: selectedProblems, + }, + }, + true + ); + if (!problems.ok) { + new Error("문제를 불러오는데 실패했습니다."); + } + const data = await problems.json(); + localStorage.setItem("bookmarkProblems", JSON.stringify(data)); + }; + await getProblmes(); + const path = `/exam/bookmark`; + window.location.href = path; }; useEffect(() => { @@ -56,7 +95,7 @@ const MobileBookMarkMain = () => { const selectProblem = (problemId: number) => { if (selectedProblems.includes(problemId)) { setSelectedProblems(selectedProblems.filter(id => id !== problemId)); - } else { + } else if (selectedProblems.length < MAX_SELECTED_PROBLEMS) { setSelectedProblems([...selectedProblems, problemId]); } }; @@ -96,7 +135,7 @@ const MobileBookMarkMain = () => { const selectAllProblems = () => { const allProblems = problems.map(problem => problem.problemId); - setSelectedProblems(allProblems); + setSelectedProblems(allProblems.slice(0, MAX_SELECTED_PROBLEMS)); }; const deselectAllProblems = () => { @@ -194,8 +233,6 @@ const MobileBookMarkMain = () => { onClick={selectAllProblems} sx={{ mr: 1, - borderRadius: "40px", - padding: "4px 12px", }} > { 전체 선택 - + = ({ = ({ > {problems.map(problem => ( - - + - selectProblem(problem.problemId)} > - selectProblem(problem.problemId)} - > - + + - - - {problem.examInfo.description} ({problem.subjectInfo.name}) - - ( - + {problem.examInfo.description} ({problem.subjectInfo.name}) + + ( + + - - {content.children} - - - ), - img: ({ node, ...content }) => <>, - }} - > - {problem.description} - - + {content.children} + + + ), + img: ({ node, ...content }) => <>, + br: ({ node, ...content }) => <>, + }} + > + {problem.description} + + - { - handleBookmark(problem.problemId); - }} - > - {problem.isBookmark ? ( - - ) : ( - - )} - - - + { + handleBookmark(problem.problemId); + }} + > + {problem.isBookmark ? ( + + ) : ( + + )} + + ))} diff --git a/src/app/exam/[problems]/page.tsx b/src/app/exam/[problems]/page.tsx index 83b037f..556893a 100644 --- a/src/app/exam/[problems]/page.tsx +++ b/src/app/exam/[problems]/page.tsx @@ -9,7 +9,7 @@ const ExamHeader = dynamic(() => import("@/src/app/exam/components/examHeader"), loading: () =>

Header Loading

, }); -const ExamPage = async () => { +const ExamPage = () => { const { getProblems, certificateInfo, loading, error } = useProblems(); if (loading) { return
로딩중...
; diff --git a/src/app/exam/components/examFooterUI.tsx b/src/app/exam/components/examFooterUI.tsx index a89c9ba..594da71 100644 --- a/src/app/exam/components/examFooterUI.tsx +++ b/src/app/exam/components/examFooterUI.tsx @@ -6,7 +6,7 @@ interface ExamFooterUIProps { problems: any; prevProblem: () => void; nextProblem: () => void; - isMd: boolean; + isSm: boolean; } const ExamFooterUI: React.FC = ({ handleOmrModal, @@ -14,7 +14,7 @@ const ExamFooterUI: React.FC = ({ problems, prevProblem, nextProblem, - isMd, + isSm, }) => { return ( = ({ sx={{ width: "100%", display: "flex", - justifyContent: isMd ? "space-between" : "center", + justifyContent: isSm ? "space-between" : "center", alignItems: "center", flexDirection: "row", - paddingX: { - xs: "25px", - md: "25px", - }, + paddingX: "25px", }} > - {isMd && ( + {isSm && ( + ); +}; +interface NoHoverTouchButtonProps { + sx?: object; + children: React.ReactNode; + onClick?: () => any; + href?: string; +} diff --git a/src/components/elements/styledElements.tsx b/src/components/elements/styledElements.tsx index 23d9505..0bb3f38 100644 --- a/src/components/elements/styledElements.tsx +++ b/src/components/elements/styledElements.tsx @@ -22,3 +22,32 @@ export const MiddleBoxRow = styled(Box)({ justifyContent: "center", width: "100%", }); + +interface NoHoverTouchButtonProps { + sx?: object; + children: React.ReactNode; + onClick?: () => any; + href?: string; +} +export const NoHoverTouchButton: React.FC = ({ + sx, + children, + onClick, + href, +}) => { + return ( + + ); +}; diff --git a/src/components/scrollAppbar.tsx b/src/components/scrollAppbar.tsx index 7d7a0cf..6a12bde 100644 --- a/src/components/scrollAppbar.tsx +++ b/src/components/scrollAppbar.tsx @@ -58,8 +58,9 @@ const ScrollAppbar = ({ isScroll }: { isScroll?: number }) => { position="fixed" sx={{ paddingX: { - xs: 2, - md: 6, + sm: "25px", + md: "25px", + lg: "0px", }, boxShadow: isScroll && isScroll > 0 ? "rgba(0, 0, 0, 0.2) 0px 0px 14px" : "none", width: "100%", @@ -70,6 +71,7 @@ const ScrollAppbar = ({ isScroll }: { isScroll?: number }) => { minHeight: "64px", backgroundColor: backgroundColor, transition: "all 0.3s", + boxSizing: "border-box", }} > { @@ -8,7 +9,23 @@ const useAppbarState = () => { useEffect(() => { const accessToken = localStorage.getItem("accessToken"); const refreshToken = localStorage.getItem("refreshToken"); - const focusTap = localStorage.getItem("focusTap"); + const path = window.location.pathname; + let focusTap; + if (path.includes("bookmark")) { + focusTap = "북마크"; + } else if (path.includes("search")) { + focusTap = "검색"; + } else if (path.includes("mypage")) { + focusTap = "마이페이지"; + } else if (path.includes("login")) { + focusTap = "로그인"; + } else if (path.includes("learning") || path.includes("study") || path.includes("exam")) { + focusTap = "문제풀이"; + } else if (path.includes("assistant")) { + focusTap = "학습비서"; + } else { + focusTap = "메인페이지"; + } if (focusTap) { setFocusTap(focusTap); diff --git a/src/hooks/useBookmarks.ts b/src/hooks/useBookmarks.ts index 7036a10..8823cf9 100644 --- a/src/hooks/useBookmarks.ts +++ b/src/hooks/useBookmarks.ts @@ -7,32 +7,77 @@ interface BookMarkProblemsProps { page: number; } const useBookmarks = (props: BookMarkProblemsProps) => { + const [certificateInfo, setCertificateInfo] = useState(); const [bookmarkedProblems, setBookmarkedProblems] = useState([]); const [totalPage, setTotalPage] = useState(0); + const [isCertified, setIsCertified] = useState(true); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + useEffect(() => { - const fetchBookmarks = async (examId: number, subjectIds: number[], page: number) => { - let path = ""; - if (subjectIds.length === 0) { + const fetchCertificateInfo = async () => { + try { + const response = await mainfetch( + "/members/myinfo/certificates", + { method: "GET" }, + true + ); + const data = await response.json(); + const certificateId = data.certificateId; + + const certificateResponse = await mainfetch( + `/certificates/${certificateId}`, + { method: "GET" }, + false + ); + const certificateData = await certificateResponse.json(); + certificateData.exams.splice(0, 0, { examId: 0, description: "전체 회차" }); + setCertificateInfo(certificateData); + } catch (err: any) { + setError(err.message); + setIsCertified(false); + } finally { + setLoading(false); + } + }; + + fetchCertificateInfo(); + }, []); + + useEffect(() => { + const fetchBookmarks = async () => { + if (props.selectedSubjectsId.length === 0) { setBookmarkedProblems([]); return; } - if (examId == 0) { - path = `/problems/bookmarked?&subject-id=${subjectIds}&page=${page}`; - } else { - path = `/problems/bookmarked?exam-id=${examId}&subject-id=${subjectIds}&page=${page}`; - } - const response = await mainfetch(path, { method: "GET" }, true); - if (response.ok) { - const data = await response.json(); - setBookmarkedProblems(data.problems); - setTotalPage(data.totalPage); - } else { - new Error("Failed to fetch bookmarks"); + + const path = + props.selectedExamId === 0 + ? `/problems/bookmarked?subject-id=${props.selectedSubjectsId}&page=${props.page}` + : `/problems/bookmarked?exam-id=${props.selectedExamId}&subject-id=${props.selectedSubjectsId}&page=${props.page}`; + + try { + const response = await mainfetch(path, { method: "GET" }, true); + if (response.ok) { + const data = await response.json(); + setBookmarkedProblems(data.problems); + setTotalPage(data.totalPage); + } else { + const data = await response.json(); + if (data.errorCode === "MEM_002") { + setIsCertified(false); + } + } + } catch (err: any) { + setError(err.message); } }; - fetchBookmarks(props.selectedExamId, props.selectedSubjectsId, props.page); - }, [props.selectedExamId, props.selectedSubjectsId, props.page]); - return { bookmarkedProblems, totalPage }; + + if (!loading) { + fetchBookmarks(); + } + }, [props.selectedExamId, props.selectedSubjectsId, props.page, loading]); + return { bookmarkedProblems, totalPage, isCertified, certificateInfo, loading }; }; export default useBookmarks; diff --git a/src/hooks/useCertificateInfo.ts b/src/hooks/useCertificateInfo.ts index a1170e4..f47bfc8 100644 --- a/src/hooks/useCertificateInfo.ts +++ b/src/hooks/useCertificateInfo.ts @@ -17,7 +17,7 @@ const useCertificateInfo = () => { false ); const data = await response.json(); - data.exams.splice(0, 0, { examId: "0", description: "전체 회차" }); + data.exams.splice(0, 0, { examId: 0, description: "전체 회차" }); setCertificateInfo(data); } catch (err: any) { setError(err.message); diff --git a/src/hooks/useProblems.tsx b/src/hooks/useProblems.tsx index d2bc8ad..2f0ef73 100644 --- a/src/hooks/useProblems.tsx +++ b/src/hooks/useProblems.tsx @@ -9,30 +9,50 @@ const useProblems = () => { const [error, setError] = useState(null); const path = usePathname().split("/exam/")[1] ?? usePathname().split("/study/")[1]; useEffect(() => { - const fetchProblems = async () => { - try { - const response = await mainfetch("/problems/set?" + path, { method: "GET" }, false); - const data = await response.json(); - // 선택 정답을 추가한 데이터 - const newProblems = data.problems.map((problem: Problem, index: number) => { - return { - ...problem, - chooseNumber: -1, - viewSolution: false, - viewTheory: false, - problemNumber: index + 1, - }; - }); - setgetProblems(newProblems); - setCertificateInfo(data.certificateInfo); - } catch (err: any) { - setError(err.message); - } finally { - setLoading(false); + if (path.includes("bookmark")) { + const problems = localStorage.getItem("bookmarkProblems"); + if (!problems) { + setError("Error"); } - }; + const getBookmarkProblems = problems ? JSON.parse(problems) : []; + const newProblems = getBookmarkProblems.problems.map((problem: Problem, index: number) => { + return { + ...problem, + chooseNumber: -1, + viewSolution: false, + viewTheory: false, + problemNumber: index + 1, + }; + }); + setgetProblems(newProblems); + setCertificateInfo(getBookmarkProblems.certificateInfo); + setLoading(false); + } else { + const fetchProblems = async () => { + try { + const response = await mainfetch("/problems/set?" + path, { method: "GET" }, false); + const data = await response.json(); + // 선택 정답을 추가한 데이터 + const newProblems = data.problems.map((problem: Problem, index: number) => { + return { + ...problem, + chooseNumber: -1, + viewSolution: false, + viewTheory: false, + problemNumber: index + 1, + }; + }); + setgetProblems(newProblems); + setCertificateInfo(data.certificateInfo); + } catch (err: any) { + setError(err.message); + } finally { + setLoading(false); + } + }; - fetchProblems(); + fetchProblems(); + } }, []); return { getProblems, certificateInfo, loading, error };