Skip to content

Commit 3071137

Browse files
authored
feat : 편지작성, 랜덤편지 구현 90% 완료, 편지상세는 이후에 테스트 (#67)
* feat:편지 작성 페이지 전역변수 matchingId값 추가 + removeProperty 유틸함수 추가 + 답장 api 연결 + 편지 유형 분기처리 작업중(어렵다) * feat:랜덤편지 승인 api 연결(405오류 뜸) + 임시매칭상태, 쿨타임 상태 확인 api 생성(연결안함) + 랜덤편지에서 편지 이동시 location state 추가 + 랜덤 편지 조회 데이터 사용 * feat:랜덤편지 매칭 여부 api 연결 * refactor:매칭 제한시간, 쿨타임 타이머 리팩토링 * refactor:편지작성, 편지상세 페이지 api코드 수정 * refactor : api response값 호출 컴포넌트에서 후처리 하도록 코드 수정 + 랜덤편지, 편지작성 배포api 연결 및 수정사항 수정(1차) * feat : 랜덤편지 매칭취소시 뒤로가기 처리(임시) * feat : 랜덤편지 편지 승인시 매칭 후 받는 랜덤 편지 테이블 유무 확인 response의 data값 matchedLetter state 만들어 selectedLetter랑 따로 관리(Matched 페이지 전용) * feat:최초편지, 최초답장 구현 완료(디테일 처리 필요) * chore : 더미 데이터 삭제 * chore : 코드리뷰 수정사항 반영
1 parent 911533d commit 3071137

File tree

22 files changed

+511
-259
lines changed

22 files changed

+511
-259
lines changed

src/apis/client.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ const processQueue = (error: unknown, token: string | null = null) => {
3434
client.interceptors.request.use(
3535
(config) => {
3636
const accessToken = useAuthStore.getState().accessToken;
37-
3837
if (config.url !== '/auth/reissue' && accessToken) {
3938
config.headers.Authorization = `Bearer ${accessToken}`;
4039
}

src/apis/letterDetail.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,23 @@
11
import client from './client';
22

3-
const getLetter = async (
4-
letterId: string,
5-
setLetterState: React.Dispatch<React.SetStateAction<LetterDetail | null>>,
6-
callBack?: () => void,
7-
) => {
3+
const getLetter = async (letterId: string) => {
84
try {
95
const res = await client.get(`/api/letters/${letterId}`);
106
if (!res) throw new Error('편지 데이터를 가져오는 도중 에러가 발생했습니다.');
11-
setLetterState(res.data.data);
12-
if (callBack) callBack();
137
console.log(res);
8+
return res;
149
} catch (error) {
1510
console.error(error);
1611
}
1712
};
1813

19-
const deleteLetter = async (letterId: string, callBack?: () => void) => {
14+
const deleteLetter = async (letterId: string) => {
2015
try {
2116
console.log(`/api/letters/${letterId}`);
2217
const res = await client.delete(`/api/letters/${letterId}`);
2318
if (!res) throw new Error('편지 삭제 요청 도중 에러가 발생했습니다.');
24-
if (callBack) callBack();
2519
console.log(res);
20+
return res;
2621
} catch (error) {
2722
console.error(error);
2823
}

src/apis/randomLetter.ts

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,70 @@
11
import client from './client';
22

3-
const getRandomLetters = async (
4-
setRandomLettersState: React.Dispatch<React.SetStateAction<RandomLetters[]>>,
5-
category: string | null,
6-
) => {
3+
const getRandomLetters = async (category: string | null) => {
74
try {
8-
const res = await client.get(`/api/random/${category}`);
5+
const res = await client.get(`/api/random-letters/${category}`);
96
if (!res) throw new Error('랜덤 편지 데이터를 가져오는 도중 에러가 발생했습니다.');
10-
setRandomLettersState(res.data.data);
117
console.log(res);
8+
return res;
129
} catch (error) {
1310
console.error(error);
1411
}
1512
};
1613

17-
export { getRandomLetters };
14+
const postRandomLettersApprove = async (approveRequest: ApproveRequest, callBack?: () => void) => {
15+
try {
16+
console.log('엔드포인트 : /api/random-letters/approve');
17+
console.log('request', approveRequest);
18+
const res = await client.post('/api/random-letters/approve', approveRequest);
19+
if (!res) throw new Error('랜덤편지 매칭수락 도중 에러가 발생했습니다.');
20+
if (callBack) callBack();
21+
return res;
22+
} catch (error) {
23+
console.error(error);
24+
}
25+
};
26+
27+
const getRandomLetterMatched = async (callBack?: () => void) => {
28+
try {
29+
const res = await client.post('/api/random-letters/valid-table');
30+
if (!res)
31+
throw new Error('랜덤 편지 최종 매칭 시간 검증 데이터를 가자오는 도중 에러가 발생했습니다.');
32+
if (callBack) callBack();
33+
console.log(res);
34+
return res;
35+
} catch (error) {
36+
console.error(error);
37+
}
38+
};
39+
40+
const getRandomLetterCoolTime = async (callBack?: () => void) => {
41+
try {
42+
const res = await client.post('/api/random-letters/valid');
43+
if (!res)
44+
throw new Error('랜덤 편지 최종 매칭 시간 검증 데이터를 가자오는 도중 에러가 발생했습니다.');
45+
if (callBack) callBack();
46+
console.log(res);
47+
return res;
48+
} catch (error) {
49+
console.error(error);
50+
}
51+
};
52+
53+
const deleteRandomLetterMatching = async () => {
54+
try {
55+
const res = await client.delete('/api/random-letters/matching/cancel');
56+
if (!res) throw new Error('매칭 취소 도중 에러가 발생했습니다.');
57+
console.log(res);
58+
return res;
59+
} catch (error) {
60+
console.log(error);
61+
}
62+
};
63+
64+
export {
65+
getRandomLetters,
66+
postRandomLettersApprove,
67+
getRandomLetterCoolTime,
68+
getRandomLetterMatched,
69+
deleteRandomLetterMatching,
70+
};

src/apis/write.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,37 @@
1+
// import { AxiosResponse } from 'axios';
12
import client from './client';
23

3-
const postLetter = async (data: LetterRequest, callBack?: () => void) => {
4+
const postLetter = async (data: LetterRequest) => {
45
try {
56
const res = await client.post('/api/letters', data);
6-
if (callBack) callBack();
7+
if (!res) throw new Error('편지 전송과정중에서 오류가 발생했습니다.');
8+
console.log(`api 주소 : /api/letters, 전송타입 : post`);
9+
return res;
10+
} catch (error) {
11+
console.error(error);
12+
}
13+
};
14+
15+
const postFirstReply = async (data: FirstReplyRequest) => {
16+
try {
17+
const res = await client.post('/api/random-letters/matching', data);
18+
if (!res) throw new Error('최초 답장 전송과정중에서 오류가 발생했습니다.');
19+
console.log(`api 주소 : /api/random-letters/matching, 전송타입 : post`);
720
console.log(res);
21+
return res;
822
} catch (error) {
923
console.error(error);
1024
}
1125
};
1226

13-
const getPrevLetter = async (
14-
letterId: string,
15-
setPrevLetterState: React.Dispatch<React.SetStateAction<PrevLetter[]>>,
16-
callBack?: () => void,
17-
) => {
27+
const getPrevLetter = async (letterId: string) => {
1828
try {
1929
const res = await client.get(`/api/letters/${letterId}/previous`);
20-
setPrevLetterState(res.data.data);
21-
if (callBack) callBack();
2230
console.log(res);
31+
return res;
2332
} catch (error) {
2433
console.error(error);
2534
}
2635
};
2736

28-
export { postLetter, getPrevLetter };
37+
export { postLetter, postFirstReply, getPrevLetter };

src/components/ResultLetter.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ export default function ResultLetter({
99
}: {
1010
categoryName: Category;
1111
title: string;
12-
zipCode?: string;
12+
zipCode: string;
1313
}) {
1414
const date = new Date();
1515
const today = `${date.getFullYear()}${date.getMonth() + 1}${date.getDate()}일`;
1616

1717
return (
18-
<LetterWrapper>
18+
<LetterWrapper className="min-w-[300px]">
1919
<div className="flex w-full flex-col gap-[35px]">
2020
<div className="flex justify-between gap-3">
2121
<div className="flex flex-col gap-2.5">

src/layouts/Header.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
import { Link } from 'react-router';
1+
import { Link, useNavigate } from 'react-router';
22

33
import { AlarmIcon, ArrowLeftIcon, PersonIcon } from '@/assets/icons';
44

55
const Header = () => {
66
// TODO: 뒤로 가기 버튼이 보이는 조건 추가
77
// TODO: 스크롤 발생 시, 어떻게 보여져야 하는지
8+
const navigate = useNavigate();
89
return (
910
<header className="fixed top-0 z-40 flex h-16 w-full max-w-150 items-center justify-between p-5">
10-
<ArrowLeftIcon className="h-6 w-6 text-white" />
11+
<button onClick={() => navigate(-1)}>
12+
<ArrowLeftIcon className="h-6 w-6 text-white" />
13+
</button>
1114
<div className="flex items-center gap-3">
1215
<Link to="/mypage/notifications">
1316
<AlarmIcon className="h-6 w-6 text-white" />

src/pages/LetterDetail/index.tsx

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,38 @@ const LetterDetailPage = () => {
3939
}
4040
setDegreeModalOpen(false);
4141
};
42+
43+
const handleDeleteLetter = async (letterId: string) => {
44+
const res = await deleteLetter(letterId);
45+
if (res?.status === 200) {
46+
navigate(-1);
47+
} else {
48+
alert('편지 삭제 도중 오류 발생(임시)');
49+
}
50+
};
51+
4252
useEffect(() => {
4353
document.body.addEventListener('click', handleOutsideClick);
54+
55+
const handleGetLetter = async (letterId: string) => {
56+
const res = await getLetter(letterId);
57+
if (res?.status === 200) {
58+
setLetterDetail(res.data.data);
59+
} else {
60+
alert(
61+
'에러가 발생했거나 존재하지 않거나 따숨님의 편지가 아니에요(임시) - 이거 에러코드 따른 처리 달리해야할듯',
62+
);
63+
navigate(-1);
64+
}
65+
};
4466
if (params.id) {
45-
getLetter(params.id, setLetterDetail);
67+
handleGetLetter(params.id);
4668
}
4769

4870
return () => {
4971
document.body.removeEventListener('click', handleOutsideClick);
5072
};
51-
}, [params.id]);
73+
}, [params.id, navigate]);
5274
return (
5375
<>
5476
{reportModalOpen && <ReportModal onClose={() => setReportModalOpen(false)} />}
@@ -137,7 +159,7 @@ const LetterDetailPage = () => {
137159
setDeleteModalOpen(false);
138160
}}
139161
onConfirm={() => {
140-
if (params.id) deleteLetter(params.id);
162+
if (params.id) handleDeleteLetter(params.id);
141163
navigate(-1);
142164
}}
143165
/>

src/pages/RandomLetters/components/CoolTime.tsx

Lines changed: 26 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,22 @@ import { useNavigate } from 'react-router';
33

44
import LetterWrapper from '@/components/LetterWrapper';
55
import { formatNumber } from '@/utils/formatNumber';
6+
import { timeFormatter } from '@/utils/timeFormatter';
67

78
// import letterPink from '@/assets/images/letter-pink.png';
89

910
export default function CoolTime({
10-
setCoolTime,
11+
setIsCoolTime,
12+
coolTime,
1113
}: {
12-
setCoolTime: React.Dispatch<React.SetStateAction<boolean>>;
14+
setIsCoolTime: React.Dispatch<React.SetStateAction<boolean>>;
15+
coolTime: CoolTime;
1316
}) {
1417
const navigate = useNavigate();
1518

16-
const TIME_STAMP = '2025-02-26T22:13:25.262045608';
19+
const TIME_STAMP = coolTime?.lastMatchedAt
20+
? coolTime.lastMatchedAt
21+
: '2025-03-01T21:15:25.262045608';
1722

1823
const COMPLETED_DATE = new Date(TIME_STAMP);
1924

@@ -24,56 +29,36 @@ export default function CoolTime({
2429

2530
const endTime = END_DATE.getTime() - NOW_DATE.getTime();
2631

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+
const [endTimeSeconds, setEndTimeSeconds] = useState(endTime / 1000);
33+
34+
const formatedEndTime = timeFormatter(endTimeSeconds);
3235

3336
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);
37+
const endTargetTime = Date.now() + endTime;
38+
39+
const count = setInterval(() => {
40+
const now = Date.now();
41+
const newEndTimeSeconds = Math.max(0, Math.floor((endTargetTime - now) / 1000));
42+
43+
if (endTimeSeconds <= 0) {
44+
setIsCoolTime(false);
6345
}
46+
47+
setEndTimeSeconds(newEndTimeSeconds);
6448
}, 1000);
6549

6650
return () => {
67-
clearInterval(endTimeFlow);
51+
clearInterval(count);
6852
};
69-
}, [endTimes, setCoolTime]);
53+
});
54+
7055
return (
7156
<div className="mt-20 flex flex-col items-center justify-center">
7257
<div className="body-m flex flex-col items-center justify-center">
7358
<p className="text-gray-60">랜덤 편지 활성화 까지</p>
7459
<p className="text-gray-80">
75-
{formatNumber(endTimes.hours)} : {formatNumber(endTimes.minutes)} :{' '}
76-
{formatNumber(endTimes.seconds)}
60+
{formatNumber(formatedEndTime.hours)} : {formatNumber(formatedEndTime.minutes)} :{' '}
61+
{formatNumber(formatedEndTime.seconds)}
7762
</p>
7863
<div className="mt-2 w-75">
7964
<LetterWrapper>

0 commit comments

Comments
 (0)