From d91166999e9cfcf461ef63773c34959fdc8d498d Mon Sep 17 00:00:00 2001 From: ImxYJL Date: Thu, 14 Nov 2024 17:29:19 +0900 Subject: [PATCH 01/24] =?UTF-8?q?feat:=20beforeunload,=20visibilitychange?= =?UTF-8?q?=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=EA=B0=80=20=EC=9D=BC=EC=96=B4?= =?UTF-8?q?=EB=82=AC=EC=9D=84=20=EB=95=8C=20=EC=9E=91=EC=84=B1=ED=95=98?= =?UTF-8?q?=EB=8D=98=20=EB=A6=AC=EB=B7=B0=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=EB=93=A4=EC=9D=84=20=EC=A0=80=EC=9E=A5=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../form/components/CardForm/index.tsx | 69 ++++++++++++++++++- .../hooks/answers/useUpdateDefaultAnswers.ts | 17 +++-- .../form/hooks/useNavigateBlocker.ts | 13 ---- 3 files changed, 77 insertions(+), 22 deletions(-) diff --git a/frontend/src/pages/ReviewWritingPage/form/components/CardForm/index.tsx b/frontend/src/pages/ReviewWritingPage/form/components/CardForm/index.tsx index 158441881..2ecd0d5c9 100644 --- a/frontend/src/pages/ReviewWritingPage/form/components/CardForm/index.tsx +++ b/frontend/src/pages/ReviewWritingPage/form/components/CardForm/index.tsx @@ -1,5 +1,5 @@ import { useEffect } from 'react'; -import { useSetRecoilState } from 'recoil'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; import { useSearchParamAndQuery } from '@/hooks'; import { CARD_FORM_MODAL_KEY } from '@/pages/ReviewWritingPage/constants'; @@ -15,7 +15,7 @@ import useCardFormModal from '@/pages/ReviewWritingPage/modals/hooks/useCardForm import MobileProgressBar from '@/pages/ReviewWritingPage/progressBar/components/MobileProgressBar'; import ProgressBar from '@/pages/ReviewWritingPage/progressBar/components/ProgressBar'; import { CardSlider } from '@/pages/ReviewWritingPage/slider/components'; -import { reviewRequestCodeAtom } from '@/recoil'; +import { answerMapAtom, answerValidationMapAtom, reviewRequestCodeAtom, selectedCategoryAtom } from '@/recoil'; import { calculateParticle } from '@/utils'; import * as S from './styles'; @@ -25,6 +25,67 @@ const CardForm = () => { paramKey: 'reviewRequestCode', }); + // 작성했던 내용들을 저장하는 로직 + const selectedCategory = useRecoilValue(selectedCategoryAtom); + const answerMap = useRecoilValue(answerMapAtom); + const answerValidation = useRecoilValue(answerValidationMapAtom); + + const getCurrentSelectedCategory = () => { + if (selectedCategory && selectedCategory.length > 0) { + return selectedCategory; + } + }; + + const getAnswerValidation = () => { + if (answerValidation && answerValidation.size > 0) { + const plainObjectAnswers = Array.from(answerValidation.entries()); + return plainObjectAnswers.length > 0 ? plainObjectAnswers : null; + } + }; + + const getCurrentAnswers = () => { + if (answerMap) { + const plainObjectAnswers = Array.from(answerMap.entries()); + return plainObjectAnswers.length > 0 ? plainObjectAnswers : null; + } + }; + + const handleBeforeUnloadChange = () => { + const selectedCategories = getCurrentSelectedCategory(); + const answers = getCurrentAnswers(); + const answerValidations = getAnswerValidation(); + + // 빈 상태인지 확인 후 저장 + if (selectedCategories) { + localStorage.setItem(`selectedCategories_${reviewRequestCode}`, JSON.stringify(selectedCategories)); + } + if (answerValidations) { + localStorage.setItem(`answerValidations_${reviewRequestCode}`, JSON.stringify(answerValidations)); + } + if (answers) { + localStorage.setItem(`answers_${reviewRequestCode}`, JSON.stringify(answers)); + } + }; + + useEffect(() => { + // TODO: 라우터를 통한 이동에서도 동작할 수 있도록 하기 + + window.addEventListener('beforeunload', handleBeforeUnloadChange); + document.addEventListener('visibilitychange', handleBeforeUnloadChange); + // window.addEventListener('pagehide', handleBeforeUnloadChange); + + return () => { + window.removeEventListener('beforeunload', handleBeforeUnloadChange); + document.removeEventListener('visibilitychange', handleBeforeUnloadChange); + // window.removeEventListener('pagehide', handleBeforeUnloadChange); + }; + }, [reviewRequestCode, answerMap, selectedCategory, answerValidation]); + + //// 복원 로직 + const setAnswerMap = useSetRecoilState(answerMapAtom); // 답변 상태를 설정할 수 있게 해줌 + + ////////////////// 기존 로직 + const setReviewRequestCode = useSetRecoilState(reviewRequestCodeAtom); const { currentCardIndex, handleCurrentCardIndex } = useCurrentCardIndex(); @@ -57,7 +118,7 @@ const CardForm = () => { useEffect(() => { return () => { - // 페이지 나갈때 관련 recoil 상태 초기화 + // 페이지 나갈 때 관련 recoil 상태 초기화 resetFormRecoil(); }; }, []); @@ -95,6 +156,8 @@ const CardForm = () => { isOpen={isOpen} closeModal={closeModal} handleNavigateConfirmButtonClick={handleNavigateConfirmButtonClick} + handleRestoreButtonClick={() => {}} + // handleRestoreButtonClick={handleRestoreAnswers} /> ); diff --git a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useUpdateDefaultAnswers.ts b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useUpdateDefaultAnswers.ts index 1b6efae67..118513080 100644 --- a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useUpdateDefaultAnswers.ts +++ b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useUpdateDefaultAnswers.ts @@ -1,5 +1,5 @@ import { useEffect } from 'react'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import { answerMapAtom, answerValidationMapAtom, cardSectionListSelector } from '@/recoil'; import { ReviewWritingAnswer, ReviewWritingCardQuestion } from '@/types'; @@ -12,9 +12,10 @@ const DEFAULT_VALUE = { * cardSectionListSelector(=리뷰 작성 페이지에서 리뷰이가 작성해야하는 질문지)가 변경되었을때, 이에 맞추어서 답변(answerMap)과 답변들의 유효성 여부(answerValidationMap)을 변경하는 훅 */ const useUpdateDefaultAnswers = () => { - const cardSectionList = useRecoilValue(cardSectionListSelector); // NOTE : answerMap - 질문에 대한 답변들 , number : questionId const [answerMap, setAnswerMap] = useRecoilState(answerMapAtom); + + const cardSectionList = useRecoilValue(cardSectionListSelector); // NOTE : answerValidationMap -질문의 단볍들의 유효성 여부 ,number: questionId const [answerValidationMap, setAnswerValidationMap] = useRecoilState(answerValidationMapAtom); /* NOTE: 질문 변경 시, answerMap 변경 케이스 정리 @@ -101,10 +102,14 @@ const useUpdateDefaultAnswers = () => { }; useEffect(() => { - const { newAnswerMap, newAnswerValidationMap } = makeNewAnswerAndValidationMaps(); - setAnswerMap(newAnswerMap); - setAnswerValidationMap(newAnswerValidationMap); - }, [cardSectionList]); + // answerMap이 비어 있을 때만 작성 페이지 초기화 작업 수행 + // : 작성한 내용이 아예 없는 상황에서 새로고침할 때, 기존 답변을 저장한 로컬 스토리지가 초기화되는 것을 막기 위함 + if (answerMap?.size === 0) { + const { newAnswerMap, newAnswerValidationMap } = makeNewAnswerAndValidationMaps(); + setAnswerMap(newAnswerMap); + setAnswerValidationMap(newAnswerValidationMap); + } + }, [cardSectionList, setAnswerMap, setAnswerValidationMap]); }; export default useUpdateDefaultAnswers; diff --git a/frontend/src/pages/ReviewWritingPage/form/hooks/useNavigateBlocker.ts b/frontend/src/pages/ReviewWritingPage/form/hooks/useNavigateBlocker.ts index d3bc57ab5..84a0616bd 100644 --- a/frontend/src/pages/ReviewWritingPage/form/hooks/useNavigateBlocker.ts +++ b/frontend/src/pages/ReviewWritingPage/form/hooks/useNavigateBlocker.ts @@ -16,11 +16,6 @@ const useNavigateBlocker = ({ openNavigateConfirmModal }: UseNavigateBlockerProp return [...answerMap.values()].some((answer) => !!answer.selectedOptionIds?.length || !!answer.text?.length); }; - // 페이지 새로고침 및 닫기에 대한 처리: 브라우저 기본 alert 등장 - const handleNavigationBlock = (event: BeforeUnloadEvent) => { - if (isAnswerInProgress()) event.preventDefault(); - }; - // 페이지 히스토리에 영향을 주는 페이지 이동 처리: useBlocker 이용 const blocker = useBlocker(({ currentLocation, nextLocation }) => { const isLeavingPage = currentLocation.pathname !== nextLocation.pathname; @@ -35,14 +30,6 @@ const useNavigateBlocker = ({ openNavigateConfirmModal }: UseNavigateBlockerProp } }, [blocker]); - useEffect(() => { - window.addEventListener('beforeunload', handleNavigationBlock); - - return () => { - window.removeEventListener('beforeunload', handleNavigationBlock); - }; - }, [answerMap]); - return { blocker, }; From 2da42621f8bbaebefccbd218fa80fcc8e4037e02 Mon Sep 17 00:00:00 2001 From: ImxYJL Date: Sat, 23 Nov 2024 19:58:18 +0900 Subject: [PATCH 02/24] =?UTF-8?q?feat:=20=EB=A1=9C=EC=BB=AC=20=EC=8A=A4?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=EC=A7=80=EC=97=90=EC=84=9C=20=EA=B8=B0?= =?UTF-8?q?=EC=A1=B4=EC=97=90=20=EC=9E=91=EC=84=B1=ED=95=9C=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=EB=A5=BC=20=EB=B6=88=EB=9F=AC=EC=99=80=20=EC=A0=84?= =?UTF-8?q?=EC=97=AD=20=EC=83=81=ED=83=9C=EC=97=90=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../form/components/CardForm/index.tsx | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/frontend/src/pages/ReviewWritingPage/form/components/CardForm/index.tsx b/frontend/src/pages/ReviewWritingPage/form/components/CardForm/index.tsx index 2ecd0d5c9..bfb6b7a8d 100644 --- a/frontend/src/pages/ReviewWritingPage/form/components/CardForm/index.tsx +++ b/frontend/src/pages/ReviewWritingPage/form/components/CardForm/index.tsx @@ -1,5 +1,5 @@ import { useEffect } from 'react'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useRecoilState, useSetRecoilState } from 'recoil'; import { useSearchParamAndQuery } from '@/hooks'; import { CARD_FORM_MODAL_KEY } from '@/pages/ReviewWritingPage/constants'; @@ -25,11 +25,28 @@ const CardForm = () => { paramKey: 'reviewRequestCode', }); - // 작성했던 내용들을 저장하는 로직 - const selectedCategory = useRecoilValue(selectedCategoryAtom); - const answerMap = useRecoilValue(answerMapAtom); - const answerValidation = useRecoilValue(answerValidationMapAtom); + const [selectedCategory, setSelectedCategory] = useRecoilState(selectedCategoryAtom); + const [answerMap, setAnswerMap] = useRecoilState(answerMapAtom); + const [answerValidation, setAnswerValidation] = useRecoilState(answerValidationMapAtom); + + // 로컬 스토리지에서 값을 불러와 전역 상태에 저장 + useEffect(() => { + (() => { + const storedSelectedCategories = localStorage.getItem(`selectedCategories_${reviewRequestCode}`); + const storedAnswerValidations = localStorage.getItem(`answerValidations_${reviewRequestCode}`); + const storedAnswers = localStorage.getItem(`answers_${reviewRequestCode}`); + + const selectedCategories = storedSelectedCategories ? JSON.parse(storedSelectedCategories) : null; + const answerValidations = storedAnswerValidations ? new Map(JSON.parse(storedAnswerValidations)) : new Map(); + const answers = storedAnswers ? JSON.parse(storedAnswers) : null; + + setSelectedCategory(selectedCategories); + setAnswerValidation(answerValidations); + setAnswerMap(new Map(answers)); + })(); + }, []); + // 작성했던 내용들을 저장하는 로직 const getCurrentSelectedCategory = () => { if (selectedCategory && selectedCategory.length > 0) { return selectedCategory; @@ -55,13 +72,13 @@ const CardForm = () => { const answers = getCurrentAnswers(); const answerValidations = getAnswerValidation(); - // 빈 상태인지 확인 후 저장 if (selectedCategories) { localStorage.setItem(`selectedCategories_${reviewRequestCode}`, JSON.stringify(selectedCategories)); } if (answerValidations) { localStorage.setItem(`answerValidations_${reviewRequestCode}`, JSON.stringify(answerValidations)); } + if (answers) { localStorage.setItem(`answers_${reviewRequestCode}`, JSON.stringify(answers)); } @@ -81,18 +98,15 @@ const CardForm = () => { }; }, [reviewRequestCode, answerMap, selectedCategory, answerValidation]); - //// 복원 로직 - const setAnswerMap = useSetRecoilState(answerMapAtom); // 답변 상태를 설정할 수 있게 해줌 - ////////////////// 기존 로직 const setReviewRequestCode = useSetRecoilState(reviewRequestCodeAtom); const { currentCardIndex, handleCurrentCardIndex } = useCurrentCardIndex(); - // 리뷰에 필요한 질문지,프로젝트 정보 가져오기 + // 프로젝트 정보 및 질문지를 서버에서 가져옴 const { revieweeName, projectName } = useLoadAndPrepareReview({ reviewRequestCode }); - // 답변 + // 생성된 질문지를 바탕으로 답변 기본값 및 답변의 유효성 기본값 설정 useUpdateDefaultAnswers(); From c548e89a4e63a43da77d4e78649585fffa928366 Mon Sep 17 00:00:00 2001 From: ImxYJL Date: Sat, 23 Nov 2024 20:18:56 +0900 Subject: [PATCH 03/24] =?UTF-8?q?feat:=20=EB=A1=9C=EC=BB=AC=20=EC=8A=A4?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=EC=A7=80=EC=97=90=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=ED=95=B4=EB=92=80=EB=8D=98=20=EA=B0=9D=EA=B4=80=EC=8B=9D=20?= =?UTF-8?q?=EB=8B=B5=EB=B3=80=EC=9D=84=20=EC=B2=B4=ED=81=AC=EB=B0=95?= =?UTF-8?q?=EC=8A=A4=20=EC=83=81=ED=83=9C=EC=97=90=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../multipleChoice/useMultipleChoice.ts | 36 +++++++++++++++++-- .../multipleChoice/useOptionSelection.ts | 5 +++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/multipleChoice/useMultipleChoice.ts b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/multipleChoice/useMultipleChoice.ts index f5f7986e1..0e0253756 100644 --- a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/multipleChoice/useMultipleChoice.ts +++ b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/multipleChoice/useMultipleChoice.ts @@ -1,4 +1,8 @@ -import { ReviewWritingCardQuestion } from '@/types'; +import { useEffect } from 'react'; +import { useRecoilValue } from 'recoil'; + +import { answerMapAtom } from '@/recoil'; +import { ReviewWritingAnswer, ReviewWritingCardQuestion } from '@/types'; import useAboveSelectionLimit from './useAboveSelectionLimit'; import useCheckTailQuestionAnswer from './useCheckTailQuestionAnswer'; @@ -16,7 +20,9 @@ interface UseMultipleChoiceProps { const useMultipleChoice = ({ question, handleModalOpen }: UseMultipleChoiceProps) => { const { isAnsweredTailQuestion } = useCheckTailQuestionAnswer({ question }); - const { selectedOptionList, isSelectedCheckbox, updateSelectedOptionList } = useOptionSelection(); + const { selectedOptionList, isSelectedCheckbox, updateSelectedOptionList, initSelectedOptionList } = + useOptionSelection(); + const answerMap = useRecoilValue(answerMapAtom); const { updateAnswerState } = useUpdateMultipleChoiceAnswer({ question }); @@ -34,6 +40,31 @@ const useMultipleChoice = ({ question, handleModalOpen }: UseMultipleChoiceProps }, ); + interface findSelectedOptionIdsParams { + answerMap: Map | null; + questionId: number; + } + + // 로컬 스토리지에 저장했던 답변으로부터터, questionId를 통해 해당 질문의 selectedOptionIds를 찾는 함수 + const findSelectedOptionIds = ({ answerMap, questionId }: findSelectedOptionIdsParams) => { + if (!answerMap) return null; + + for (const [, value] of answerMap) { + if (value.questionId === questionId) { + return value.selectedOptionIds; + } + } + return null; + }; + + // 저장된 객관식 답변이 있다면 복원 + useEffect(() => { + const questionId = question.questionId; + const selectedOptionList = findSelectedOptionIds({ answerMap, questionId }); + + if (selectedOptionList) initSelectedOptionList([...selectedOptionList]); + }, []); + const handleCheckboxChange = (event: React.ChangeEvent) => { const { id, checked } = event.currentTarget; const optionId = Number(id); @@ -88,4 +119,5 @@ const useMultipleChoice = ({ question, handleModalOpen }: UseMultipleChoiceProps unCheckCategoryOptionId, }; }; + export default useMultipleChoice; diff --git a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/multipleChoice/useOptionSelection.ts b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/multipleChoice/useOptionSelection.ts index 65e836602..927275084 100644 --- a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/multipleChoice/useOptionSelection.ts +++ b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/multipleChoice/useOptionSelection.ts @@ -15,6 +15,10 @@ const useOptionSelection = () => { checked: boolean; } + const initSelectedOptionList = (newSelectedOptionList: number[]) => { + setSelectedOptionList(newSelectedOptionList); + }; + /** * checkbox의 change 이벤트에 따라 새로운 selectedOptionList를 반환하는 함수 */ @@ -37,6 +41,7 @@ const useOptionSelection = () => { selectedOptionList, isSelectedCheckbox, updateSelectedOptionList, + initSelectedOptionList, }; }; From 4235e7ecccf0a64ba5c0e94a394aabc939705d78 Mon Sep 17 00:00:00 2001 From: ImxYJL Date: Sat, 23 Nov 2024 20:49:12 +0900 Subject: [PATCH 04/24] =?UTF-8?q?feat:=20=EB=A1=9C=EC=BB=AC=20=EC=8A=A4?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=EC=A7=80=EC=97=90=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=ED=95=B4=EB=92=80=EB=8D=98=20=EC=A3=BC=EA=B4=80=EC=8B=9D=20?= =?UTF-8?q?=EB=8B=B5=EB=B3=80=EC=9D=84=20textarea=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=EC=97=90=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../form/hooks/answers/useTextAnswer/index.ts | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useTextAnswer/index.ts b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useTextAnswer/index.ts index 5afb5ec55..02e6950b3 100644 --- a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useTextAnswer/index.ts +++ b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useTextAnswer/index.ts @@ -1,6 +1,8 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; +import { useRecoilValue } from 'recoil'; import { TEXT_ANSWER_LENGTH } from '@/pages/ReviewWritingPage/constants'; +import { answerMapAtom } from '@/recoil'; import { ReviewWritingAnswer, ReviewWritingCardQuestion } from '@/types'; import useUpdateReviewerAnswer from '../useUpdateReviewerAnswer'; @@ -21,10 +23,36 @@ interface UseTextAnswerProps { */ const useTextAnswer = ({ question }: UseTextAnswerProps) => { const { updateAnswerMap, updateAnswerValidationMap } = useUpdateReviewerAnswer(); + const answerMap = useRecoilValue(answerMapAtom); const [text, setText] = useState(''); const [errorMessage, setErrorMessage] = useState(TEXT_ANSWER_ERROR_MESSAGE.noError); + // 로컬 스토리지에 저장했던 답변으로부터터, questionId를 통해 해당 질문의 서술형 답변을 찾는 함수 + // TODO: 복원을 위한 find 함수들을 별도 유틸로 분리 및 통합 + interface findTextAnswerParams { + answerMap: Map | null; + questionId: number; + } + const findTextAnswer = ({ answerMap, questionId }: findTextAnswerParams) => { + if (!answerMap) return null; + + for (const [, value] of answerMap) { + if (value.questionId === questionId) { + return value.text; + } + } + return null; + }; + + // 저장된 주관식 답변이 있다면 복원 + useEffect(() => { + const questionId = question.questionId; + const textAnswer = findTextAnswer({ answerMap, questionId }); + + if (textAnswer) setText(textAnswer); + }, []); + const handleTextAnswerChange = (event: React.ChangeEvent) => { const { value } = event.target; From 3aeb37cf356ef91a819ae3d63f24e5e827b1a07e Mon Sep 17 00:00:00 2001 From: ImxYJL Date: Sun, 24 Nov 2024 18:13:11 +0900 Subject: [PATCH 05/24] =?UTF-8?q?feat:=20=EB=A1=9C=EC=BB=AC=20=EC=8A=A4?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=EC=A7=80=EC=97=90=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=ED=95=B4=EB=92=80=EB=8D=98=20=EC=83=81=ED=83=9C=EB=A5=BC=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=EA=B7=B8=EB=A0=88=EC=8A=A4=20=EB=B0=94?= =?UTF-8?q?=EC=97=90=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../progressBar/hooks/useStepList/index.ts | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/ReviewWritingPage/progressBar/hooks/useStepList/index.ts b/frontend/src/pages/ReviewWritingPage/progressBar/hooks/useStepList/index.ts index c7669682c..b4a6a64a8 100644 --- a/frontend/src/pages/ReviewWritingPage/progressBar/hooks/useStepList/index.ts +++ b/frontend/src/pages/ReviewWritingPage/progressBar/hooks/useStepList/index.ts @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react'; import { useRecoilValue } from 'recoil'; +import { useSearchParamAndQuery } from '@/hooks'; import { answerValidationMapAtom, cardSectionListSelector } from '@/recoil'; interface UseStepListProps { @@ -31,7 +32,7 @@ const useStepList = ({ currentCardIndex }: UseStepListProps) => { const newStepList: Step[] = []; cardSectionList?.forEach((section, index) => { - const isPreviousDone = index === 0 || newStepList.every((step) => step.isDone); + const isPreviousDone = index === 0 || newStepList.every((step) => step.isDone); const isMovingAvailable = isPreviousDone && visitedCardIdList.includes(section.sectionId); const isCurrentStep = index === currentCardIndex; @@ -64,6 +65,42 @@ const useStepList = ({ currentCardIndex }: UseStepListProps) => { }); }; + // 복원 및 저장 로직 + const { param: reviewRequestCode } = useSearchParamAndQuery({ + paramKey: 'reviewRequestCode', + }); + + const handleBeforeUnloadChange = () => { + if (visitedCardIdList.length > 0) { + localStorage.setItem(`visitedCardIdList_${reviewRequestCode}`, JSON.stringify(visitedCardIdList)); + } + }; + + // 복원 + useEffect(() => { + (() => { + const storedVisitedCardIdList = localStorage.getItem(`visitedCardIdList_${reviewRequestCode}`); + const defaultVisitedCardIdList = cardSectionList.length > 0 ? [cardSectionList[0].sectionId] : []; + const parsedVisitedCardIdList = storedVisitedCardIdList + ? JSON.parse(storedVisitedCardIdList) + : defaultVisitedCardIdList; + + setVisitedCardIdList(parsedVisitedCardIdList); + })(); + }, [reviewRequestCode, cardSectionList]); + + // 저장 + useEffect(() => { + // TODO: 라우터를 통한 이동에서도 동작할 수 있도록 하기 + window.addEventListener('beforeunload', handleBeforeUnloadChange); + document.addEventListener('visibilitychange', handleBeforeUnloadChange); + + return () => { + window.removeEventListener('beforeunload', handleBeforeUnloadChange); + document.removeEventListener('visibilitychange', handleBeforeUnloadChange); + }; + }, [reviewRequestCode, visitedCardIdList]); + useEffect(() => { updateVisitedCardIdList(); }, [cardSectionList, currentCardIndex]); From 995527a002352d5663a20e44f5ca40e68ebf7cee Mon Sep 17 00:00:00 2001 From: ImxYJL Date: Mon, 25 Nov 2024 22:30:09 +0900 Subject: [PATCH 06/24] =?UTF-8?q?feat:=20react=20router=EB=A5=BC=20?= =?UTF-8?q?=EC=9D=B4=EC=9A=A9=ED=95=9C=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EC=9D=B4=ED=83=88=20=EC=8B=9C=EC=97=90=EB=8F=84=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=EB=A5=BC=20=EB=A1=9C=EC=BB=AC=20=EC=8A=A4=ED=86=A0=EB=A6=AC?= =?UTF-8?q?=EC=A7=80=EC=97=90=20=EC=A0=80=EC=9E=A5=ED=95=98=EA=B3=A0=20?= =?UTF-8?q?=EB=B3=B5=EC=9B=90=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80,=20=EA=B7=B8=EC=97=90=20=EB=94=B0=EB=A5=B8?= =?UTF-8?q?=20NavigateBlockerModal=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../form/components/CardForm/index.tsx | 61 ++++++------------- .../CardFormModalContainer/index.tsx | 12 +--- .../components/NavigateBlockerModal/index.tsx | 40 ------------ .../components/NavigateBlockerModal/style.ts | 20 ------ .../modals/components/index.tsx | 1 - 5 files changed, 19 insertions(+), 115 deletions(-) delete mode 100644 frontend/src/pages/ReviewWritingPage/modals/components/NavigateBlockerModal/index.tsx delete mode 100644 frontend/src/pages/ReviewWritingPage/modals/components/NavigateBlockerModal/style.ts diff --git a/frontend/src/pages/ReviewWritingPage/form/components/CardForm/index.tsx b/frontend/src/pages/ReviewWritingPage/form/components/CardForm/index.tsx index bfb6b7a8d..d71363e6a 100644 --- a/frontend/src/pages/ReviewWritingPage/form/components/CardForm/index.tsx +++ b/frontend/src/pages/ReviewWritingPage/form/components/CardForm/index.tsx @@ -1,13 +1,11 @@ -import { useEffect } from 'react'; +import { useCallback, useEffect } from 'react'; import { useRecoilState, useSetRecoilState } from 'recoil'; import { useSearchParamAndQuery } from '@/hooks'; -import { CARD_FORM_MODAL_KEY } from '@/pages/ReviewWritingPage/constants'; import { useCurrentCardIndex, useResetFormRecoil, useUpdateDefaultAnswers, - useNavigateBlocker, useLoadAndPrepareReview, } from '@/pages/ReviewWritingPage/form/hooks'; import { CardFormModalContainer } from '@/pages/ReviewWritingPage/modals/components'; @@ -29,21 +27,19 @@ const CardForm = () => { const [answerMap, setAnswerMap] = useRecoilState(answerMapAtom); const [answerValidation, setAnswerValidation] = useRecoilState(answerValidationMapAtom); - // 로컬 스토리지에서 값을 불러와 전역 상태에 저장 + // 로컬 스토리지의 값으로 전역 상태 복원 useEffect(() => { - (() => { - const storedSelectedCategories = localStorage.getItem(`selectedCategories_${reviewRequestCode}`); - const storedAnswerValidations = localStorage.getItem(`answerValidations_${reviewRequestCode}`); - const storedAnswers = localStorage.getItem(`answers_${reviewRequestCode}`); - - const selectedCategories = storedSelectedCategories ? JSON.parse(storedSelectedCategories) : null; - const answerValidations = storedAnswerValidations ? new Map(JSON.parse(storedAnswerValidations)) : new Map(); - const answers = storedAnswers ? JSON.parse(storedAnswers) : null; - - setSelectedCategory(selectedCategories); - setAnswerValidation(answerValidations); - setAnswerMap(new Map(answers)); - })(); + const storedSelectedCategories = localStorage.getItem(`selectedCategories_${reviewRequestCode}`); + const storedAnswerValidations = localStorage.getItem(`answerValidations_${reviewRequestCode}`); + const storedAnswers = localStorage.getItem(`answers_${reviewRequestCode}`); + + const selectedCategories = storedSelectedCategories ? JSON.parse(storedSelectedCategories) : null; + const answerValidations = storedAnswerValidations ? new Map(JSON.parse(storedAnswerValidations)) : new Map(); + const answers = storedAnswers ? JSON.parse(storedAnswers) : null; + + setSelectedCategory(selectedCategories); + setAnswerValidation(answerValidations); + setAnswerMap(new Map(answers)); }, []); // 작성했던 내용들을 저장하는 로직 @@ -67,7 +63,7 @@ const CardForm = () => { } }; - const handleBeforeUnloadChange = () => { + const handleBeforeUnloadChange = useCallback(() => { const selectedCategories = getCurrentSelectedCategory(); const answers = getCurrentAnswers(); const answerValidations = getAnswerValidation(); @@ -82,21 +78,12 @@ const CardForm = () => { if (answers) { localStorage.setItem(`answers_${reviewRequestCode}`, JSON.stringify(answers)); } - }; + }, [selectedCategory, answerMap, answerValidation, reviewRequestCode]); + // 실시간 답변 상태를 로컬 스토리지에 저장 useEffect(() => { - // TODO: 라우터를 통한 이동에서도 동작할 수 있도록 하기 - - window.addEventListener('beforeunload', handleBeforeUnloadChange); - document.addEventListener('visibilitychange', handleBeforeUnloadChange); - // window.addEventListener('pagehide', handleBeforeUnloadChange); - - return () => { - window.removeEventListener('beforeunload', handleBeforeUnloadChange); - document.removeEventListener('visibilitychange', handleBeforeUnloadChange); - // window.removeEventListener('pagehide', handleBeforeUnloadChange); - }; - }, [reviewRequestCode, answerMap, selectedCategory, answerValidation]); + handleBeforeUnloadChange(); + }, [handleBeforeUnloadChange]); ////////////////// 기존 로직 @@ -113,17 +100,6 @@ const CardForm = () => { // 모달 const { handleOpenModal, closeModal, isOpen } = useCardFormModal(); - const handleNavigateConfirmButtonClick = () => { - closeModal(CARD_FORM_MODAL_KEY.navigateConfirm); - - if (blocker.proceed) blocker.proceed(); - }; - - // 작성 중인 답변이 있는 경우 페이지 이동을 막는 기능 - const { blocker } = useNavigateBlocker({ - openNavigateConfirmModal: () => handleOpenModal('navigateConfirm'), - }); - const { resetFormRecoil } = useResetFormRecoil(); useEffect(() => { @@ -169,7 +145,6 @@ const CardForm = () => { {}} // handleRestoreButtonClick={handleRestoreAnswers} /> diff --git a/frontend/src/pages/ReviewWritingPage/modals/components/CardFormModalContainer/index.tsx b/frontend/src/pages/ReviewWritingPage/modals/components/CardFormModalContainer/index.tsx index 8832f2ac3..18519aefe 100644 --- a/frontend/src/pages/ReviewWritingPage/modals/components/CardFormModalContainer/index.tsx +++ b/frontend/src/pages/ReviewWritingPage/modals/components/CardFormModalContainer/index.tsx @@ -5,7 +5,6 @@ import { ErrorBoundary } from '@/components'; import { CARD_FORM_MODAL_KEY } from '@/pages/ReviewWritingPage/constants'; import { AnswerListRecheckModal, - NavigateBlockerModal, SubmitCheckModal, } from '@/pages/ReviewWritingPage/modals/components'; import { answerMapAtom, cardSectionListSelector } from '@/recoil'; @@ -15,13 +14,12 @@ import SubmitErrorModal from '../SubmitErrorModal'; interface CardFormModalContainerProps { isOpen: (key: string) => boolean; closeModal: (key: string) => void; - handleNavigateConfirmButtonClick: () => void; + handleRestoreButtonClick: () => void; } const CardFormModalContainer = ({ isOpen, closeModal, - handleNavigateConfirmButtonClick, }: CardFormModalContainerProps) => { const answerMap = useRecoilValue(answerMapAtom); const cardSectionList = useRecoilValue(cardSectionListSelector); @@ -48,7 +46,6 @@ const CardFormModalContainer = ({ )} - {isOpen(CARD_FORM_MODAL_KEY.recheck) && cardSectionList && answerMap && ( closeModal(CARD_FORM_MODAL_KEY.recheck)} /> )} - {isOpen(CARD_FORM_MODAL_KEY.navigateConfirm) && ( - closeModal(CARD_FORM_MODAL_KEY.navigateConfirm)} - handleCloseModal={() => closeModal(CARD_FORM_MODAL_KEY.navigateConfirm)} - /> - )} ); }; diff --git a/frontend/src/pages/ReviewWritingPage/modals/components/NavigateBlockerModal/index.tsx b/frontend/src/pages/ReviewWritingPage/modals/components/NavigateBlockerModal/index.tsx deleted file mode 100644 index 684b109e6..000000000 --- a/frontend/src/pages/ReviewWritingPage/modals/components/NavigateBlockerModal/index.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { ConfirmModal } from '@/components'; - -import * as S from './style'; - -interface NavigateBlockerModalProps { - handleNavigateConfirmButtonClick: () => void; - handleCancelButtonClick: () => void; - handleCloseModal: () => void; -} - -const NavigateBlockerModal = ({ - handleNavigateConfirmButtonClick, - handleCancelButtonClick, - handleCloseModal, -}: NavigateBlockerModalProps) => { - return ( - - -

페이지를 이동하면 작성한 답변이 삭제돼요

-

페이지 이동을 진행할까요?

-
-
- ); -}; - -export default NavigateBlockerModal; diff --git a/frontend/src/pages/ReviewWritingPage/modals/components/NavigateBlockerModal/style.ts b/frontend/src/pages/ReviewWritingPage/modals/components/NavigateBlockerModal/style.ts deleted file mode 100644 index 14d8246fb..000000000 --- a/frontend/src/pages/ReviewWritingPage/modals/components/NavigateBlockerModal/style.ts +++ /dev/null @@ -1,20 +0,0 @@ -import styled from '@emotion/styled'; - -import media from '@/utils/media'; - -export const ConfirmModalMessage = styled.div` - display: flex; - flex-direction: column; - gap: 0.8rem; - align-items: center; - - p { - margin: 0; - text-align: center; - } - - ${media.xSmall} { - width: 100%; - min-width: 70vw; - } -`; diff --git a/frontend/src/pages/ReviewWritingPage/modals/components/index.tsx b/frontend/src/pages/ReviewWritingPage/modals/components/index.tsx index f4a21430b..8026ace2c 100644 --- a/frontend/src/pages/ReviewWritingPage/modals/components/index.tsx +++ b/frontend/src/pages/ReviewWritingPage/modals/components/index.tsx @@ -1,5 +1,4 @@ export { default as AnswerListRecheckModal } from './AnswerListRecheckModal'; export { default as CardFormModalContainer } from './CardFormModalContainer'; -export { default as NavigateBlockerModal } from './NavigateBlockerModal'; export { default as SubmitCheckModal } from './SubmitCheckModal'; export { default as StrengthUnCheckModal } from './StrengthUnCheckModal'; From e9022d0d25b926dc1bc3aaf235122739ef0a3031 Mon Sep 17 00:00:00 2001 From: ImxYJL Date: Mon, 25 Nov 2024 22:34:40 +0900 Subject: [PATCH 07/24] =?UTF-8?q?feat:=20=EA=B0=9D=EA=B4=80=EC=8B=9D=20?= =?UTF-8?q?=EC=A7=88=EB=AC=B8=EC=97=90=EC=84=9C=EB=8F=84=20react=20router?= =?UTF-8?q?=EB=A5=BC=20=EC=9D=B4=EC=9A=A9=ED=96=88=EC=9D=84=20=EB=95=8C=20?= =?UTF-8?q?=EB=8B=B5=EB=B3=80=EC=9D=84=20=EC=A0=80=EC=9E=A5=ED=95=98?= =?UTF-8?q?=EA=B3=A0=20=EB=B3=B5=EC=9B=90=ED=95=98=EB=8A=94=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hooks/answers/multipleChoice/useMultipleChoice.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/multipleChoice/useMultipleChoice.ts b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/multipleChoice/useMultipleChoice.ts index 0e0253756..9417a8fb3 100644 --- a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/multipleChoice/useMultipleChoice.ts +++ b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/multipleChoice/useMultipleChoice.ts @@ -45,7 +45,7 @@ const useMultipleChoice = ({ question, handleModalOpen }: UseMultipleChoiceProps questionId: number; } - // 로컬 스토리지에 저장했던 답변으로부터터, questionId를 통해 해당 질문의 selectedOptionIds를 찾는 함수 + // 로컬 스토리지에 저장했던 답변으로부터, questionId를 통해 해당 질문의 selectedOptionIds를 찾는 함수 const findSelectedOptionIds = ({ answerMap, questionId }: findSelectedOptionIdsParams) => { if (!answerMap) return null; @@ -59,11 +59,14 @@ const useMultipleChoice = ({ question, handleModalOpen }: UseMultipleChoiceProps // 저장된 객관식 답변이 있다면 복원 useEffect(() => { + if (!answerMap || answerMap.size === 0) return; + if (selectedOptionList.length > 0) return; + const questionId = question.questionId; - const selectedOptionList = findSelectedOptionIds({ answerMap, questionId }); + const selectedOptionIds = findSelectedOptionIds({ answerMap, questionId }); - if (selectedOptionList) initSelectedOptionList([...selectedOptionList]); - }, []); + if (selectedOptionIds) initSelectedOptionList([...selectedOptionIds]); + }, [answerMap, question]); const handleCheckboxChange = (event: React.ChangeEvent) => { const { id, checked } = event.currentTarget; From 4993856d3412a07e9d90c6ead99b72de0ab1514c Mon Sep 17 00:00:00 2001 From: ImxYJL Date: Mon, 25 Nov 2024 22:35:56 +0900 Subject: [PATCH 08/24] =?UTF-8?q?feat:=20react=20router=EB=A5=BC=20?= =?UTF-8?q?=EC=9D=B4=EC=9A=A9=ED=96=88=EC=9D=84=20=EB=95=8C=20=EA=B8=B0?= =?UTF-8?q?=EC=A1=B4=20=ED=94=84=EB=A1=9C=EA=B7=B8=EB=A0=88=EC=8A=A4=20?= =?UTF-8?q?=EB=B0=94=20=EC=83=81=ED=83=9C=EB=A5=BC=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=ED=95=98=EA=B3=A0=20=EB=B3=B5=EC=9B=90=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../progressBar/hooks/useStepList/index.ts | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/frontend/src/pages/ReviewWritingPage/progressBar/hooks/useStepList/index.ts b/frontend/src/pages/ReviewWritingPage/progressBar/hooks/useStepList/index.ts index b4a6a64a8..606c4ff43 100644 --- a/frontend/src/pages/ReviewWritingPage/progressBar/hooks/useStepList/index.ts +++ b/frontend/src/pages/ReviewWritingPage/progressBar/hooks/useStepList/index.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { useRecoilValue } from 'recoil'; import { useSearchParamAndQuery } from '@/hooks'; @@ -32,7 +32,7 @@ const useStepList = ({ currentCardIndex }: UseStepListProps) => { const newStepList: Step[] = []; cardSectionList?.forEach((section, index) => { - const isPreviousDone = index === 0 || newStepList.every((step) => step.isDone); + const isPreviousDone = index === 0 || newStepList.every((step) => step.isDone); const isMovingAvailable = isPreviousDone && visitedCardIdList.includes(section.sectionId); const isCurrentStep = index === currentCardIndex; @@ -70,36 +70,27 @@ const useStepList = ({ currentCardIndex }: UseStepListProps) => { paramKey: 'reviewRequestCode', }); - const handleBeforeUnloadChange = () => { + const handleBeforeUnloadChange = useCallback(() => { if (visitedCardIdList.length > 0) { localStorage.setItem(`visitedCardIdList_${reviewRequestCode}`, JSON.stringify(visitedCardIdList)); } - }; + }, [reviewRequestCode, visitedCardIdList]); - // 복원 + // 복원 useEffect(() => { - (() => { const storedVisitedCardIdList = localStorage.getItem(`visitedCardIdList_${reviewRequestCode}`); const defaultVisitedCardIdList = cardSectionList.length > 0 ? [cardSectionList[0].sectionId] : []; const parsedVisitedCardIdList = storedVisitedCardIdList ? JSON.parse(storedVisitedCardIdList) : defaultVisitedCardIdList; - setVisitedCardIdList(parsedVisitedCardIdList); - })(); + setVisitedCardIdList(parsedVisitedCardIdList); }, [reviewRequestCode, cardSectionList]); - // 저장 - useEffect(() => { - // TODO: 라우터를 통한 이동에서도 동작할 수 있도록 하기 - window.addEventListener('beforeunload', handleBeforeUnloadChange); - document.addEventListener('visibilitychange', handleBeforeUnloadChange); - - return () => { - window.removeEventListener('beforeunload', handleBeforeUnloadChange); - document.removeEventListener('visibilitychange', handleBeforeUnloadChange); - }; - }, [reviewRequestCode, visitedCardIdList]); + // 로컬 스토리지와의 동기화를 위한 useEffect + useEffect(()=>{ + handleBeforeUnloadChange(); + },[visitedCardIdList]); useEffect(() => { updateVisitedCardIdList(); From 28c7af55daf4b28b379ded6a5c12a784c95f6ae9 Mon Sep 17 00:00:00 2001 From: ImxYJL Date: Mon, 25 Nov 2024 22:36:32 +0900 Subject: [PATCH 09/24] =?UTF-8?q?feat:=20=EC=A3=BC=EA=B4=80=EC=8B=9D=20?= =?UTF-8?q?=EC=A7=88=EB=AC=B8=EC=97=90=EC=84=9C=EB=8F=84=20react=20router?= =?UTF-8?q?=EB=A5=BC=20=EC=9D=B4=EC=9A=A9=ED=96=88=EC=9D=84=20=EB=95=8C=20?= =?UTF-8?q?=EB=8B=B5=EB=B3=80=EC=9D=84=20=EC=A0=80=EC=9E=A5=ED=95=98?= =?UTF-8?q?=EA=B3=A0=20=EB=B3=B5=EC=9B=90=ED=95=98=EB=8A=94=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../form/hooks/answers/useTextAnswer/index.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useTextAnswer/index.ts b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useTextAnswer/index.ts index 02e6950b3..37f925529 100644 --- a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useTextAnswer/index.ts +++ b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useTextAnswer/index.ts @@ -28,7 +28,7 @@ const useTextAnswer = ({ question }: UseTextAnswerProps) => { const [text, setText] = useState(''); const [errorMessage, setErrorMessage] = useState(TEXT_ANSWER_ERROR_MESSAGE.noError); - // 로컬 스토리지에 저장했던 답변으로부터터, questionId를 통해 해당 질문의 서술형 답변을 찾는 함수 + // 로컬 스토리지에 저장했던 답변으로부터, questionId를 통해 해당 질문의 서술형 답변을 찾는 함수 // TODO: 복원을 위한 find 함수들을 별도 유틸로 분리 및 통합 interface findTextAnswerParams { answerMap: Map | null; @@ -47,11 +47,14 @@ const useTextAnswer = ({ question }: UseTextAnswerProps) => { // 저장된 주관식 답변이 있다면 복원 useEffect(() => { + if (!answerMap || answerMap.size === 0) return; + if(text && text.length > 0) return; + const questionId = question.questionId; const textAnswer = findTextAnswer({ answerMap, questionId }); if (textAnswer) setText(textAnswer); - }, []); + }, [answerMap, question]); const handleTextAnswerChange = (event: React.ChangeEvent) => { const { value } = event.target; From 2624752bb40958c2da7f96020c4c70ff48ad851f Mon Sep 17 00:00:00 2001 From: ImxYJL Date: Mon, 25 Nov 2024 22:44:25 +0900 Subject: [PATCH 10/24] =?UTF-8?q?chore:=20=EA=B0=84=EB=8B=A8=ED=95=9C=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EA=B0=84=EC=86=8C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ReviewWritingPage/form/hooks/answers/useSubmitAnswers.ts | 2 +- .../form/hooks/answers/useUpdateDefaultAnswers.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useSubmitAnswers.ts b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useSubmitAnswers.ts index 3c1e210d0..91eaba2d9 100644 --- a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useSubmitAnswers.ts +++ b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useSubmitAnswers.ts @@ -33,7 +33,7 @@ const useSubmitAnswers = ({ closeSubmitConfirmModal }: UseSubmitAnswersProps) => if (!answerMap || !reviewRequestCode) return; const result: ReviewWritingFormResult = { - reviewRequestCode: reviewRequestCode, + reviewRequestCode, answers: Array.from(answerMap.values()), }; diff --git a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useUpdateDefaultAnswers.ts b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useUpdateDefaultAnswers.ts index 118513080..a22112c94 100644 --- a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useUpdateDefaultAnswers.ts +++ b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useUpdateDefaultAnswers.ts @@ -1,5 +1,5 @@ import { useEffect } from 'react'; -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; +import { useRecoilState, useRecoilValue } from 'recoil'; import { answerMapAtom, answerValidationMapAtom, cardSectionListSelector } from '@/recoil'; import { ReviewWritingAnswer, ReviewWritingCardQuestion } from '@/types'; @@ -15,6 +15,7 @@ const useUpdateDefaultAnswers = () => { // NOTE : answerMap - 질문에 대한 답변들 , number : questionId const [answerMap, setAnswerMap] = useRecoilState(answerMapAtom); + // 서버에서 받아온 질문지 const cardSectionList = useRecoilValue(cardSectionListSelector); // NOTE : answerValidationMap -질문의 단볍들의 유효성 여부 ,number: questionId const [answerValidationMap, setAnswerValidationMap] = useRecoilState(answerValidationMapAtom); From badc5c3b8a4b5b8f626b08082c8ee8ddd0b1d2b5 Mon Sep 17 00:00:00 2001 From: ImxYJL Date: Thu, 28 Nov 2024 22:16:26 +0900 Subject: [PATCH 11/24] =?UTF-8?q?feat:=20=EC=9E=91=EC=84=B1=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EA=B4=80=EB=A0=A8=20=EB=A1=9C=EC=BB=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=86=A0=EB=A6=AC=EC=A7=80=20=ED=82=A4=20=EC=83=81?= =?UTF-8?q?=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/constants/storageKey.ts | 7 ++++++ .../form/components/CardForm/index.tsx | 21 +++++++++++----- .../progressBar/hooks/useStepList/index.ts | 24 +++++++++++-------- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/frontend/src/constants/storageKey.ts b/frontend/src/constants/storageKey.ts index 5fb04d984..c5750874a 100644 --- a/frontend/src/constants/storageKey.ts +++ b/frontend/src/constants/storageKey.ts @@ -1,3 +1,10 @@ +export const STORED_DATA_NAME = { + selectedCategories :'selectedCategories', + answerValidations: 'answerValidations', + answers: 'answers', + visitedCardIdList: 'visitedCardIdList' +} as const; + export const LOCAL_STORAGE_KEY = { isHighlightEditable: 'isHighlightEditable', isHighlightError: 'isHighlightError', diff --git a/frontend/src/pages/ReviewWritingPage/form/components/CardForm/index.tsx b/frontend/src/pages/ReviewWritingPage/form/components/CardForm/index.tsx index d71363e6a..d8e15aff9 100644 --- a/frontend/src/pages/ReviewWritingPage/form/components/CardForm/index.tsx +++ b/frontend/src/pages/ReviewWritingPage/form/components/CardForm/index.tsx @@ -1,6 +1,7 @@ import { useCallback, useEffect } from 'react'; import { useRecoilState, useSetRecoilState } from 'recoil'; +import { STORED_DATA_NAME } from '@/constants'; import { useSearchParamAndQuery } from '@/hooks'; import { useCurrentCardIndex, @@ -29,9 +30,11 @@ const CardForm = () => { // 로컬 스토리지의 값으로 전역 상태 복원 useEffect(() => { - const storedSelectedCategories = localStorage.getItem(`selectedCategories_${reviewRequestCode}`); - const storedAnswerValidations = localStorage.getItem(`answerValidations_${reviewRequestCode}`); - const storedAnswers = localStorage.getItem(`answers_${reviewRequestCode}`); + const storedSelectedCategories = localStorage.getItem( + `${STORED_DATA_NAME.selectedCategories}_${reviewRequestCode}`, + ); + const storedAnswerValidations = localStorage.getItem(`${STORED_DATA_NAME.answerValidations}_${reviewRequestCode}`); + const storedAnswers = localStorage.getItem(`${STORED_DATA_NAME.answers}_${reviewRequestCode}`); const selectedCategories = storedSelectedCategories ? JSON.parse(storedSelectedCategories) : null; const answerValidations = storedAnswerValidations ? new Map(JSON.parse(storedAnswerValidations)) : new Map(); @@ -69,14 +72,20 @@ const CardForm = () => { const answerValidations = getAnswerValidation(); if (selectedCategories) { - localStorage.setItem(`selectedCategories_${reviewRequestCode}`, JSON.stringify(selectedCategories)); + localStorage.setItem( + `${STORED_DATA_NAME.selectedCategories}_${reviewRequestCode}`, + JSON.stringify(selectedCategories), + ); } if (answerValidations) { - localStorage.setItem(`answerValidations_${reviewRequestCode}`, JSON.stringify(answerValidations)); + localStorage.setItem( + `${STORED_DATA_NAME.answerValidations}_${reviewRequestCode}`, + JSON.stringify(answerValidations), + ); } if (answers) { - localStorage.setItem(`answers_${reviewRequestCode}`, JSON.stringify(answers)); + localStorage.setItem(`${STORED_DATA_NAME.answers}_${reviewRequestCode}`, JSON.stringify(answers)); } }, [selectedCategory, answerMap, answerValidation, reviewRequestCode]); diff --git a/frontend/src/pages/ReviewWritingPage/progressBar/hooks/useStepList/index.ts b/frontend/src/pages/ReviewWritingPage/progressBar/hooks/useStepList/index.ts index 606c4ff43..094a4d73d 100644 --- a/frontend/src/pages/ReviewWritingPage/progressBar/hooks/useStepList/index.ts +++ b/frontend/src/pages/ReviewWritingPage/progressBar/hooks/useStepList/index.ts @@ -1,6 +1,7 @@ import { useCallback, useEffect, useState } from 'react'; import { useRecoilValue } from 'recoil'; +import { STORED_DATA_NAME } from '@/constants'; import { useSearchParamAndQuery } from '@/hooks'; import { answerValidationMapAtom, cardSectionListSelector } from '@/recoil'; @@ -72,25 +73,28 @@ const useStepList = ({ currentCardIndex }: UseStepListProps) => { const handleBeforeUnloadChange = useCallback(() => { if (visitedCardIdList.length > 0) { - localStorage.setItem(`visitedCardIdList_${reviewRequestCode}`, JSON.stringify(visitedCardIdList)); + localStorage.setItem( + `${STORED_DATA_NAME.visitedCardIdList}_${reviewRequestCode}`, + JSON.stringify(visitedCardIdList), + ); } }, [reviewRequestCode, visitedCardIdList]); - // 복원 + // 복원 useEffect(() => { - const storedVisitedCardIdList = localStorage.getItem(`visitedCardIdList_${reviewRequestCode}`); - const defaultVisitedCardIdList = cardSectionList.length > 0 ? [cardSectionList[0].sectionId] : []; - const parsedVisitedCardIdList = storedVisitedCardIdList - ? JSON.parse(storedVisitedCardIdList) - : defaultVisitedCardIdList; + const storedVisitedCardIdList = localStorage.getItem(`${STORED_DATA_NAME.visitedCardIdList}_${reviewRequestCode}`); + const defaultVisitedCardIdList = cardSectionList.length > 0 ? [cardSectionList[0].sectionId] : []; + const parsedVisitedCardIdList = storedVisitedCardIdList + ? JSON.parse(storedVisitedCardIdList) + : defaultVisitedCardIdList; - setVisitedCardIdList(parsedVisitedCardIdList); + setVisitedCardIdList(parsedVisitedCardIdList); }, [reviewRequestCode, cardSectionList]); // 로컬 스토리지와의 동기화를 위한 useEffect - useEffect(()=>{ + useEffect(() => { handleBeforeUnloadChange(); - },[visitedCardIdList]); + }, [visitedCardIdList]); useEffect(() => { updateVisitedCardIdList(); From 71e1b345ac09b0ab4d1f53b7220fa49e9ed3c072 Mon Sep 17 00:00:00 2001 From: ImxYJL Date: Sun, 1 Dec 2024 14:47:48 +0900 Subject: [PATCH 12/24] =?UTF-8?q?feat:=20=EC=A0=9C=EC=B6=9C=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C=20=EC=8B=9C=20=EB=A1=9C=EC=BB=AC=20=EC=8A=A4=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=EC=A7=80=EC=97=90=20=EC=A0=80=EC=9E=A5=ED=96=88?= =?UTF-8?q?=EB=8D=98=20=EB=A6=AC=EB=B7=B0=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../form/hooks/answers/index.ts | 1 + .../answers/useDeleteReviewInLocalStorage.ts | 25 +++++++++++++++++++ .../form/hooks/answers/useSubmitAnswers.ts | 7 ++++-- 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 frontend/src/pages/ReviewWritingPage/form/hooks/answers/useDeleteReviewInLocalStorage.ts diff --git a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/index.ts b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/index.ts index 1d1ce3d5d..7709dd4d4 100644 --- a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/index.ts +++ b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/index.ts @@ -3,3 +3,4 @@ export { default as useTextAnswer } from './useTextAnswer'; export { default as useUpdateDefaultAnswers } from './useUpdateDefaultAnswers'; export { default as useUpdateReviewerAnswer } from './useUpdateReviewerAnswer'; export { default as useSubmitAnswers } from './useSubmitAnswers'; +export { default as useDeleteReviewInLocalStorage } from './useDeleteReviewInLocalStorage'; diff --git a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useDeleteReviewInLocalStorage.ts b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useDeleteReviewInLocalStorage.ts new file mode 100644 index 000000000..8d3d99dfe --- /dev/null +++ b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useDeleteReviewInLocalStorage.ts @@ -0,0 +1,25 @@ +import { STORED_DATA_NAME } from '@/constants'; +import { useSearchParamAndQuery } from '@/hooks'; + +const useDeleteReviewInLocalStorage = () => { + const { param: reviewRequestCode } = useSearchParamAndQuery({ + paramKey: 'reviewRequestCode', + }); + + const deleteReviewDataInLocalStorage = (key: keyof typeof STORED_DATA_NAME) => { + localStorage.removeItem(`${STORED_DATA_NAME[key]}_${reviewRequestCode}`); + }; + + const deleteAllReviewDataInLocalStorage = () => { + Object.values(STORED_DATA_NAME).forEach((key) => { + localStorage.removeItem(`${STORED_DATA_NAME[key]}_${reviewRequestCode}`); + }); + }; + + return { + deleteReviewDataInLocalStorage, + deleteAllReviewDataInLocalStorage, + }; +}; + +export default useDeleteReviewInLocalStorage; diff --git a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useSubmitAnswers.ts b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useSubmitAnswers.ts index 961cbce05..8ae04d451 100644 --- a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useSubmitAnswers.ts +++ b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useSubmitAnswers.ts @@ -7,6 +7,8 @@ import { ReviewWritingFormResult } from '@/types'; import useMutateReview from '../useMutateReview'; +import { useDeleteReviewInLocalStorage } from '.'; + interface UseSubmitAnswersProps { closeSubmitConfirmModal: () => void; } @@ -15,12 +17,13 @@ interface UseSubmitAnswersProps { */ const useSubmitAnswers = ({ closeSubmitConfirmModal }: UseSubmitAnswersProps) => { const reviewRequestCode = useRecoilValue(reviewRequestCodeAtom); - const answerMap = useRecoilValue(answerMapAtom); const navigate = useNavigate(); - + const { deleteAllReviewDataInLocalStorage } = useDeleteReviewInLocalStorage(); + const executeAfterMutateSuccess = () => { + deleteAllReviewDataInLocalStorage(); navigate(`/${ROUTE.reviewWritingComplete}/${reviewRequestCode}`, { state: { isValidAccess: true } }); closeSubmitConfirmModal(); }; From ca5ce80538433d26c31b9f3a322266aef3d2322e Mon Sep 17 00:00:00 2001 From: ImxYJL Date: Sun, 1 Dec 2024 18:41:43 +0900 Subject: [PATCH 13/24] =?UTF-8?q?fix:=20=EC=83=81=ED=83=9C=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=ED=99=94=20=EA=B0=92=EC=9D=B4=20=EB=B3=B5=EC=9B=90?= =?UTF-8?q?=EB=90=9C=20=EA=B0=92=EC=9D=84=20=EB=8D=AE=EC=96=B4=EC=94=8C?= =?UTF-8?q?=EC=9A=B0=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20updateVisite?= =?UTF-8?q?dCardIdList=EC=97=90=20=EC=A1=B0=EA=B1=B4=EB=AC=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20early=20return=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../progressBar/hooks/useStepList/index.ts | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/frontend/src/pages/ReviewWritingPage/progressBar/hooks/useStepList/index.ts b/frontend/src/pages/ReviewWritingPage/progressBar/hooks/useStepList/index.ts index 094a4d73d..e6b15fb37 100644 --- a/frontend/src/pages/ReviewWritingPage/progressBar/hooks/useStepList/index.ts +++ b/frontend/src/pages/ReviewWritingPage/progressBar/hooks/useStepList/index.ts @@ -72,21 +72,20 @@ const useStepList = ({ currentCardIndex }: UseStepListProps) => { }); const handleBeforeUnloadChange = useCallback(() => { - if (visitedCardIdList.length > 0) { - localStorage.setItem( - `${STORED_DATA_NAME.visitedCardIdList}_${reviewRequestCode}`, - JSON.stringify(visitedCardIdList), - ); - } + if (visitedCardIdList.length === 0) return; + + localStorage.setItem( + `${STORED_DATA_NAME.visitedCardIdList}_${reviewRequestCode}`, + JSON.stringify(visitedCardIdList), + ); }, [reviewRequestCode, visitedCardIdList]); // 복원 useEffect(() => { + if (cardSectionList.length === 0 || !reviewRequestCode) return; + const storedVisitedCardIdList = localStorage.getItem(`${STORED_DATA_NAME.visitedCardIdList}_${reviewRequestCode}`); - const defaultVisitedCardIdList = cardSectionList.length > 0 ? [cardSectionList[0].sectionId] : []; - const parsedVisitedCardIdList = storedVisitedCardIdList - ? JSON.parse(storedVisitedCardIdList) - : defaultVisitedCardIdList; + const parsedVisitedCardIdList = storedVisitedCardIdList ? JSON.parse(storedVisitedCardIdList) : []; setVisitedCardIdList(parsedVisitedCardIdList); }, [reviewRequestCode, cardSectionList]); @@ -97,6 +96,8 @@ const useStepList = ({ currentCardIndex }: UseStepListProps) => { }, [visitedCardIdList]); useEffect(() => { + if (cardSectionList.length === 0 || visitedCardIdList.length === 0) return; + updateVisitedCardIdList(); }, [cardSectionList, currentCardIndex]); From 25bb3c957f29b6568f2f578d9b3e1aea1b45eded Mon Sep 17 00:00:00 2001 From: ImxYJL Date: Sun, 1 Dec 2024 18:45:49 +0900 Subject: [PATCH 14/24] =?UTF-8?q?feat:=20RestoreAnswerCheckModal=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ReviewWritingPage/constants/modal.ts | 1 + .../RestoreAnswerCheckModal/index.tsx | 48 +++++++++++++++++++ .../RestoreAnswerCheckModal/style.ts | 26 ++++++++++ .../modals/components/index.tsx | 1 + 4 files changed, 76 insertions(+) create mode 100644 frontend/src/pages/ReviewWritingPage/modals/components/RestoreAnswerCheckModal/index.tsx create mode 100644 frontend/src/pages/ReviewWritingPage/modals/components/RestoreAnswerCheckModal/style.ts diff --git a/frontend/src/pages/ReviewWritingPage/constants/modal.ts b/frontend/src/pages/ReviewWritingPage/constants/modal.ts index 7ddda454e..601bb1f37 100644 --- a/frontend/src/pages/ReviewWritingPage/constants/modal.ts +++ b/frontend/src/pages/ReviewWritingPage/constants/modal.ts @@ -3,4 +3,5 @@ export const CARD_FORM_MODAL_KEY = { navigateConfirm: 'NAVIGATE_CONFIRM', recheck: 'RECHECK', submitError: 'SUBMIT_ERROR', + restoreConfirm: 'RESTORE_CONFIRM', }; diff --git a/frontend/src/pages/ReviewWritingPage/modals/components/RestoreAnswerCheckModal/index.tsx b/frontend/src/pages/ReviewWritingPage/modals/components/RestoreAnswerCheckModal/index.tsx new file mode 100644 index 000000000..13701ad6e --- /dev/null +++ b/frontend/src/pages/ReviewWritingPage/modals/components/RestoreAnswerCheckModal/index.tsx @@ -0,0 +1,48 @@ +import { ConfirmModal } from '@/components'; +import { useDeleteReviewInLocalStorage } from '@/pages/ReviewWritingPage/form/hooks'; + +import * as S from './style'; + +interface SubmitCheckModalProps { + restoreAnswer: () => void; + closeModal: () => void; +} + +const RestoreAnswerCheckModal = ({ restoreAnswer, closeModal }: SubmitCheckModalProps) => { + const { deleteAllReviewDataInLocalStorage } = useDeleteReviewInLocalStorage(); + + const handleRestoreButtonClick = () => { + restoreAnswer(); + closeModal(); + }; + + const handleDeleteButtonClick = () => { + deleteAllReviewDataInLocalStorage(); + closeModal(); + }; + + return ( + + + 작성했던 리뷰가 있어요 +

