diff --git a/src/apis/client.ts b/src/apis/client.ts index 092d56f..2564af2 100644 --- a/src/apis/client.ts +++ b/src/apis/client.ts @@ -102,8 +102,8 @@ client.interceptors.response.use( } } } - if (isLoggedIn) logout(); - console.error('Failed to refresh token', error); + // if (isLoggedIn) logout(); + // console.error('Failed to refresh token', error); return Promise.reject(error); }, ); diff --git a/src/apis/letterDetail.ts b/src/apis/letterDetail.ts index d321438..6a72b66 100644 --- a/src/apis/letterDetail.ts +++ b/src/apis/letterDetail.ts @@ -23,4 +23,17 @@ const deleteLetter = async (letterId: string) => { } }; -export { getLetter, deleteLetter }; +const postEvaluateLetter = async (letterId: number, evaluation: LetterEvaluation) => { + try { + const res = await client.post(`/api/letters/${letterId}/evaluate`, { + evaluation: evaluation, + }); + if (!res) throw new Error('편지 삭제 요청 도중 에러가 발생했습니다.'); + console.log(res); + return res; + } catch (error) { + console.error(error); + } +}; + +export { getLetter, deleteLetter, postEvaluateLetter }; diff --git a/src/apis/randomLetter.ts b/src/apis/randomLetter.ts index 994aff6..515a1d8 100644 --- a/src/apis/randomLetter.ts +++ b/src/apis/randomLetter.ts @@ -4,7 +4,6 @@ const getRandomLetters = async (category: string | null) => { try { const res = await client.get(`/api/random-letters/${category}`); if (!res) throw new Error('랜덤 편지 데이터를 가져오는 도중 에러가 발생했습니다.'); - console.log(res); return res; } catch (error) { console.error(error); @@ -13,8 +12,6 @@ const getRandomLetters = async (category: string | null) => { const postRandomLettersApprove = async (approveRequest: ApproveRequest, callBack?: () => void) => { try { - console.log('엔드포인트 : /api/random-letters/approve'); - console.log('request', approveRequest); const res = await client.post('/api/random-letters/approve', approveRequest); if (!res) throw new Error('랜덤편지 매칭수락 도중 에러가 발생했습니다.'); if (callBack) callBack(); @@ -30,7 +27,6 @@ const getRandomLetterMatched = async (callBack?: () => void) => { if (!res) throw new Error('랜덤 편지 최종 매칭 시간 검증 데이터를 가자오는 도중 에러가 발생했습니다.'); if (callBack) callBack(); - console.log(res); return res; } catch (error) { console.error(error); @@ -43,7 +39,6 @@ const getRandomLetterCoolTime = async (callBack?: () => void) => { if (!res) throw new Error('랜덤 편지 최종 매칭 시간 검증 데이터를 가자오는 도중 에러가 발생했습니다.'); if (callBack) callBack(); - console.log(res); return res; } catch (error) { console.error(error); @@ -54,7 +49,6 @@ const deleteRandomLetterMatching = async () => { try { const res = await client.delete('/api/random-letters/matching/cancel'); if (!res) throw new Error('매칭 취소 도중 에러가 발생했습니다.'); - console.log(res); return res; } catch (error) { console.log(error); diff --git a/src/apis/write.ts b/src/apis/write.ts index 7046e1f..70984de 100644 --- a/src/apis/write.ts +++ b/src/apis/write.ts @@ -2,10 +2,10 @@ import client from './client'; const postLetter = async (data: LetterRequest) => { + console.log('request', data); try { const res = await client.post('/api/letters', data); - if (!res) throw new Error('편지 전송과정중에서 오류가 발생했습니다.'); - console.log(`api 주소 : /api/letters, 전송타입 : post`); + if (!res) throw new Error('편지 전송과정에서 오류가 발생했습니다.'); return res; } catch (error) { console.error(error); @@ -13,11 +13,10 @@ const postLetter = async (data: LetterRequest) => { }; const postFirstReply = async (data: FirstReplyRequest) => { + console.log('Firstrequest', data); try { const res = await client.post('/api/random-letters/matching', data); - if (!res) throw new Error('최초 답장 전송과정중에서 오류가 발생했습니다.'); - console.log(`api 주소 : /api/random-letters/matching, 전송타입 : post`); - console.log(res); + if (!res) throw new Error('최초 답장 전송과정에서 오류가 발생했습니다.'); return res; } catch (error) { console.error(error); @@ -27,11 +26,33 @@ const postFirstReply = async (data: FirstReplyRequest) => { const getPrevLetter = async (letterId: string) => { try { const res = await client.get(`/api/letters/${letterId}/previous`); - console.log(res); + if (!res) throw new Error('이전편지를 불러오는중 오류가 발생했습니다.'); return res; } catch (error) { console.error(error); } }; -export { postLetter, postFirstReply, getPrevLetter }; +// 임시저장 최초 생성 +const postTemporarySave = async (data: TemporaryRequest) => { + try { + const res = client.post(`/api/letters/temporary-save`, data); + if (!res) throw new Error('편지 임시저장과정에서 오류가 발생했습니다.'); + return res; + } catch (error) { + console.error(error); + } +}; + +// 임시저장 수정 +const PatchTemporarySave = async (data: TemporaryRequest) => { + try { + const res = client.post(`/api/letters/temporary-save`, data); + if (!res) throw new Error('편지 임시저장과정에서 오류가 발생했습니다.'); + return res; + } catch (error) { + console.error(error); + } +}; + +export { postLetter, postFirstReply, getPrevLetter, postTemporarySave, PatchTemporarySave }; diff --git a/src/components/ReportModal.tsx b/src/components/ReportModal.tsx index be47d81..41ce17f 100644 --- a/src/components/ReportModal.tsx +++ b/src/components/ReportModal.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import { twMerge } from 'tailwind-merge'; import { postReports } from '@/apis/admin'; @@ -8,7 +8,7 @@ import TextareaField from './TextareaField'; interface ReportModalProps { reportType: ReportType; - letterId: number | null; + letterId: number; onClose: () => void; } @@ -42,6 +42,7 @@ const ReportModal = ({ reportType, letterId, onClose }: ReportModalProps) => { const res = await postReports(postReportRequest); if (res?.status === 200) { alert('신고 처리되었습니다.'); + console.log(res); onClose(); } else if (res?.status === 409) { alert('신고한 이력이 있습니다.'); @@ -49,13 +50,6 @@ const ReportModal = ({ reportType, letterId, onClose }: ReportModalProps) => { } }; - useEffect(() => { - if (!postReportRequest.letterId) { - alert('신고 모달을 여는 과정에서 오류가 발생했습니다. 새로고침을 눌러주세요'); - onClose(); - } - }); - return ( >; +} +export default function DegreeSelector({ letterDetail, setLetterDetail }: DegreeSelector) { + const handlePostEvaluateLetter = async ( + letterId: number | undefined, + evaluation: LetterEvaluation, + ) => { + if (!letterId) return alert('편지id값이 담겨있지 않습니다.'); + const res = await postEvaluateLetter(letterId, evaluation); + if (res?.status === 200) { + console.log('평가완료'); + setLetterDetail((cur) => ({ ...cur, evaluated: true })); + } + }; + const DEGREES = [ + { + icon: , + title: '따뜻해요', + onClick: () => { + handlePostEvaluateLetter(letterDetail?.letterId, 'GOOD'); + }, + }, + { + icon: , + title: '그럭저럭', + onClick: () => { + handlePostEvaluateLetter(letterDetail?.letterId, 'SOSO'); + }, + }, + { + icon: , + title: '앗! 차가워', + onClick: () => { + handlePostEvaluateLetter(letterDetail?.letterId, 'BAD'); + }, + }, + ]; + return ( +
+ {DEGREES.map((degree, idx) => { + return ( + + ); + })} +
+ ); +} diff --git a/src/pages/LetterDetail/components/LetterDetailContent.tsx b/src/pages/LetterDetail/components/LetterDetailContent.tsx new file mode 100644 index 0000000..4e7761d --- /dev/null +++ b/src/pages/LetterDetail/components/LetterDetailContent.tsx @@ -0,0 +1,26 @@ +import { twMerge } from 'tailwind-merge'; + +import { FONT_TYPE_OBJ } from '@/pages/Write/constants'; + +interface LetterDetailContent { + letterDetail: LetterDetail; +} +export default function LetterDetailContent({ letterDetail }: LetterDetailContent) { + return ( + <> +
+ TO. 따숨이 + {letterDetail.title} +
+ + FROM. {letterDetail.zipCode} + + ); +} diff --git a/src/pages/LetterDetail/components/LetterDetailDegreeButton.tsx b/src/pages/LetterDetail/components/LetterDetailDegreeButton.tsx new file mode 100644 index 0000000..769aa51 --- /dev/null +++ b/src/pages/LetterDetail/components/LetterDetailDegreeButton.tsx @@ -0,0 +1,50 @@ +import { useEffect, useRef } from 'react'; + +import { ThermostatIcon } from '@/assets/icons'; + +interface LetterDetailDegreeButton { + letterDetail: LetterDetail | null; + setDegreeModalOpen: React.Dispatch>; +} +export default function LetterDetailDegreeButton({ + letterDetail, + setDegreeModalOpen, +}: LetterDetailDegreeButton) { + const degreeButtonRef = useRef(null); + + useEffect(() => { + const handleOutsideClick = (event: MouseEvent) => { + const target = event.target as Node; + if (!target || degreeButtonRef.current?.contains(target)) { + return; + } + setDegreeModalOpen(false); + }; + + document.body.addEventListener('click', handleOutsideClick); + + return () => { + document.body.removeEventListener('click', handleOutsideClick); + }; + }, [setDegreeModalOpen]); + return ( + <> + {letterDetail?.evaluated ? ( +
+ 온도 측정된 편지에요! +
+ ) : ( + + )} + + ); +} diff --git a/src/pages/LetterDetail/components/LetterDetailHeader.tsx b/src/pages/LetterDetail/components/LetterDetailHeader.tsx new file mode 100644 index 0000000..17cc18c --- /dev/null +++ b/src/pages/LetterDetail/components/LetterDetailHeader.tsx @@ -0,0 +1,60 @@ +import { useState } from 'react'; + +import { DeleteIcon, SirenOutlinedIcon } from '@/assets/icons'; +import BackButton from '@/components/BackButton'; +import useAuthStore from '@/stores/authStore'; + +import DegreeSelector from './DegreeSelector'; +import LetterDetailDegreeButton from './LetterDetailDegreeButton'; + +interface LetterDetailHeader { + letterDetail: LetterDetail; + setLetterDetail: React.Dispatch>; + setDeleteModalOpen: React.Dispatch>; + setReportModalOpen: React.Dispatch>; +} +export default function LetterDetailHeader({ + letterDetail, + setLetterDetail, + setDeleteModalOpen, + setReportModalOpen, +}: LetterDetailHeader) { + const [degreeModalOpen, setDegreeModalOpen] = useState(false); + + const userZipCode = useAuthStore((state) => state.zipCode); + + return ( +
+ +
+ {userZipCode !== letterDetail?.zipCode && ( + + )} + {userZipCode === letterDetail?.zipCode && ( + + )} + {userZipCode !== letterDetail?.zipCode && ( + + )} + {degreeModalOpen && ( + + )} +
+
+ ); +} diff --git a/src/pages/LetterDetail/components/LetterDetailReplyButton.tsx b/src/pages/LetterDetail/components/LetterDetailReplyButton.tsx new file mode 100644 index 0000000..60c8ad3 --- /dev/null +++ b/src/pages/LetterDetail/components/LetterDetailReplyButton.tsx @@ -0,0 +1,19 @@ +import { useNavigate } from 'react-router'; + +interface LetterDetailReplyButton { + letterDetail: LetterDetail; +} +export default function LetterDetailReplyButton({ letterDetail }: LetterDetailReplyButton) { + const navigate = useNavigate(); + return ( + + ); +} diff --git a/src/pages/LetterDetail/index.tsx b/src/pages/LetterDetail/index.tsx index 88a40a4..86cf44f 100644 --- a/src/pages/LetterDetail/index.tsx +++ b/src/pages/LetterDetail/index.tsx @@ -1,45 +1,27 @@ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useNavigate, useParams } from 'react-router'; import { twMerge } from 'tailwind-merge'; import { deleteLetter, getLetter } from '@/apis/letterDetail'; -import { - CloudIcon, - DeleteIcon, - SirenOutlinedIcon, - SnowIcon, - ThermostatIcon, - WarmIcon, -} from '@/assets/icons'; -import BackButton from '@/components/BackButton'; import ConfirmModal from '@/components/ConfirmModal'; import ReportModal from '@/components/ReportModal'; -import { FONT_TYPE_OBJ, PAPER_TYPE_OBJ } from '@/pages/Write/constants'; +import { PAPER_TYPE_OBJ } from '@/pages/Write/constants'; +import useAuthStore from '@/stores/authStore'; + +import LetterDetailContent from './components/LetterDetailContent'; +import LetterDetailHeader from './components/LetterDetailHeader'; +import LetterDetailReplyButton from './components/LetterDetailReplyButton'; const LetterDetailPage = () => { const params = useParams(); const navigate = useNavigate(); - // 상대방의 우편번호도 데이터에 포함되어야 할 거 같음!!! - const [letterDetail, setLetterDetail] = useState(null); - const DEGREES = [ - { icon: , title: '따뜻해요' }, - { icon: , title: '그럭저럭' }, - { icon: , title: '앗! 차가워' }, - ]; - const [degreeModalOpen, setDegreeModalOpen] = useState(false); + const [letterDetail, setLetterDetail] = useState({} as LetterDetail); + const userZipCode = useAuthStore((state) => state.zipCode); + const [reportModalOpen, setReportModalOpen] = useState(false); const [deleteModalOpen, setDeleteModalOpen] = useState(false); - const degreeButtonRef = useRef(null); - const handleOutsideClick = (event: MouseEvent) => { - const target = event.target as Node; - if (!target || degreeButtonRef.current?.contains(target)) { - return; - } - setDegreeModalOpen(false); - }; - const handleDeleteLetter = async (letterId: string) => { const res = await deleteLetter(letterId); if (res?.status === 200) { @@ -50,12 +32,11 @@ const LetterDetailPage = () => { }; useEffect(() => { - document.body.addEventListener('click', handleOutsideClick); - const handleGetLetter = async (letterId: string) => { const res = await getLetter(letterId); if (res?.status === 200) { - setLetterDetail(res.data.data); + const data: LetterDetail = res.data.data; + setLetterDetail(data); } else { alert( '에러가 발생했거나 존재하지 않거나 따숨님의 편지가 아니에요(임시) - 이거 에러코드 따른 처리 달리해야할듯', @@ -66,17 +47,15 @@ const LetterDetailPage = () => { if (params.id) { handleGetLetter(params.id); } - - return () => { - document.body.removeEventListener('click', handleOutsideClick); - }; }, [params.id, navigate]); + + if (!letterDetail) return <>; return ( <> {reportModalOpen && ( setReportModalOpen(false)} /> )} @@ -86,75 +65,16 @@ const LetterDetailPage = () => { letterDetail && PAPER_TYPE_OBJ[letterDetail.paperType], )} > -
- -
- - - - {degreeModalOpen && ( -
- {DEGREES.map((degree, idx) => { - return ( - - ); - })} -
- )} -
-
-
- TO. 따숨이 - {letterDetail?.title} -
- - FROM. {'12E12'} - + + + {userZipCode !== letterDetail?.zipCode && ( + + )} {deleteModalOpen && (
- -
+ +
+ 기다림의 미학을 느껴보시는건 어떨까요? +
- - {randomLetters.map((list, idx) => { - return ( - -
{ - setOpenModal(true); - setSelectedLetter(list); - }} - > - -
-
- ); - })} -
+ {randomLetters.length === 0 ? ( + +
+
+ 편지가 없습니다. + 따숨님의 편지를 작성해보시겠어요? +
+ +
+
+ ) : ( + + {randomLetters.map((list, idx) => { + return ( + +
{ + setOpenModal(true); + setSelectedLetter(list); + }} + > + +
+
+ ); + })} +
+ )}
diff --git a/src/pages/RandomLetters/constants/index.ts b/src/pages/RandomLetters/constants/index.ts index 47fa7f5..b10f948 100644 --- a/src/pages/RandomLetters/constants/index.ts +++ b/src/pages/RandomLetters/constants/index.ts @@ -1,5 +1,5 @@ -const CATEGORY_LIST: { title: string; category: Category | null }[] = [ - { title: '전체', category: null }, +const CATEGORY_LIST: { title: string; category: Category | 'ALL' }[] = [ + { title: '전체', category: 'ALL' }, { title: '위로와 공감', category: 'CONSOLATION' }, { title: '축하와 응원', category: 'CELEBRATION' }, { title: '고민 상담', category: 'CONSULT' }, diff --git a/src/pages/Write/CategorySelect.tsx b/src/pages/Write/CategorySelect.tsx index bdabcbb..7244b0b 100644 --- a/src/pages/Write/CategorySelect.tsx +++ b/src/pages/Write/CategorySelect.tsx @@ -59,9 +59,7 @@ export default function CategorySelect({
작성하신 편지는 - {'00'}시간 - {'00'}분 - {'00'}초 후에 도착합니다. + 1시간 후에 도착합니다.
diff --git a/src/pages/Write/LetterEditor.tsx b/src/pages/Write/LetterEditor.tsx index 4e91d38..c22f206 100644 --- a/src/pages/Write/LetterEditor.tsx +++ b/src/pages/Write/LetterEditor.tsx @@ -1,9 +1,10 @@ import { useEffect, useState } from 'react'; -import { useLocation } from 'react-router'; +import { useLocation, useNavigate } from 'react-router'; import { twMerge } from 'tailwind-merge'; -import { postFirstReply, postLetter } from '@/apis/write'; +import { postFirstReply, postLetter, postTemporarySave } from '@/apis/write'; import BackButton from '@/components/BackButton'; +import ConfirmModal from '@/components/ConfirmModal'; import WritePageButton from '@/pages/Write/components/WritePageButton'; import { FONT_TYPE_OBJ } from '@/pages/Write/constants'; import OptionSlide from '@/pages/Write/OptionSlide'; @@ -11,20 +12,22 @@ import useWrite from '@/stores/writeStore'; import { removeProperty } from '@/utils/removeProperty'; export default function LetterEditor({ + letterId, setStep, prevLetter, setSend, - searchParams, isReply, }: { - setStep: React.Dispatch>; + letterId: string | null; + isReply: boolean; prevLetter: PrevLetter[]; + setStep: React.Dispatch>; setSend: React.Dispatch>; - searchParams: URLSearchParams; - isReply: boolean; }) { const location = useLocation(); + const navigate = useNavigate(); const [randomMatched, setRandomMatched] = useState(false); + const [isTemporaryConfirmModal, setIsTemporaryConfirmModal] = useState(false); const letterRequest = useWrite((state) => state.letterRequest); const setLetterRequest = useWrite((state) => state.setLetterRequest); @@ -63,35 +66,72 @@ export default function LetterEditor({ console.log('prevLetter', prevLetter); setLetterRequest({ receiverId: prevLetter[0].memberId, - parentLetterId: Number(searchParams.get('letterId')), + parentLetterId: Number(letterId), category: prevLetter[0].category, matchingId: prevLetter[0].matchingId, }); } - }, [prevLetter, searchParams, setLetterRequest, isReply]); + }, [prevLetter, setLetterRequest, isReply]); + + const handlePostTemporarySave = async () => { + if (!letterId) return alert('임시저장중 오류 발생'); + const LETTER_STATE_DUMMY = false; + const requestLetterId = LETTER_STATE_DUMMY || null; + // MEMO : 임시저장 전송 방식 : 최초임시저장은 letterId : null, 임시저장 업데이트는 letterId : location state로 받아오는 임시저장편지의 letterId값 + const temporaryRequest: TemporaryRequest = { ...letterRequest, letterId: requestLetterId }; + const res = await postTemporarySave(temporaryRequest); + if (res?.status === 200) { + console.log(res); + navigate('/'); + } else { + alert('실패'); + } + }; return (
+ {isTemporaryConfirmModal && ( + setIsTemporaryConfirmModal(false)} + onConfirm={() => { + handlePostTemporarySave(); + }} + /> + )}
{isReply ? ( - { - if (letterRequest.title.trim() !== '' && letterRequest.content.trim() !== '') { - if (randomMatched) { - const firstReplyRequest = removeProperty(letterRequest, ['matchingId']); - console.log(firstReplyRequest); - handlePostFirstReply(firstReplyRequest); +
+ {!randomMatched && ( + { + setIsTemporaryConfirmModal(true); + }} + /> + )} + { + if (letterRequest.title.trim() !== '' && letterRequest.content.trim() !== '') { + if (randomMatched) { + const firstReplyRequest = removeProperty(letterRequest, ['matchingId']); + console.log(firstReplyRequest); + handlePostFirstReply(firstReplyRequest); + } else { + handlePostReply(letterRequest); + } } else { - handlePostReply(letterRequest); + alert('편지 제목, 내용이 작성되었는지 확인해주세요'); } - } else { - alert('편지 제목, 내용이 작성되었는지 확인해주세요'); - } - }} - /> + }} + /> +
) : (