Skip to content

Commit 30bc2f2

Browse files
wldnjs990nirii00
authored andcommitted
feat:랜덤 편지 + 편지 상세 1차 기능 구현 (#46)
* feat:편지 상세 페이지 삭제 모달 추가 * chore:기존 파일들 컴포넌트파일로 이동 * feat:쿨타임 컴포넌트 생성 + 랜덤편지 타입 수정 * feat:차단된 편지 버튼 disabled처리 * feat:편지 상세보기 모달 생성(매칭편지는 상세페이지와 분리) * fix:쿨타임 페이지 letterWrapper 적용 * feat:편지 상세, 랜덤 편지 api throw error 예외처리 추가 * feat:편지 매칭 제한시간 구현 * feat:랜덤편지 페이지 매칭 제한시간, 쿨타임 로직 구현 * feat:랜덤편지 페이지 편지 매칭 제한시간, 쿨타임 시간 구현 * feat:매칭된 편지 전달시 location값 전달처리({randomMatched: true}) * feat:코드리뷰 사항 수정
1 parent ca23169 commit 30bc2f2

File tree

16 files changed

+521
-144
lines changed

16 files changed

+521
-144
lines changed

src/apis/letterDetail.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const getLetter = async (
77
) => {
88
try {
99
const res = await client.get(`/api/letters/${letterId}`);
10+
if (!res) throw new Error('편지 데이터를 가져오는 도중 에러가 발생했습니다.');
1011
setLetterState(res.data.data);
1112
if (callBack) callBack();
1213
console.log(res);
@@ -19,6 +20,7 @@ const deleteLetter = async (letterId: string, callBack?: () => void) => {
1920
try {
2021
console.log(`/api/letters/${letterId}`);
2122
const res = await client.delete(`/api/letters/${letterId}`);
23+
if (!res) throw new Error('편지 삭제 요청 도중 에러가 발생했습니다.');
2224
if (callBack) callBack();
2325
console.log(res);
2426
} catch (error) {

src/apis/randomLetter.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import client from './client';
2+
3+
const getRandomLetters = async (
4+
setRandomLettersState: React.Dispatch<React.SetStateAction<RandomLetters[]>>,
5+
category: string | null,
6+
) => {
7+
try {
8+
const res = await client.get(`/api/random/${category}`);
9+
if (!res) throw new Error('랜덤 편지 데이터를 가져오는 도중 에러가 발생했습니다.');
10+
setRandomLettersState(res.data.data);
11+
console.log(res);
12+
} catch (error) {
13+
console.error(error);
14+
}
15+
};
16+
17+
export { getRandomLetters };

src/components/ResultLetter.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import LetterWrapper from './LetterWrapper';
55
export default function ResultLetter({
66
categoryName = 'CONSOLATION',
77
title,
8-
zipCode = 'error',
8+
zipCode = 'ERROR',
99
}: {
1010
categoryName: Category;
1111
title: string;

src/pages/LetterDetail/index.tsx

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
WarmIcon,
1313
} from '@/assets/icons';
1414
import BackButton from '@/components/BackButton';
15+
import ConfirmModal from '@/components/ConfirmModal';
1516
import ReportModal from '@/components/ReportModal';
1617
import { FONT_TYPE_OBJ, PAPER_TYPE_OBJ } from '@/pages/Write/constants';
1718

@@ -28,6 +29,7 @@ const LetterDetailPage = () => {
2829
];
2930
const [degreeModalOpen, setDegreeModalOpen] = useState<boolean>(false);
3031
const [reportModalOpen, setReportModalOpen] = useState<boolean>(false);
32+
const [deleteModalOpen, setDeleteModalOpen] = useState<boolean>(false);
3133

3234
const degreeButtonRef = useRef<HTMLButtonElement>(null);
3335
const handleOutsideClick = (event: MouseEvent) => {
@@ -41,7 +43,6 @@ const LetterDetailPage = () => {
4143
document.body.addEventListener('click', handleOutsideClick);
4244
if (params.id) {
4345
getLetter(params.id, setLetterDetail);
44-
// 편지 삭제 요청 테스트(내일 삭제 버튼 만들어서 여기다 추가하긔)
4546
}
4647

4748
return () => {
@@ -72,7 +73,7 @@ const LetterDetailPage = () => {
7273
</button>
7374
<button
7475
onClick={() => {
75-
if (params.id) deleteLetter(params.id);
76+
setDeleteModalOpen(true);
7677
}}
7778
>
7879
<DeleteIcon className="text-primary-1 h-6 w-6" />
@@ -118,13 +119,29 @@ const LetterDetailPage = () => {
118119
></textarea>
119120
<span className="body-sb mt-10 flex justify-end">FROM. {'12E12'}</span>
120121
<button
121-
className="bg-primary-3 body-m mt-3 w-full rounded-lg py-2"
122+
className="bg-primary-3 disabled:bg-gray-30 body-m mt-3 w-full rounded-lg py-2 disabled:text-white"
122123
onClick={() => {
123124
navigate(`/letter/write/?letterId=${letterDetail?.letterId}`);
124125
}}
126+
disabled={!letterDetail?.matched}
125127
>
126-
편지 작성하기
128+
{letterDetail?.matched ? '편지 작성하기' : '대화가 종료된 편지입니다.'}
127129
</button>
130+
{deleteModalOpen && (
131+
<ConfirmModal
132+
title="편지를 삭제하시겠습니까?"
133+
description="삭제된 편지는 복구할 수 없습니다."
134+
cancelText="취소"
135+
confirmText="삭제"
136+
onCancel={() => {
137+
setDeleteModalOpen(false);
138+
}}
139+
onConfirm={() => {
140+
if (params.id) deleteLetter(params.id);
141+
navigate(-1);
142+
}}
143+
/>
144+
)}
128145
</div>
129146
</>
130147
);

src/pages/RandomLetters/Matched.tsx

Lines changed: 0 additions & 20 deletions
This file was deleted.

src/pages/RandomLetters/MatchingSelect.tsx

Lines changed: 0 additions & 91 deletions
This file was deleted.
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { useEffect, useState } from 'react';
2+
import { useNavigate } from 'react-router';
3+
4+
import LetterWrapper from '@/components/LetterWrapper';
5+
import { formatNumber } from '@/utils/formatNumber';
6+
7+
// import letterPink from '@/assets/images/letter-pink.png';
8+
9+
export default function CoolTime({
10+
setCoolTime,
11+
}: {
12+
setCoolTime: React.Dispatch<React.SetStateAction<boolean>>;
13+
}) {
14+
const navigate = useNavigate();
15+
16+
const TIME_STAMP = '2025-02-26T22:13:25.262045608';
17+
18+
const COMPLETED_DATE = new Date(TIME_STAMP);
19+
20+
const END_DATE = new Date(COMPLETED_DATE);
21+
END_DATE.setHours(COMPLETED_DATE.getHours() + 1);
22+
23+
const NOW_DATE = new Date();
24+
25+
const endTime = END_DATE.getTime() - NOW_DATE.getTime();
26+
27+
const [endTimes, setEndTimes] = useState({
28+
hours: Math.floor((endTime / (1000 * 60 * 60)) % 24),
29+
minutes: Math.floor((endTime / (1000 * 60)) % 60),
30+
seconds: Math.floor((endTime / 1000) % 60),
31+
});
32+
33+
useEffect(() => {
34+
if (endTimes.hours < 0 || endTimes.minutes < 0 || endTimes.seconds < 0) {
35+
setEndTimes({ hours: 0, minutes: 0, seconds: 0 });
36+
}
37+
if (endTimes.hours === 0 && endTimes.minutes === 0 && endTimes.seconds === 0) {
38+
setCoolTime(false);
39+
return;
40+
}
41+
const endTimeFlow = setInterval(() => {
42+
setEndTimes((currentTime) => {
43+
if (currentTime.seconds > 0) {
44+
return { ...currentTime, seconds: currentTime.seconds - 1 };
45+
} //
46+
else {
47+
if (currentTime.minutes > 0) {
48+
return { ...currentTime, minutes: currentTime.minutes - 1, seconds: 59 };
49+
} //
50+
else {
51+
if (currentTime.hours > 0) {
52+
return { hours: currentTime.hours - 1, minutes: 59, seconds: 59 };
53+
} //
54+
else {
55+
setCoolTime(false);
56+
return { ...currentTime };
57+
}
58+
}
59+
}
60+
});
61+
if (endTimes.hours === 0 && endTimes.minutes === 0 && endTimes.seconds === 0) {
62+
clearInterval(endTimeFlow);
63+
}
64+
}, 1000);
65+
66+
return () => {
67+
clearInterval(endTimeFlow);
68+
};
69+
}, [endTimes, setCoolTime]);
70+
return (
71+
<div className="mt-20 flex flex-col items-center justify-center">
72+
<div className="body-m flex flex-col items-center justify-center">
73+
<p className="text-gray-60">랜덤 편지 활성화 까지</p>
74+
<p className="text-gray-80">
75+
{formatNumber(endTimes.hours)} : {formatNumber(endTimes.minutes)} :{' '}
76+
{formatNumber(endTimes.seconds)}
77+
</p>
78+
<div className="mt-2 w-75">
79+
<LetterWrapper>
80+
<div className="flex h-50 w-full max-w-[300px] flex-col gap-[35px] p-4"></div>
81+
</LetterWrapper>
82+
</div>
83+
<button
84+
className="bg-primary-3 body-m mt-12.5 w-full rounded-lg py-2"
85+
onClick={() => {
86+
navigate('/');
87+
}}
88+
>
89+
홈으로 돌아가기
90+
</button>
91+
</div>
92+
</div>
93+
);
94+
}

0 commit comments

Comments
 (0)