진행 상황을 복원할까요?

+

삭제를 선택하면 기존에 작성했던 내용이 삭제돼요

+
+
+ ); +}; + +export default RestoreAnswerCheckModal; diff --git a/frontend/src/pages/ReviewWritingPage/modals/components/RestoreAnswerCheckModal/style.ts b/frontend/src/pages/ReviewWritingPage/modals/components/RestoreAnswerCheckModal/style.ts new file mode 100644 index 000000000..a3efc5306 --- /dev/null +++ b/frontend/src/pages/ReviewWritingPage/modals/components/RestoreAnswerCheckModal/style.ts @@ -0,0 +1,26 @@ +import styled from '@emotion/styled'; + +import media from '@/utils/media'; + +export const RestoreAnswerCheckModal = styled.div` + display: flex; + flex-direction: column; + gap: 1rem; + align-items: center; + + width: max-content; + p { + width: fit-content; + text-align: center; + } + + ${media.xSmall} { + width: 23rem; + } +`; + +export const ConfirmModalTitle = styled.p` + margin-bottom: 1rem; + font-size: ${({ theme }) => theme.fontSize.medium}; + font-weight: ${({ theme }) => theme.fontWeight.bold}; +`; diff --git a/frontend/src/pages/ReviewWritingPage/modals/components/index.tsx b/frontend/src/pages/ReviewWritingPage/modals/components/index.tsx index 8026ace2c..513b9e471 100644 --- a/frontend/src/pages/ReviewWritingPage/modals/components/index.tsx +++ b/frontend/src/pages/ReviewWritingPage/modals/components/index.tsx @@ -2,3 +2,4 @@ export { default as AnswerListRecheckModal } from './AnswerListRecheckModal'; export { default as CardFormModalContainer } from './CardFormModalContainer'; export { default as SubmitCheckModal } from './SubmitCheckModal'; export { default as StrengthUnCheckModal } from './StrengthUnCheckModal'; +export { default as RestoreAnswerCheckModal } from './RestoreAnswerCheckModal'; From 61647cc7eba1201ad2b188eebbccd7bedbefdeb8 Mon Sep 17 00:00:00 2001 From: ImxYJL Date: Sun, 1 Dec 2024 20:45:15 +0900 Subject: [PATCH 15/24] =?UTF-8?q?refactor:=20useModals=EC=97=90=20?= =?UTF-8?q?=EB=AA=A8=EB=8B=AC=20=EA=B8=B0=EB=B3=B8=EA=B0=92=EC=9D=84=20opt?= =?UTF-8?q?ional=EB=A1=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/modal/useModals.ts | 12 +++++++----- .../modals/hooks/useCardFormModal.ts | 9 +++++++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/frontend/src/hooks/modal/useModals.ts b/frontend/src/hooks/modal/useModals.ts index d10113453..f66a24658 100644 --- a/frontend/src/hooks/modal/useModals.ts +++ b/frontend/src/hooks/modal/useModals.ts @@ -1,11 +1,13 @@ import { useState } from 'react'; -interface Modals { - [key: string]: boolean; +export type Modals = Record; + +interface useModalsProps { + initialStates?: Modals; } -const useModals = () => { - const [modals, setModals] = useState({}); +const useModals = ({ initialStates }: useModalsProps = {}) => { + const [modals, setModals] = useState(initialStates ?? {}); const openModal = (key: string) => { setModals((prev) => ({ @@ -21,7 +23,7 @@ const useModals = () => { })); }; - const isOpen = (key: string) => modals[key]; + const isOpen = (key: string) => !!modals[key]; return { isOpen, openModal, closeModal }; }; diff --git a/frontend/src/pages/ReviewWritingPage/modals/hooks/useCardFormModal.ts b/frontend/src/pages/ReviewWritingPage/modals/hooks/useCardFormModal.ts index a7e3a53ce..91ca0a3a9 100644 --- a/frontend/src/pages/ReviewWritingPage/modals/hooks/useCardFormModal.ts +++ b/frontend/src/pages/ReviewWritingPage/modals/hooks/useCardFormModal.ts @@ -1,9 +1,14 @@ import { useModals } from '@/hooks'; +import { Modals } from '@/hooks/modal/useModals'; import { CARD_FORM_MODAL_KEY } from '../../constants'; -const useCardFormModal = () => { - const { isOpen, openModal, closeModal } = useModals(); +interface useCardFormModalProps { + initialStates: Modals; +} + +const useCardFormModal = ({ initialStates }: useCardFormModalProps) => { + const { isOpen, openModal, closeModal } = useModals({ initialStates }); const handleOpenModal = (key: keyof typeof CARD_FORM_MODAL_KEY) => { openModal(CARD_FORM_MODAL_KEY[key]); From daf118370d3be8378dd55f2d34901ef356b07c1b Mon Sep 17 00:00:00 2001 From: ImxYJL Date: Sun, 1 Dec 2024 21:29:54 +0900 Subject: [PATCH 16/24] =?UTF-8?q?refactor:=20=EB=A1=9C=EC=BB=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=86=A0=EB=A6=AC=EC=A7=80=20=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?=EB=B0=8F=20=EB=B3=B5=EC=9B=90=20=EB=A1=9C=EC=A7=81=EC=9D=84=20?= =?UTF-8?q?=EB=B3=84=EB=8F=84=EC=9D=98=20=ED=9B=85=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../form/components/CardForm/index.tsx | 100 +++--------------- .../form/hooks/answers/index.ts | 2 + .../useRestoreReviewFromLocalStorage.ts | 44 ++++++++ .../answers/useSaveReviewToLocalStorage.ts | 70 ++++++++++++ 4 files changed, 130 insertions(+), 86 deletions(-) create mode 100644 frontend/src/pages/ReviewWritingPage/form/hooks/answers/useRestoreReviewFromLocalStorage.ts create mode 100644 frontend/src/pages/ReviewWritingPage/form/hooks/answers/useSaveReviewToLocalStorage.ts diff --git a/frontend/src/pages/ReviewWritingPage/form/components/CardForm/index.tsx b/frontend/src/pages/ReviewWritingPage/form/components/CardForm/index.tsx index d8e15aff9..1b1d2c9ec 100644 --- a/frontend/src/pages/ReviewWritingPage/form/components/CardForm/index.tsx +++ b/frontend/src/pages/ReviewWritingPage/form/components/CardForm/index.tsx @@ -1,20 +1,21 @@ -import { useCallback, useEffect } from 'react'; -import { useRecoilState, useSetRecoilState } from 'recoil'; +import { useEffect } from 'react'; +import { useSetRecoilState } from 'recoil'; -import { STORED_DATA_NAME } from '@/constants'; import { useSearchParamAndQuery } from '@/hooks'; import { useCurrentCardIndex, useResetFormRecoil, useUpdateDefaultAnswers, useLoadAndPrepareReview, + useSaveReviewToLocalStorage, + useRestoreFromLocalStorage, } from '@/pages/ReviewWritingPage/form/hooks'; import { CardFormModalContainer } from '@/pages/ReviewWritingPage/modals/components'; import useCardFormModal from '@/pages/ReviewWritingPage/modals/hooks/useCardFormModal'; import MobileProgressBar from '@/pages/ReviewWritingPage/progressBar/components/MobileProgressBar'; import ProgressBar from '@/pages/ReviewWritingPage/progressBar/components/ProgressBar'; import { CardSlider } from '@/pages/ReviewWritingPage/slider/components'; -import { answerMapAtom, answerValidationMapAtom, reviewRequestCodeAtom, selectedCategoryAtom } from '@/recoil'; +import { reviewRequestCodeAtom } from '@/recoil'; import { calculateParticle } from '@/utils'; import * as S from './styles'; @@ -24,92 +25,22 @@ const CardForm = () => { paramKey: 'reviewRequestCode', }); - const [selectedCategory, setSelectedCategory] = useRecoilState(selectedCategoryAtom); - const [answerMap, setAnswerMap] = useRecoilState(answerMapAtom); - const [answerValidation, setAnswerValidation] = useRecoilState(answerValidationMapAtom); - - // 로컬 스토리지의 값으로 전역 상태 복원 - useEffect(() => { - const storedSelectedCategories = localStorage.getItem( - `${STORED_DATA_NAME.selectedCategories}_${reviewRequestCode}`, - ); - const storedAnswerValidations = localStorage.getItem(`${STORED_DATA_NAME.answerValidations}_${reviewRequestCode}`); - const storedAnswers = localStorage.getItem(`${STORED_DATA_NAME.answers}_${reviewRequestCode}`); - - const selectedCategories = storedSelectedCategories ? JSON.parse(storedSelectedCategories) : null; - const answerValidations = storedAnswerValidations ? new Map(JSON.parse(storedAnswerValidations)) : new Map(); - const answers = storedAnswers ? JSON.parse(storedAnswers) : null; - - setSelectedCategory(selectedCategories); - setAnswerValidation(answerValidations); - setAnswerMap(new Map(answers)); - }, []); - - // 작성했던 내용들을 저장하는 로직 - const getCurrentSelectedCategory = () => { - if (selectedCategory && selectedCategory.length > 0) { - return selectedCategory; - } - }; - - const getAnswerValidation = () => { - if (answerValidation && answerValidation.size > 0) { - const plainObjectAnswers = Array.from(answerValidation.entries()); - return plainObjectAnswers.length > 0 ? plainObjectAnswers : null; - } - }; - - const getCurrentAnswers = () => { - if (answerMap) { - const plainObjectAnswers = Array.from(answerMap.entries()); - return plainObjectAnswers.length > 0 ? plainObjectAnswers : null; - } - }; - - const handleBeforeUnloadChange = useCallback(() => { - const selectedCategories = getCurrentSelectedCategory(); - const answers = getCurrentAnswers(); - const answerValidations = getAnswerValidation(); - - if (selectedCategories) { - localStorage.setItem( - `${STORED_DATA_NAME.selectedCategories}_${reviewRequestCode}`, - JSON.stringify(selectedCategories), - ); - } - if (answerValidations) { - localStorage.setItem( - `${STORED_DATA_NAME.answerValidations}_${reviewRequestCode}`, - JSON.stringify(answerValidations), - ); - } - - if (answers) { - localStorage.setItem(`${STORED_DATA_NAME.answers}_${reviewRequestCode}`, JSON.stringify(answers)); - } - }, [selectedCategory, answerMap, answerValidation, reviewRequestCode]); - - // 실시간 답변 상태를 로컬 스토리지에 저장 - useEffect(() => { - handleBeforeUnloadChange(); - }, [handleBeforeUnloadChange]); + const { resetFormRecoil } = useResetFormRecoil(); + const { currentCardIndex, handleCurrentCardIndex } = useCurrentCardIndex(); - ////////////////// 기존 로직 + // 로컬 스토리지에 저장된 값을 기반으로 모달의 isOpen 여부 설정 + const { restoreData, initialModalsState } = useRestoreFromLocalStorage(); + const { handleOpenModal, closeModal, isOpen } = useCardFormModal({ initialStates: initialModalsState }); const setReviewRequestCode = useSetRecoilState(reviewRequestCodeAtom); - const { currentCardIndex, handleCurrentCardIndex } = useCurrentCardIndex(); - // 프로젝트 정보 및 질문지를 서버에서 가져옴 const { revieweeName, projectName } = useLoadAndPrepareReview({ reviewRequestCode }); // 생성된 질문지를 바탕으로 답변 기본값 및 답변의 유효성 기본값 설정 useUpdateDefaultAnswers(); - // 모달 - const { handleOpenModal, closeModal, isOpen } = useCardFormModal(); - - const { resetFormRecoil } = useResetFormRecoil(); + useSaveReviewToLocalStorage(); useEffect(() => { if (reviewRequestCode) setReviewRequestCode(reviewRequestCode); @@ -127,6 +58,8 @@ const CardForm = () => { particles: { withFinalConsonant: '을', withoutFinalConsonant: '를' }, })} 리뷰해주세요!`; + const handleRestoreAnswers = () => restoreData(); + return ( @@ -151,12 +84,7 @@ const CardForm = () => { handleOpenModal={handleOpenModal} /> - {}} - // handleRestoreButtonClick={handleRestoreAnswers} - /> + ); }; diff --git a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/index.ts b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/index.ts index 7709dd4d4..b5e237cd1 100644 --- a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/index.ts +++ b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/index.ts @@ -4,3 +4,5 @@ export { default as useUpdateDefaultAnswers } from './useUpdateDefaultAnswers'; export { default as useUpdateReviewerAnswer } from './useUpdateReviewerAnswer'; export { default as useSubmitAnswers } from './useSubmitAnswers'; export { default as useDeleteReviewInLocalStorage } from './useDeleteReviewInLocalStorage'; +export { default as useSaveReviewToLocalStorage } from './useSaveReviewToLocalStorage'; +export { default as useRestoreFromLocalStorage } from './useRestoreReviewFromLocalStorage'; diff --git a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useRestoreReviewFromLocalStorage.ts b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useRestoreReviewFromLocalStorage.ts new file mode 100644 index 000000000..dc7e2aba8 --- /dev/null +++ b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useRestoreReviewFromLocalStorage.ts @@ -0,0 +1,44 @@ +import { useCallback } from 'react'; +import { useSetRecoilState } from 'recoil'; + +import { STORED_DATA_NAME } from '@/constants'; +import { useSearchParamAndQuery } from '@/hooks'; +import { CARD_FORM_MODAL_KEY } from '@/pages/ReviewWritingPage/constants'; +import { selectedCategoryAtom, answerMapAtom, answerValidationMapAtom } from '@/recoil'; + +const useRestoreFromLocalStorage = () => { + const setSelectedCategory = useSetRecoilState(selectedCategoryAtom); + const setAnswerMap = useSetRecoilState(answerMapAtom); + const setAnswerValidation = useSetRecoilState(answerValidationMapAtom); + + const { param: reviewRequestCode } = useSearchParamAndQuery({ + paramKey: 'reviewRequestCode', + }); + + const initialModalsState = { + [CARD_FORM_MODAL_KEY.restoreConfirm]: + !!localStorage.getItem(`${STORED_DATA_NAME.selectedCategories}_${reviewRequestCode}`) || + !!localStorage.getItem(`${STORED_DATA_NAME.answers}_${reviewRequestCode}`) || + !!localStorage.getItem(`${STORED_DATA_NAME.answerValidations}_${reviewRequestCode}`), + }; + + const restoreData = useCallback(() => { + const storedSelectedCategories = localStorage.getItem( + `${STORED_DATA_NAME.selectedCategories}_${reviewRequestCode}`, + ); + const storedAnswerValidations = localStorage.getItem(`${STORED_DATA_NAME.answerValidations}_${reviewRequestCode}`); + const storedAnswers = localStorage.getItem(`${STORED_DATA_NAME.answers}_${reviewRequestCode}`); + + const selectedCategories = storedSelectedCategories ? JSON.parse(storedSelectedCategories) : null; + const answerValidations = storedAnswerValidations ? new Map(JSON.parse(storedAnswerValidations)) : new Map(); + const answers = storedAnswers ? JSON.parse(storedAnswers) : null; + + setSelectedCategory(selectedCategories); + setAnswerValidation(answerValidations); + setAnswerMap(new Map(answers)); + }, [reviewRequestCode, setSelectedCategory, setAnswerMap, setAnswerValidation]); + + return { restoreData, initialModalsState }; +}; + +export default useRestoreFromLocalStorage; diff --git a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useSaveReviewToLocalStorage.ts b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useSaveReviewToLocalStorage.ts new file mode 100644 index 000000000..939697488 --- /dev/null +++ b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useSaveReviewToLocalStorage.ts @@ -0,0 +1,70 @@ +import { useCallback, useEffect } from 'react'; +import { useRecoilValue } from 'recoil'; + +import { STORED_DATA_NAME } from '@/constants'; +import { useSearchParamAndQuery } from '@/hooks'; +import { answerMapAtom, answerValidationMapAtom, selectedCategoryAtom } from '@/recoil'; + +/** + * 리뷰와 관련된 데이터들을 실시간으로 로컬 스토리지에 저장하는 훅 + */ +const useSaveReviewToLocalStorage = () => { + const selectedCategory = useRecoilValue(selectedCategoryAtom); + const answerMap = useRecoilValue(answerMapAtom); + const answerValidation = useRecoilValue(answerValidationMapAtom); + + const { param: reviewRequestCode } = useSearchParamAndQuery({ + paramKey: 'reviewRequestCode', + }); + + const getCurrentSelectedCategory = useCallback(() => { + if (!selectedCategory || selectedCategory.length === 0) return null; + + return selectedCategory; + }, [selectedCategory]); + + const getCurrentAnswerValidation = useCallback(() => { + if (!answerValidation || answerValidation.size === 0) return null; + + const plainObjectAnswers = Array.from(answerValidation.entries()); + return plainObjectAnswers.length > 0 ? plainObjectAnswers : null; + }, [answerValidation]); + + const getCurrentAnswers = useCallback(() => { + if (!answerMap || answerMap.size === 0) return null; + + const plainObjectAnswers = Array.from(answerMap.entries()); + return plainObjectAnswers.length > 0 ? plainObjectAnswers : null; + }, [answerMap]); + + const saveToLocalStorage = useCallback(() => { + const selectedCategories = getCurrentSelectedCategory(); + const answers = getCurrentAnswers(); + const answerValidations = getCurrentAnswerValidation(); + + if (selectedCategories) { + localStorage.setItem( + `${STORED_DATA_NAME.selectedCategories}_${reviewRequestCode}`, + JSON.stringify(selectedCategories), + ); + } + + if (answerValidations) { + localStorage.setItem( + `${STORED_DATA_NAME.answerValidations}_${reviewRequestCode}`, + JSON.stringify(answerValidations), + ); + } + + if (answers) { + localStorage.setItem(`${STORED_DATA_NAME.answers}_${reviewRequestCode}`, JSON.stringify(answers)); + } + }, [getCurrentSelectedCategory, getCurrentAnswers, getCurrentAnswerValidation, reviewRequestCode]); + + // 로컬 스토리지 동기화 + useEffect(() => { + saveToLocalStorage(); + }, [saveToLocalStorage]); +}; + +export default useSaveReviewToLocalStorage; From b590349c71d8b5867c9fd87f27f33a169f0ee168 Mon Sep 17 00:00:00 2001 From: ImxYJL Date: Sun, 1 Dec 2024 21:31:32 +0900 Subject: [PATCH 17/24] =?UTF-8?q?chore:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=ED=9B=85=20=EC=82=AC=EC=9A=A9=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/HighlightEditor/hooks/useEditableState.ts | 2 +- .../ReviewWritingPage/form/components/CardForm/index.tsx | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/frontend/src/components/highlight/components/HighlightEditor/hooks/useEditableState.ts b/frontend/src/components/highlight/components/HighlightEditor/hooks/useEditableState.ts index b08eb1f77..be573f3be 100644 --- a/frontend/src/components/highlight/components/HighlightEditor/hooks/useEditableState.ts +++ b/frontend/src/components/highlight/components/HighlightEditor/hooks/useEditableState.ts @@ -1,4 +1,4 @@ -import { useEffect, useLayoutEffect, useState } from 'react'; +import { useLayoutEffect, useState } from 'react'; import { HIGHLIGHT_EVENT_NAME, LOCAL_STORAGE_KEY } from '@/constants'; import { trackEventInAmplitude } from '@/utils'; diff --git a/frontend/src/pages/ReviewWritingPage/form/components/CardForm/index.tsx b/frontend/src/pages/ReviewWritingPage/form/components/CardForm/index.tsx index 1b1d2c9ec..e5593a0aa 100644 --- a/frontend/src/pages/ReviewWritingPage/form/components/CardForm/index.tsx +++ b/frontend/src/pages/ReviewWritingPage/form/components/CardForm/index.tsx @@ -1,5 +1,4 @@ import { useEffect } from 'react'; -import { useSetRecoilState } from 'recoil'; import { useSearchParamAndQuery } from '@/hooks'; import { @@ -15,7 +14,6 @@ import useCardFormModal from '@/pages/ReviewWritingPage/modals/hooks/useCardForm import MobileProgressBar from '@/pages/ReviewWritingPage/progressBar/components/MobileProgressBar'; import ProgressBar from '@/pages/ReviewWritingPage/progressBar/components/ProgressBar'; import { CardSlider } from '@/pages/ReviewWritingPage/slider/components'; -import { reviewRequestCodeAtom } from '@/recoil'; import { calculateParticle } from '@/utils'; import * as S from './styles'; @@ -32,8 +30,6 @@ const CardForm = () => { const { restoreData, initialModalsState } = useRestoreFromLocalStorage(); const { handleOpenModal, closeModal, isOpen } = useCardFormModal({ initialStates: initialModalsState }); - const setReviewRequestCode = useSetRecoilState(reviewRequestCodeAtom); - // 프로젝트 정보 및 질문지를 서버에서 가져옴 const { revieweeName, projectName } = useLoadAndPrepareReview({ reviewRequestCode }); @@ -42,10 +38,6 @@ const CardForm = () => { useSaveReviewToLocalStorage(); - useEffect(() => { - if (reviewRequestCode) setReviewRequestCode(reviewRequestCode); - }, [reviewRequestCode]); - useEffect(() => { return () => { // 페이지 나갈 때 관련 recoil 상태 초기화 From 1195b04d5acb523c671f59d9d70c1ad09cc79896 Mon Sep 17 00:00:00 2001 From: ImxYJL Date: Mon, 2 Dec 2024 15:19:35 +0900 Subject: [PATCH 18/24] =?UTF-8?q?chore:=20index=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EC=97=90=20default=20export=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/CardFormModalContainer/index.tsx | 15 +++++++++------ .../ReviewWritingPage/modals/components/index.tsx | 1 + 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/frontend/src/pages/ReviewWritingPage/modals/components/CardFormModalContainer/index.tsx b/frontend/src/pages/ReviewWritingPage/modals/components/CardFormModalContainer/index.tsx index 18519aefe..493f8dfe1 100644 --- a/frontend/src/pages/ReviewWritingPage/modals/components/CardFormModalContainer/index.tsx +++ b/frontend/src/pages/ReviewWritingPage/modals/components/CardFormModalContainer/index.tsx @@ -6,21 +6,18 @@ import { CARD_FORM_MODAL_KEY } from '@/pages/ReviewWritingPage/constants'; import { AnswerListRecheckModal, SubmitCheckModal, + SubmitErrorModal, + RestoreAnswerCheckModal, } from '@/pages/ReviewWritingPage/modals/components'; import { answerMapAtom, cardSectionListSelector } from '@/recoil'; -import SubmitErrorModal from '../SubmitErrorModal'; - interface CardFormModalContainerProps { isOpen: (key: string) => boolean; closeModal: (key: string) => void; handleRestoreButtonClick: () => void; } -const CardFormModalContainer = ({ - isOpen, - closeModal, -}: CardFormModalContainerProps) => { +const CardFormModalContainer = ({ isOpen, closeModal, handleRestoreButtonClick }: CardFormModalContainerProps) => { const answerMap = useRecoilValue(answerMapAtom); const cardSectionList = useRecoilValue(cardSectionListSelector); @@ -53,6 +50,12 @@ const CardFormModalContainer = ({ closeModal={() => closeModal(CARD_FORM_MODAL_KEY.recheck)} /> )} + {isOpen(CARD_FORM_MODAL_KEY.restoreConfirm) && ( + closeModal(CARD_FORM_MODAL_KEY.restoreConfirm)} + > + )} ); }; diff --git a/frontend/src/pages/ReviewWritingPage/modals/components/index.tsx b/frontend/src/pages/ReviewWritingPage/modals/components/index.tsx index 513b9e471..e5a3eaa2d 100644 --- a/frontend/src/pages/ReviewWritingPage/modals/components/index.tsx +++ b/frontend/src/pages/ReviewWritingPage/modals/components/index.tsx @@ -3,3 +3,4 @@ export { default as CardFormModalContainer } from './CardFormModalContainer'; export { default as SubmitCheckModal } from './SubmitCheckModal'; export { default as StrengthUnCheckModal } from './StrengthUnCheckModal'; export { default as RestoreAnswerCheckModal } from './RestoreAnswerCheckModal'; +export { default as SubmitErrorModal } from './SubmitErrorModal'; From 3a5ab6123cd736ab582bbe338de947319a612c64 Mon Sep 17 00:00:00 2001 From: ImxYJL Date: Mon, 2 Dec 2024 16:56:50 +0900 Subject: [PATCH 19/24] =?UTF-8?q?test:=20useNavigation=EC=9D=84=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EB=8A=94=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=97=90=20BrowserRouter=20Wrapper=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../progressBar/hooks/useStepList/test.tsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/frontend/src/pages/ReviewWritingPage/progressBar/hooks/useStepList/test.tsx b/frontend/src/pages/ReviewWritingPage/progressBar/hooks/useStepList/test.tsx index 7217a17b8..f56143050 100644 --- a/frontend/src/pages/ReviewWritingPage/progressBar/hooks/useStepList/test.tsx +++ b/frontend/src/pages/ReviewWritingPage/progressBar/hooks/useStepList/test.tsx @@ -1,4 +1,5 @@ import { renderHook, waitFor } from '@testing-library/react'; +import { BrowserRouter } from 'react-router-dom'; import { RecoilRoot, RecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import { REVIEW_QUESTION_DATA, STRENGTH_SECTION_LIST } from '@/mocks/mockData'; @@ -23,13 +24,15 @@ interface RenderUseStepListHookProps { const renderUseStepListHook = ({ currentCardIndex }: RenderUseStepListHookProps) => { const wrapper = ({ children }: EssentialPropsWithChildren) => ( - { - set(reviewWritingFormSectionListAtom, REVIEW_QUESTION_DATA.sections); - }} - > - {children} - + + { + set(reviewWritingFormSectionListAtom, REVIEW_QUESTION_DATA.sections); + }} + > + {children} + + ); return renderHook( From 2a44ab9e7fe63e026c63c0296bfd4d2808b2fdfd Mon Sep 17 00:00:00 2001 From: ImxYJL Date: Mon, 9 Dec 2024 21:22:09 +0900 Subject: [PATCH 20/24] =?UTF-8?q?chore:=20=EB=A1=9C=EC=BB=AC=20=EC=8A=A4?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=EC=A7=80=20=EC=A0=80=EC=9E=A5=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=9D=B4=EB=A6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ReviewWritingPage/progressBar/hooks/useStepList/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/ReviewWritingPage/progressBar/hooks/useStepList/index.ts b/frontend/src/pages/ReviewWritingPage/progressBar/hooks/useStepList/index.ts index e6b15fb37..94e1ffcf9 100644 --- a/frontend/src/pages/ReviewWritingPage/progressBar/hooks/useStepList/index.ts +++ b/frontend/src/pages/ReviewWritingPage/progressBar/hooks/useStepList/index.ts @@ -71,7 +71,7 @@ const useStepList = ({ currentCardIndex }: UseStepListProps) => { paramKey: 'reviewRequestCode', }); - const handleBeforeUnloadChange = useCallback(() => { + const storeVisitedCardIdList = useCallback(() => { if (visitedCardIdList.length === 0) return; localStorage.setItem( @@ -92,7 +92,7 @@ const useStepList = ({ currentCardIndex }: UseStepListProps) => { // 로컬 스토리지와의 동기화를 위한 useEffect useEffect(() => { - handleBeforeUnloadChange(); + storeVisitedCardIdList(); }, [visitedCardIdList]); useEffect(() => { From c7c43421164163845a2c4e189199c2d25beaee8d Mon Sep 17 00:00:00 2001 From: ImxYJL Date: Fri, 13 Dec 2024 20:30:24 +0900 Subject: [PATCH 21/24] =?UTF-8?q?chore:=20=EC=9D=B8=ED=84=B0=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EC=9D=B4=EB=A6=84=EC=9D=84=20PascalCase?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/modal/useModals.ts | 4 ++-- .../form/hooks/answers/multipleChoice/useMultipleChoice.ts | 4 ++-- .../form/hooks/answers/useTextAnswer/index.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/hooks/modal/useModals.ts b/frontend/src/hooks/modal/useModals.ts index f66a24658..f5d82ead7 100644 --- a/frontend/src/hooks/modal/useModals.ts +++ b/frontend/src/hooks/modal/useModals.ts @@ -2,11 +2,11 @@ import { useState } from 'react'; export type Modals = Record; -interface useModalsProps { +interface UseModalsProps { initialStates?: Modals; } -const useModals = ({ initialStates }: useModalsProps = {}) => { +const useModals = ({ initialStates }: UseModalsProps = {}) => { const [modals, setModals] = useState(initialStates ?? {}); const openModal = (key: string) => { diff --git a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/multipleChoice/useMultipleChoice.ts b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/multipleChoice/useMultipleChoice.ts index 9417a8fb3..755f45e04 100644 --- a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/multipleChoice/useMultipleChoice.ts +++ b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/multipleChoice/useMultipleChoice.ts @@ -40,13 +40,13 @@ const useMultipleChoice = ({ question, handleModalOpen }: UseMultipleChoiceProps }, ); - interface findSelectedOptionIdsParams { + interface FindSelectedOptionIdsParams { answerMap: Map | null; questionId: number; } // 로컬 스토리지에 저장했던 답변으로부터, questionId를 통해 해당 질문의 selectedOptionIds를 찾는 함수 - const findSelectedOptionIds = ({ answerMap, questionId }: findSelectedOptionIdsParams) => { + const findSelectedOptionIds = ({ answerMap, questionId }: FindSelectedOptionIdsParams) => { if (!answerMap) return null; for (const [, value] of answerMap) { diff --git a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useTextAnswer/index.ts b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useTextAnswer/index.ts index 37f925529..cbe81da50 100644 --- a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useTextAnswer/index.ts +++ b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/useTextAnswer/index.ts @@ -30,11 +30,11 @@ const useTextAnswer = ({ question }: UseTextAnswerProps) => { // 로컬 스토리지에 저장했던 답변으로부터, questionId를 통해 해당 질문의 서술형 답변을 찾는 함수 // TODO: 복원을 위한 find 함수들을 별도 유틸로 분리 및 통합 - interface findTextAnswerParams { + interface FindTextAnswerParams { answerMap: Map | null; questionId: number; } - const findTextAnswer = ({ answerMap, questionId }: findTextAnswerParams) => { + const findTextAnswer = ({ answerMap, questionId }: FindTextAnswerParams) => { if (!answerMap) return null; for (const [, value] of answerMap) { From 567b1195bd7c9ec433f88a78626a88d1c1d119e8 Mon Sep 17 00:00:00 2001 From: ImxYJL Date: Fri, 13 Dec 2024 20:34:07 +0900 Subject: [PATCH 22/24] =?UTF-8?q?chore:=20=ED=83=80=EC=9E=85,=20=EC=9D=B8?= =?UTF-8?q?=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EC=9D=B4=EB=A6=84?= =?UTF-8?q?=EC=9D=80=20PascalCase=EB=A5=BC=20=EA=B0=95=EC=A0=9C=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20eslint=20=EA=B7=9C=EC=B9=99=EC=B9=99=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/.eslintrc.cjs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs index dd825e3f4..9825392e2 100644 --- a/frontend/.eslintrc.cjs +++ b/frontend/.eslintrc.cjs @@ -56,6 +56,17 @@ module.exports = { }, }, ], + '@typescript-eslint/naming-convention': [ + 'error', + { + selector: 'interface', + format: ['PascalCase'], + }, + { + selector: 'typeAlias', + format: ['PascalCase'], + }, + ], }, settings: { 'import/resolver': { From d5947ae16e7006424559ce41c398125479d38f2c Mon Sep 17 00:00:00 2001 From: ImxYJL Date: Fri, 13 Dec 2024 20:47:19 +0900 Subject: [PATCH 23/24] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20Map=20=EB=B0=98=EB=B3=B5=EB=AC=B8=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hooks/answers/multipleChoice/useMultipleChoice.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/multipleChoice/useMultipleChoice.ts b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/multipleChoice/useMultipleChoice.ts index 755f45e04..348999cca 100644 --- a/frontend/src/pages/ReviewWritingPage/form/hooks/answers/multipleChoice/useMultipleChoice.ts +++ b/frontend/src/pages/ReviewWritingPage/form/hooks/answers/multipleChoice/useMultipleChoice.ts @@ -49,12 +49,8 @@ const useMultipleChoice = ({ question, handleModalOpen }: UseMultipleChoiceProps const findSelectedOptionIds = ({ answerMap, questionId }: FindSelectedOptionIdsParams) => { if (!answerMap) return null; - for (const [, value] of answerMap) { - if (value.questionId === questionId) { - return value.selectedOptionIds; - } - } - return null; + const selectedItem = answerMap.get(questionId); + return selectedItem ? selectedItem.selectedOptionIds : null; }; // 저장된 객관식 답변이 있다면 복원 From 339c387cf745678f9065fa2e6488079480194334 Mon Sep 17 00:00:00 2001 From: ImxYJL Date: Sat, 14 Dec 2024 16:37:56 +0900 Subject: [PATCH 24/24] =?UTF-8?q?chore:=20=EC=9D=B8=ED=84=B0=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EC=9D=B4=EB=A6=84=20PascalCase=EB=A1=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/ReviewWritingPage/modals/hooks/useCardFormModal.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/ReviewWritingPage/modals/hooks/useCardFormModal.ts b/frontend/src/pages/ReviewWritingPage/modals/hooks/useCardFormModal.ts index 91ca0a3a9..fea77f018 100644 --- a/frontend/src/pages/ReviewWritingPage/modals/hooks/useCardFormModal.ts +++ b/frontend/src/pages/ReviewWritingPage/modals/hooks/useCardFormModal.ts @@ -3,11 +3,11 @@ import { Modals } from '@/hooks/modal/useModals'; import { CARD_FORM_MODAL_KEY } from '../../constants'; -interface useCardFormModalProps { +interface UseCardFormModalProps { initialStates: Modals; } -const useCardFormModal = ({ initialStates }: useCardFormModalProps) => { +const useCardFormModal = ({ initialStates }: UseCardFormModalProps) => { const { isOpen, openModal, closeModal } = useModals({ initialStates }); const handleOpenModal = (key: keyof typeof CARD_FORM_MODAL_KEY) => {