diff --git a/src/apis/letterDetail.ts b/src/apis/letterDetail.ts index d2f176b..0091069 100644 --- a/src/apis/letterDetail.ts +++ b/src/apis/letterDetail.ts @@ -7,6 +7,7 @@ const getLetter = async ( ) => { try { const res = await client.get(`/api/letters/${letterId}`); + if (!res) throw new Error('편지 데이터를 가져오는 도중 에러가 발생했습니다.'); setLetterState(res.data.data); if (callBack) callBack(); console.log(res); @@ -19,6 +20,7 @@ const deleteLetter = async (letterId: string, callBack?: () => void) => { try { console.log(`/api/letters/${letterId}`); const res = await client.delete(`/api/letters/${letterId}`); + if (!res) throw new Error('편지 삭제 요청 도중 에러가 발생했습니다.'); if (callBack) callBack(); console.log(res); } catch (error) { diff --git a/src/apis/randomLetter.ts b/src/apis/randomLetter.ts new file mode 100644 index 0000000..4742f21 --- /dev/null +++ b/src/apis/randomLetter.ts @@ -0,0 +1,17 @@ +import client from './client'; + +const getRandomLetters = async ( + setRandomLettersState: React.Dispatch>, + category: string | null, +) => { + try { + const res = await client.get(`/api/random/${category}`); + if (!res) throw new Error('랜덤 편지 데이터를 가져오는 도중 에러가 발생했습니다.'); + setRandomLettersState(res.data.data); + console.log(res); + } catch (error) { + console.error(error); + } +}; + +export { getRandomLetters }; diff --git a/src/components/ResultLetter.tsx b/src/components/ResultLetter.tsx index 0e56b7d..7f1b131 100644 --- a/src/components/ResultLetter.tsx +++ b/src/components/ResultLetter.tsx @@ -5,7 +5,7 @@ import LetterWrapper from './LetterWrapper'; export default function ResultLetter({ categoryName = 'CONSOLATION', title, - zipCode = 'error', + zipCode = 'ERROR', }: { categoryName: Category; title: string; diff --git a/src/pages/LetterDetail/index.tsx b/src/pages/LetterDetail/index.tsx index c892656..cd9da97 100644 --- a/src/pages/LetterDetail/index.tsx +++ b/src/pages/LetterDetail/index.tsx @@ -12,6 +12,7 @@ import { 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'; @@ -28,6 +29,7 @@ const LetterDetailPage = () => { ]; const [degreeModalOpen, setDegreeModalOpen] = useState(false); const [reportModalOpen, setReportModalOpen] = useState(false); + const [deleteModalOpen, setDeleteModalOpen] = useState(false); const degreeButtonRef = useRef(null); const handleOutsideClick = (event: MouseEvent) => { @@ -41,7 +43,6 @@ const LetterDetailPage = () => { document.body.addEventListener('click', handleOutsideClick); if (params.id) { getLetter(params.id, setLetterDetail); - // 편지 삭제 요청 테스트(내일 삭제 버튼 만들어서 여기다 추가하긔) } return () => { @@ -72,7 +73,7 @@ const LetterDetailPage = () => { + {deleteModalOpen && ( + { + setDeleteModalOpen(false); + }} + onConfirm={() => { + if (params.id) deleteLetter(params.id); + navigate(-1); + }} + /> + )} ); diff --git a/src/pages/RandomLetters/Matched.tsx b/src/pages/RandomLetters/Matched.tsx deleted file mode 100644 index bc77d3a..0000000 --- a/src/pages/RandomLetters/Matched.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import ResultLetter from '@/components/ResultLetter'; - -export default function Matched() { - return ( -
-
-

답장까지 남은 시간

-

- {'00'} : {'00'} : {'00'} -

-
- -
- -
-
- ); -} diff --git a/src/pages/RandomLetters/MatchingSelect.tsx b/src/pages/RandomLetters/MatchingSelect.tsx deleted file mode 100644 index 375c9bd..0000000 --- a/src/pages/RandomLetters/MatchingSelect.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { useState } from 'react'; -import { EffectCards } from 'swiper/modules'; -import { Swiper, SwiperSlide } from 'swiper/react'; -import { twMerge } from 'tailwind-merge'; - -import 'swiper/swiper-bundle.css'; - -import { RestartIcon } from '@/assets/icons'; -import ResultLetter from '@/components/ResultLetter'; - -export default function MatchingSelect({ - setOpenModal, - setSelectedLetter, -}: { - setOpenModal: React.Dispatch>; - setSelectedLetter: React.Dispatch>; -}) { - const [selectedCategory, setSelectedCategory] = useState('전체'); - - const CATEGORY_LIST = ['전체', '위로와 공감', '축하와 응원', '고민 상담', '기타']; - const DUMMY_LIST: { categoryName: Category; title: string }[] = [ - { categoryName: 'CONSOLATION', title: '위로가 필요해요' }, - { categoryName: 'CELEBRATION', title: '저에게 미움받을 용기를 주세요' }, - { categoryName: 'CONSULT', title: '삶이 무료해서 고민이에요' }, - { categoryName: 'ETC', title: '어제 꾼 꿈이 신기했어요' }, - { categoryName: 'CONSULT', title: '삶이 유료해서 고민이에요' }, - { categoryName: 'CELEBRATION', title: '어제 취업했어요!!!!' }, - { categoryName: 'CELEBRATION', title: '어제 게임 신기록 세웠어요!!!!!' }, - { categoryName: 'ETC', title: '기타는 핑거스타일이 멋있는거 같아요' }, - { categoryName: 'CONSOLATION', title: '10년지기 친구가 이사를 가요' }, - { - categoryName: 'ETC', - title: - '햄부기햄북 햄북어 햄북스딱스 함부르크햄부가우가 햄비기햄부거 햄부가티햄부기온앤 온 을 차려오거라.', - }, - ]; - - return ( - <> -
- -
- - {DUMMY_LIST.map((list, idx) => { - return ( - -
{ - setOpenModal(true); - setSelectedLetter(() => ({ - categoryName: list.categoryName, - title: list.title, - })); - }} - > - -
-
- ); - })} -
-
-
- -
- {CATEGORY_LIST.map((category, idx) => { - return ( - - ); - })} -
- - ); -} diff --git a/src/pages/RandomLetters/components/CoolTime.tsx b/src/pages/RandomLetters/components/CoolTime.tsx new file mode 100644 index 0000000..68882da --- /dev/null +++ b/src/pages/RandomLetters/components/CoolTime.tsx @@ -0,0 +1,94 @@ +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router'; + +import LetterWrapper from '@/components/LetterWrapper'; +import { formatNumber } from '@/utils/formatNumber'; + +// import letterPink from '@/assets/images/letter-pink.png'; + +export default function CoolTime({ + setCoolTime, +}: { + setCoolTime: React.Dispatch>; +}) { + const navigate = useNavigate(); + + const TIME_STAMP = '2025-02-26T22:13:25.262045608'; + + const COMPLETED_DATE = new Date(TIME_STAMP); + + const END_DATE = new Date(COMPLETED_DATE); + END_DATE.setHours(COMPLETED_DATE.getHours() + 1); + + const NOW_DATE = new Date(); + + const endTime = END_DATE.getTime() - NOW_DATE.getTime(); + + const [endTimes, setEndTimes] = useState({ + hours: Math.floor((endTime / (1000 * 60 * 60)) % 24), + minutes: Math.floor((endTime / (1000 * 60)) % 60), + seconds: Math.floor((endTime / 1000) % 60), + }); + + useEffect(() => { + if (endTimes.hours < 0 || endTimes.minutes < 0 || endTimes.seconds < 0) { + setEndTimes({ hours: 0, minutes: 0, seconds: 0 }); + } + if (endTimes.hours === 0 && endTimes.minutes === 0 && endTimes.seconds === 0) { + setCoolTime(false); + return; + } + const endTimeFlow = setInterval(() => { + setEndTimes((currentTime) => { + if (currentTime.seconds > 0) { + return { ...currentTime, seconds: currentTime.seconds - 1 }; + } // + else { + if (currentTime.minutes > 0) { + return { ...currentTime, minutes: currentTime.minutes - 1, seconds: 59 }; + } // + else { + if (currentTime.hours > 0) { + return { hours: currentTime.hours - 1, minutes: 59, seconds: 59 }; + } // + else { + setCoolTime(false); + return { ...currentTime }; + } + } + } + }); + if (endTimes.hours === 0 && endTimes.minutes === 0 && endTimes.seconds === 0) { + clearInterval(endTimeFlow); + } + }, 1000); + + return () => { + clearInterval(endTimeFlow); + }; + }, [endTimes, setCoolTime]); + return ( +
+
+

랜덤 편지 활성화 까지

+

+ {formatNumber(endTimes.hours)} : {formatNumber(endTimes.minutes)} :{' '} + {formatNumber(endTimes.seconds)} +

+
+ +
+
+
+ +
+
+ ); +} diff --git a/src/pages/RandomLetters/components/Matched.tsx b/src/pages/RandomLetters/components/Matched.tsx new file mode 100644 index 0000000..5825da9 --- /dev/null +++ b/src/pages/RandomLetters/components/Matched.tsx @@ -0,0 +1,139 @@ +import { useEffect, useState } from 'react'; + +import ResultLetter from '@/components/ResultLetter'; +import { formatNumber } from '@/utils/formatNumber'; + +export default function Matched({ + setMatched, + setCoolTime, +}: { + setMatched: React.Dispatch>; + setCoolTime: React.Dispatch>; +}) { + const [isDisabled, setIsDisabled] = useState(false); + + const TIME_STAMP = '2025-02-25T21:52:25.262045608'; + + const MATCHED_DATE = new Date(TIME_STAMP); + + const END_DATE = new Date(MATCHED_DATE); + END_DATE.setHours(MATCHED_DATE.getHours() + 24); + + const GRACE_DATE = new Date(MATCHED_DATE); + GRACE_DATE.setMinutes(MATCHED_DATE.getMinutes() + 5); + + const NOW_DATE = new Date(); + + const endTime = END_DATE.getTime() - NOW_DATE.getTime(); + const graceTime = GRACE_DATE.getTime() - NOW_DATE.getTime(); + + const [endTimes, setEndTimes] = useState({ + hours: Math.floor((endTime / (1000 * 60 * 60)) % 24), + minutes: Math.floor((endTime / (1000 * 60)) % 60), + seconds: Math.floor((endTime / 1000) % 60), + }); + + const [graceTimes, setGraceTimes] = useState({ + minutes: Math.floor((graceTime / (1000 * 60)) % 60), + seconds: Math.floor((graceTime / 1000) % 60), + }); + + useEffect(() => { + if (endTimes.hours < 0 || endTimes.minutes < 0 || endTimes.seconds < 0) { + setEndTimes({ hours: 0, minutes: 0, seconds: 0 }); + } + if (endTimes.hours === 0 && endTimes.minutes === 0 && endTimes.seconds === 0) { + setMatched(false); + setCoolTime(true); + return; + } + const endTimeFlow = setInterval(() => { + setEndTimes((currentTime) => { + if (currentTime.seconds > 0) { + return { ...currentTime, seconds: currentTime.seconds - 1 }; + } // + else { + if (currentTime.minutes > 0) { + return { ...currentTime, minutes: currentTime.minutes - 1, seconds: 59 }; + } // + else { + if (currentTime.hours > 0) { + return { hours: currentTime.hours - 1, minutes: 59, seconds: 59 }; + } // + else { + setMatched(false); + setCoolTime(true); + return { ...currentTime }; + } + } + } + }); + if (endTimes.hours === 0 && endTimes.minutes === 0 && endTimes.seconds === 0) { + clearInterval(endTimeFlow); + } + }, 1000); + + return () => { + clearInterval(endTimeFlow); + }; + }, [endTimes, setMatched, setCoolTime]); + + useEffect(() => { + if (graceTimes.minutes < 0 || graceTimes.seconds < 0) { + setGraceTimes({ minutes: 0, seconds: 0 }); + } + if (graceTimes.minutes === 0 && graceTimes.seconds === 0) { + return setIsDisabled(true); + } + const graceTimeFlow = setInterval(() => { + setGraceTimes((currentTime) => { + if (currentTime.seconds > 0) { + return { ...currentTime, seconds: currentTime.seconds - 1 }; + } // + else { + if (currentTime.minutes > 0) { + return { minutes: currentTime.minutes - 1, seconds: 59 }; + } // + else { + setIsDisabled(true); + return { ...currentTime }; + } + } + }); + if (graceTimes.minutes === 0 && graceTimes.seconds === 0) { + clearInterval(graceTimeFlow); + } + }, 1000); + + return () => { + clearInterval(graceTimeFlow); + }; + }, [graceTimes]); + + return ( +
+
+

답장까지 남은 시간

+

+ {formatNumber(endTimes.hours)} : {formatNumber(endTimes.minutes)} :{' '} + {formatNumber(endTimes.seconds)} +

+
+ +
+ +
+
+ ); +} diff --git a/src/pages/RandomLetters/components/MatchedLetter.tsx b/src/pages/RandomLetters/components/MatchedLetter.tsx new file mode 100644 index 0000000..bda4395 --- /dev/null +++ b/src/pages/RandomLetters/components/MatchedLetter.tsx @@ -0,0 +1,57 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router'; +import { twMerge } from 'tailwind-merge'; + +import BackButton from '@/components/BackButton'; +import ReportModal from '@/components/ReportModal'; +import { FONT_TYPE_OBJ, PAPER_TYPE_OBJ } from '@/pages/Write/constants'; + +const MatchedLetter = ({ selectedLetter }: { selectedLetter: RandomLetters }) => { + const navigate = useNavigate(); + // 상대방의 우편번호도 데이터에 포함되어야 할 거 같음!!! + const [letterDetail] = useState(null); + + const [reportModalOpen, setReportModalOpen] = useState(false); + + return ( + <> + {reportModalOpen && setReportModalOpen(false)} />} +
+
+ +
+
+ TO. 따숨이 + {selectedLetter?.title} +
+ + FROM. {selectedLetter.zipCode} + +
+ + ); +}; + +export default MatchedLetter; diff --git a/src/pages/RandomLetters/components/MatchingSelect.tsx b/src/pages/RandomLetters/components/MatchingSelect.tsx new file mode 100644 index 0000000..45fbc79 --- /dev/null +++ b/src/pages/RandomLetters/components/MatchingSelect.tsx @@ -0,0 +1,112 @@ +import { useEffect, useState } from 'react'; +import { EffectCards } from 'swiper/modules'; +import { Swiper, SwiperSlide } from 'swiper/react'; +import { twMerge } from 'tailwind-merge'; + +import 'swiper/swiper-bundle.css'; + +import { getRandomLetters } from '@/apis/randomLetter'; +import { RestartIcon } from '@/assets/icons'; +import ResultLetter from '@/components/ResultLetter'; + +import { CATEGORY_LIST } from '../constants'; + +export default function MatchingSelect({ + setOpenModal, + setSelectedLetter, +}: { + setOpenModal: React.Dispatch>; + setSelectedLetter: React.Dispatch>; +}) { + const [selectedCategory, setSelectedCategory] = useState(null); + const [randomLetters, setRandomLetters] = useState([]); + + const DUMMY_LIST: RandomLetters[] = [ + { + letterId: 1, + title: '위로가 필요해요', + zipCode: '1aq23', + category: 'CONSOLATION', + createdAt: new Date(), + }, + { + letterId: 2, + title: '아래로가 필요해요', + zipCode: '23w7q', + category: 'CELEBRATION', + createdAt: new Date(), + }, + { + letterId: 3, + title: '안녕하세요', + zipCode: '9a5g7', + category: 'ETC', + createdAt: new Date(), + }, + ]; + + useEffect(() => { + getRandomLetters(setRandomLetters, selectedCategory); + console.log(randomLetters); + }, [selectedCategory]); + + return ( + <> +
+ +
+ + {DUMMY_LIST.map((list, idx) => { + return ( + +
{ + setOpenModal(true); + setSelectedLetter(list); + }} + > + +
+
+ ); + })} +
+
+
+ +
+ {CATEGORY_LIST.map((category, idx) => { + return ( + + ); + })} +
+ + ); +} diff --git a/src/pages/RandomLetters/MatchingSelectModal.tsx b/src/pages/RandomLetters/components/MatchingSelectModal.tsx similarity index 71% rename from src/pages/RandomLetters/MatchingSelectModal.tsx rename to src/pages/RandomLetters/components/MatchingSelectModal.tsx index 6be984e..9d634b4 100644 --- a/src/pages/RandomLetters/MatchingSelectModal.tsx +++ b/src/pages/RandomLetters/components/MatchingSelectModal.tsx @@ -1,4 +1,4 @@ -import { useNavigate } from 'react-router'; +// import { useNavigate } from 'react-router'; import ModalOverlay from '@/components/ModalOverlay'; import ResultLetter from '@/components/ResultLetter'; @@ -6,11 +6,13 @@ import ResultLetter from '@/components/ResultLetter'; function MatchingSelectModal({ setOpenModal, selectedLetter, + setOpenSelectedDetailModal, }: { setOpenModal: React.Dispatch>; - selectedLetter: SelectedLetter; + selectedLetter: RandomLetters; + setOpenSelectedDetailModal: React.Dispatch>; }) { - const navigate = useNavigate(); + // const navigate = useNavigate(); return (
@@ -19,7 +21,11 @@ function MatchingSelectModal({ 수락한 편지는 5분이 지나면 취소할 수 없습니다.
- +