Skip to content

Commit

Permalink
[FE] refactor: 리뷰 작성 페이지 리팩토링 (#219)
Browse files Browse the repository at this point in the history
* refactor: 각 모달별로 상태와 동작을 관리하는 훅 분리

* refactor: 리뷰 작성 폼의 로직을 훅으로 분리

* refactor: 리뷰 작성 페이지에 분리한 훅 적용

* refactor: URL에서 reviewRequestCode를 추출하는 로직을 훅으로 분리

* chore: 불필요한 테스트용 코드 제거

* chore: ErrorModal을 닫을 때 errorMessage 상태도 초기값으로 변경하도록 수정
  • Loading branch information
chysis authored Aug 6, 2024
1 parent c1e5bab commit dbeb7c4
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 55 deletions.
16 changes: 16 additions & 0 deletions frontend/src/hooks/useAlertModal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useState } from 'react';

const useAlertModal = () => {
const [isAlertModalOpen, setIsAlertModalOpen] = useState(false);

const openAlertModal = () => setIsAlertModalOpen(true);
const closeAlertModal = () => setIsAlertModalOpen(false);

return {
isAlertModalOpen,
openAlertModal,
closeAlertModal,
};
};

export default useAlertModal;
16 changes: 16 additions & 0 deletions frontend/src/hooks/useConfirmModal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useState } from 'react';

const useConfirmModal = () => {
const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false);

const openConfirmModal = () => setIsConfirmModalOpen(true);
const closeConfirmModal = () => setIsConfirmModalOpen(false);

return {
isConfirmModalOpen,
openConfirmModal,
closeConfirmModal,
};
};

export default useConfirmModal;
25 changes: 25 additions & 0 deletions frontend/src/hooks/useErrorModal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useState } from 'react';

const useErrorModal = () => {
const [isErrorModalOpen, setIsErrorModalOpen] = useState(false);
const [errorMessage, setErrorMessage] = useState('');

const openErrorModal = (errorMessage: string) => {
setErrorMessage(errorMessage);
setIsErrorModalOpen(true);
};

const closeErrorModal = () => {
setErrorMessage('');
setIsErrorModalOpen(false);
};

return {
isErrorModalOpen,
errorMessage,
openErrorModal,
closeErrorModal,
};
};

export default useErrorModal;
59 changes: 59 additions & 0 deletions frontend/src/hooks/useReviewForm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { useEffect, useState } from 'react';

import { getDataToWriteReviewApi } from '@/apis/review';
import { REVIEW } from '@/constants';
import { Keyword, ReviewContent, WritingReviewInfoData } from '@/types';

interface UseReviewFormProps {
reviewRequestCode: string;
openErrorModal: (errorMessage: string) => void;
}

const useReviewForm = ({ reviewRequestCode, openErrorModal }: UseReviewFormProps) => {
const [dataToWrite, setDataToWrite] = useState<WritingReviewInfoData | null>(null);
const [answers, setAnswers] = useState<ReviewContent[]>([]);
const [selectedKeywords, setSelectedKeywords] = useState<number[]>([]);

const isValidAnswersLength = !answers.some((id) => id.answer.length < REVIEW.answerMinLength);
const isValidKeywordSelection =
selectedKeywords.length >= REVIEW.keywordMinCount && selectedKeywords.length <= REVIEW.keywordMaxCount;
const isValidForm = isValidAnswersLength && isValidKeywordSelection;

useEffect(() => {
const getDataToWrite = async () => {
const data = await getDataToWriteReviewApi(reviewRequestCode);
setDataToWrite(data);
setAnswers(data.questions.map((question) => ({ questionId: question.id, answer: '' })));
};

getDataToWrite();
}, [reviewRequestCode]);

const handleAnswerChange = (questionId: number, value: string) => {
setAnswers((prev) =>
prev.map((answer) => (answer.questionId === questionId ? { ...answer, answer: value } : answer)),
);
};

const handleKeywordButtonClick = (keyword: Keyword) => {
if (selectedKeywords.length === REVIEW.keywordMaxCount && !selectedKeywords.includes(keyword.id)) {
openErrorModal('키워드는 최대 5개까지 선택할 수 있어요.');
return;
}

setSelectedKeywords((prev) =>
prev.includes(keyword.id) ? selectedKeywords.filter((id) => id !== keyword.id) : [...prev, keyword.id],
);
};

return {
dataToWrite,
answers,
selectedKeywords,
isValidForm,
handleAnswerChange,
handleKeywordButtonClick,
};
};

export default useReviewForm;
11 changes: 11 additions & 0 deletions frontend/src/hooks/useReviewRequestCode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useLocation } from 'react-router';

const useReviewRequestCode = () => {
const location = useLocation();
const params = location.pathname.split('/');
const reviewRequestCode = params.slice(-1).toString();

return { reviewRequestCode };
};

export default useReviewRequestCode;
76 changes: 21 additions & 55 deletions frontend/src/pages/ReviewWriting/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { useEffect, useState } from 'react';
import { useLocation, useNavigate } from 'react-router';
import { useNavigate } from 'react-router';

import { getDataToWriteReviewApi, postReviewApi } from '@/apis/review';
import { postReviewApi } from '@/apis/review';
// import ClockLogo from '@/assets/clock.svg';
import GithubLogoIcon from '@/assets/githubLogo.svg';
import { ConfirmModal, ErrorAlertModal } from '@/components';
import Button from '@/components/common/Button';
import { REVIEW } from '@/constants/review';
import { Keyword, ReviewContent, ReviewData, WritingReviewInfoData } from '@/types';
import useConfirmModal from '@/hooks/useConfirmModal';
import useErrorModal from '@/hooks/useErrorModal';
import useReviewForm from '@/hooks/useReviewForm';
import useReviewRequestCode from '@/hooks/useReviewRequestCode';
import { ReviewData } from '@/types';

import LoadingPage from '../LoadingPage';

Expand All @@ -22,53 +24,17 @@ const SUBMIT_CONFIRM_MESSAGE = `리뷰를 제출할까요?
const ReviewWritingPage = () => {
const navigate = useNavigate();

const [dataToWrite, setDataToWrite] = useState<WritingReviewInfoData | null>(null);
const [answers, setAnswers] = useState<ReviewContent[]>([]);
const [selectedKeywords, setSelectedKeywords] = useState<number[]>([]);
const { reviewRequestCode } = useReviewRequestCode();

const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false);
const [isErrorModalOpen, setIsErrorModalOpen] = useState(false);
const { isConfirmModalOpen, openConfirmModal, closeConfirmModal } = useConfirmModal();
const { isErrorModalOpen, errorMessage, openErrorModal, closeErrorModal } = useErrorModal();

const isValidAnswersLength = !answers.some((id) => id.answer.length < REVIEW.answerMinLength);
const isValidKeywordSelection =
selectedKeywords.length >= REVIEW.keywordMinCount && selectedKeywords.length <= REVIEW.keywordMaxCount;
const isValidForm = isValidAnswersLength && isValidKeywordSelection;

const location = useLocation();
const params = location.pathname.split('/');
const reviewRequestCode = params.slice(-1).toString();

useEffect(() => {
const getDataToWrite = async () => {
const data = await getDataToWriteReviewApi(reviewRequestCode);
setDataToWrite(data);
setAnswers(data.questions.map((question) => ({ questionId: question.id, answer: '' })));
};

getDataToWrite();
}, []);

const handleAnswerChange = (questionId: number, value: string) => {
setAnswers((prev) =>
prev.map((answer) => (answer.questionId === questionId ? { ...answer, answer: value } : answer)),
);
};

const handleKeywordButtonClick = (keyword: Keyword) => {
if (selectedKeywords.length === REVIEW.keywordMaxCount && !selectedKeywords.includes(keyword.id)) {
alert('키워드는 최대 5개까지 선택할 수 있어요.');
return;
}

setSelectedKeywords((prev) =>
prev.includes(keyword.id) ? selectedKeywords.filter((id) => id !== keyword.id) : [...prev, keyword.id],
);
};
const { dataToWrite, answers, selectedKeywords, isValidForm, handleAnswerChange, handleKeywordButtonClick } =
useReviewForm({ reviewRequestCode, openErrorModal });

const handleClickSubmitButton = async (event: React.FormEvent) => {
event.preventDefault();

setIsConfirmModalOpen(true);
openConfirmModal();
};

const handleSubmitReview = async () => {
Expand All @@ -80,11 +46,11 @@ const ReviewWritingPage = () => {

try {
await postReviewApi({ reviewData });
setIsConfirmModalOpen(false);
closeConfirmModal();
navigate('/user/review-writing-complete', { replace: true });
} catch (error) {
setIsConfirmModalOpen(false);
setIsErrorModalOpen(true);
closeConfirmModal();
openErrorModal('리뷰를 제출할 수 없어요.');
}
};

Expand Down Expand Up @@ -149,18 +115,18 @@ const ReviewWritingPage = () => {
{isConfirmModalOpen && (
<ConfirmModal
confirmButton={{ type: 'primary', text: '확인', handleClick: handleSubmitReview }}
cancelButton={{ type: 'secondary', text: '취소', handleClick: () => setIsConfirmModalOpen(false) }}
handleClose={() => setIsConfirmModalOpen(false)}
cancelButton={{ type: 'secondary', text: '취소', handleClick: closeConfirmModal }}
handleClose={closeConfirmModal}
isClosableOnBackground={true}
>
{SUBMIT_CONFIRM_MESSAGE}
</ConfirmModal>
)}
{isErrorModalOpen && (
<ErrorAlertModal
errorText="오류로 인해 리뷰를 제출할 수 없어요."
closeButton={{ content: '닫기', type: 'primary', handleClick: () => setIsErrorModalOpen(false) }}
handleClose={() => setIsErrorModalOpen(false)}
errorText={errorMessage}
closeButton={{ content: '닫기', type: 'primary', handleClick: closeErrorModal }}
handleClose={closeErrorModal}
/>
)}
</S.ReviewWritingPage>
Expand Down

0 comments on commit dbeb7c4

Please sign in to comment.