Skip to content

Commit b8b7bef

Browse files
authored
feat:편지 작성 페이지 1차 기능구현 (#47)
* feat:편지전송 답장 post api 연결완료 * feat:매칭된 편지 작성시 api 분기 구현 * feat:카테고리 페이지 페이지 뒤로가기 버튼 제거 + 이전단계 위치 조정 * feat:옵션 모달 외부 클릭시 슬라이드 닫히는 로직 구현 * feat: 편지작성 페이지 전역변수 편지전송 관련 request값들 객체형태로 통일 * fix:코드리뷰 사항 수정
1 parent 5ca4f51 commit b8b7bef

File tree

14 files changed

+166
-141
lines changed

14 files changed

+166
-141
lines changed

src/apis/letterDetail.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,23 @@ import client from './client';
33
const getLetter = async (
44
letterId: string,
55
setLetterState: React.Dispatch<React.SetStateAction<LetterDetail | null>>,
6+
callBack?: () => void,
67
) => {
78
try {
89
const res = await client.get(`/api/letters/${letterId}`);
910
setLetterState(res.data.data);
11+
if (callBack) callBack();
1012
console.log(res);
1113
} catch (error) {
1214
console.error(error);
1315
}
1416
};
1517

16-
const deleteLetter = async (letterId: string) => {
18+
const deleteLetter = async (letterId: string, callBack?: () => void) => {
1719
try {
1820
console.log(`/api/letters/${letterId}`);
1921
const res = await client.delete(`/api/letters/${letterId}`);
22+
if (callBack) callBack();
2023
console.log(res);
2124
} catch (error) {
2225
console.error(error);

src/apis/write.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,24 @@
11
import client from './client';
22

3-
const postLetter = async (
4-
data: LetterRequest,
5-
setState?: React.Dispatch<React.SetStateAction<boolean>>,
6-
) => {
3+
const postLetter = async (data: LetterRequest, callBack?: () => void) => {
74
try {
85
const res = await client.post('/api/letters', data);
9-
if (setState) setState(true);
6+
if (callBack) callBack();
107
console.log(res);
118
} catch (error) {
129
console.error(error);
1310
}
1411
};
1512

1613
const getPrevLetter = async (
17-
setPrevLetterState: React.Dispatch<React.SetStateAction<PrevLetter[]>>,
1814
letterId: string,
15+
setPrevLetterState: React.Dispatch<React.SetStateAction<PrevLetter[]>>,
16+
callBack?: () => void,
1917
) => {
2018
try {
2119
const res = await client.get(`/api/letters/${letterId}/previous`);
2220
setPrevLetterState(res.data.data);
21+
if (callBack) callBack();
2322
console.log(res);
2423
} catch (error) {
2524
console.error(error);

src/components/ResultLetter.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ import LetterWrapper from './LetterWrapper';
55
export default function ResultLetter({
66
categoryName = 'CONSOLATION',
77
title,
8+
zipCode = 'error',
89
}: {
910
categoryName: Category;
1011
title: string;
12+
zipCode?: string;
1113
}) {
12-
const address = '1A3E2';
1314
const date = new Date();
1415
const today = `${date.getFullYear()}${date.getMonth() + 1}${date.getDate()}일`;
1516

@@ -26,14 +27,16 @@ export default function ResultLetter({
2627
<div className="flex flex-col gap-[5px]">
2728
<span className="caption-sb text-gray-60">{today}</span>
2829
<div className="flex gap-1">
29-
{address.split('').map((spell, idx) => (
30-
<span
31-
className="caption-r flex h-6 w-6 items-center justify-center rounded-sm bg-white/40"
32-
key={idx}
33-
>
34-
{spell}
35-
</span>
36-
))}
30+
{zipCode.split('').map((spell, idx) => {
31+
return (
32+
<span
33+
className="caption-r flex h-6 w-6 items-center justify-center rounded-sm bg-white/40"
34+
key={idx}
35+
>
36+
{spell}
37+
</span>
38+
);
39+
})}
3740
</div>
3841
</div>
3942
</div>

src/pages/Write/CategorySelect.tsx

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import { useState } from 'react';
21
import { Link } from 'react-router';
32

43
import { postLetter } from '@/apis/write';
5-
import BackButton from '@/components/BackButton';
64
import PageTitle from '@/components/PageTitle';
75
import CategoryList from '@/pages/Write/components/CategoryList';
86
import useWrite from '@/stores/writeStore';
@@ -13,33 +11,20 @@ import WritePageButton from './components/WritePageButton';
1311
export default function CategorySelect({
1412
setStep,
1513
prevLetter,
14+
send,
15+
setSend,
1616
}: {
1717
setStep: React.Dispatch<React.SetStateAction<Step>>;
1818
prevLetter: PrevLetter[];
19+
send: boolean;
20+
setSend: React.Dispatch<React.SetStateAction<boolean>>;
1921
}) {
20-
const letterTitle = useWrite((state) => state.letterTitle);
21-
const letterText = useWrite((state) => state.letterText);
22-
const category = useWrite((state) => state.category);
23-
const paperType = useWrite((state) => state.paperType);
24-
const fontType = useWrite((state) => state.fontType);
25-
26-
const [send, setSend] = useState<boolean>(false);
27-
28-
const LETTER_REQUEST: LetterRequest = {
29-
receiver: null,
30-
parentLetterId: null,
31-
title: letterTitle,
32-
content: letterText,
33-
category: category,
34-
paperType: paperType,
35-
fontType: fontType,
36-
};
22+
const letterRequest = useWrite((state) => state.letterRequest);
3723

3824
return (
3925
<>
4026
<div className="flex w-full grow flex-col items-center">
4127
<div className="absolute left-0 flex w-full items-center justify-between px-5">
42-
<BackButton />
4328
{!send && prevLetter.length <= 0 && (
4429
<WritePageButton
4530
text="이전 단계"
@@ -60,9 +45,9 @@ export default function CategorySelect({
6045
{/* 카테고리 선택 컴포넌트 */}
6146
{!send && prevLetter.length <= 0 && <CategoryList />}
6247

63-
{prevLetter.length > 0 && (
48+
{send && prevLetter.length > 0 && (
6449
<div className="mt-25 flex w-full max-w-[300px] flex-col items-center gap-5">
65-
<ResultLetterAnimation categoryName="답변자" />
50+
<ResultLetterAnimation />
6651
<div className="animate-show-text flex flex-col items-center opacity-0">
6752
<span className="body-sb text-gray-60">작성하신 편지는</span>
6853
<span className="body-sb text-gray-60">
@@ -74,9 +59,9 @@ export default function CategorySelect({
7459
</div>
7560
)}
7661

77-
{send && (
62+
{send && prevLetter.length <= 0 && (
7863
<div className="mt-25 flex w-full max-w-[300px] flex-col items-center gap-5">
79-
<ResultLetterAnimation categoryName={category} />
64+
<ResultLetterAnimation />
8065
<span className="animate-show-text body-sb text-gray-60 opacity-0">
8166
두근두근! 답장이 언제 올까요?
8267
</span>
@@ -94,8 +79,11 @@ export default function CategorySelect({
9479
<button
9580
className="bg-primary-3 body-m mt-auto flex h-10 w-full items-center justify-center rounded-lg"
9681
onClick={() => {
97-
if (category) {
98-
postLetter(LETTER_REQUEST, setSend);
82+
if (letterRequest.category) {
83+
postLetter(letterRequest, () => {
84+
console.log(letterRequest);
85+
setSend(true);
86+
});
9987
// setSend(true);
10088
} else {
10189
alert('우표 선택을 해주세요');

src/pages/Write/LetterEditor.tsx

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { useRef } from 'react';
1+
import { useEffect, useState } from 'react';
2+
import { useLocation } from 'react-router';
23
import { twMerge } from 'tailwind-merge';
34

5+
import { postLetter } from '@/apis/write';
46
import BackButton from '@/components/BackButton';
57
import WritePageButton from '@/pages/Write/components/WritePageButton';
68
import { FONT_TYPE_OBJ } from '@/pages/Write/constants';
@@ -10,26 +12,35 @@ import useWrite from '@/stores/writeStore';
1012
export default function LetterEditor({
1113
setStep,
1214
prevLetter,
15+
setSend,
16+
searchParams,
1317
}: {
1418
setStep: React.Dispatch<React.SetStateAction<Step>>;
1519
prevLetter: PrevLetter[];
20+
setSend: React.Dispatch<React.SetStateAction<boolean>>;
21+
searchParams: URLSearchParams;
1622
}) {
17-
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
23+
const location = useLocation();
24+
const [randomMatched, setRandomMatched] = useState<boolean>(false);
1825

19-
const fontType = useWrite((state) => state.fontType);
26+
const letterRequest = useWrite((state) => state.letterRequest);
27+
const setLetterRequest = useWrite((state) => state.setLetterRequest);
2028

21-
const letterTitle = useWrite((state) => state.letterTitle);
22-
const setLetterTitle = useWrite((state) => state.setLetterTitle);
23-
24-
const letterText = useWrite((state) => state.letterText);
25-
const setLetterText = useWrite((state) => state.setLetterText);
29+
useEffect(() => {
30+
if (location.state?.randomMatched) {
31+
setRandomMatched(true);
32+
}
33+
}, [location.state?.randomMatched]);
2634

27-
const handleResizeHeight = () => {
28-
if (textareaRef.current !== null) {
29-
textareaRef.current.style.height = 'auto'; //height 초기화
30-
textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
35+
useEffect(() => {
36+
if (prevLetter.length > 0) {
37+
setLetterRequest({
38+
receiverId: prevLetter[0].memberId,
39+
parentLetterId: Number(searchParams.get('letterId')),
40+
category: prevLetter[0].category,
41+
});
3142
}
32-
};
43+
}, [prevLetter, searchParams, setLetterRequest]);
3344

3445
return (
3546
<div className="flex grow flex-col pb-15">
@@ -40,8 +51,17 @@ export default function LetterEditor({
4051
<WritePageButton
4152
text="답장 전송"
4253
onClick={() => {
43-
if (letterTitle.trim() !== '' && letterText.trim() !== '') {
44-
setStep('category');
54+
if (letterRequest.title.trim() !== '' && letterRequest.content.trim() !== '') {
55+
if (randomMatched) {
56+
console.log('랜덤편지 답장 전송용API');
57+
} else {
58+
postLetter(letterRequest, () => {
59+
console.log(letterRequest);
60+
console.log(prevLetter);
61+
setSend(true);
62+
setStep('category');
63+
});
64+
}
4565
} else {
4666
alert('편지 제목, 내용이 작성되었는지 확인해주세요');
4767
}
@@ -51,7 +71,7 @@ export default function LetterEditor({
5171
<WritePageButton
5272
text="다음 단계"
5373
onClick={() => {
54-
if (letterTitle.trim() !== '' && letterText.trim() !== '') {
74+
if (letterRequest.title.trim() !== '' && letterRequest.content.trim() !== '') {
5575
setStep('category');
5676
} else {
5777
alert('편지 제목, 내용이 작성되었는지 확인해주세요');
@@ -67,23 +87,22 @@ export default function LetterEditor({
6787
placeholder="제목을 입력해주세요."
6888
className="body-sb placeholder:text-gray-40 placeholder:border-0"
6989
onChange={(e) => {
70-
setLetterTitle(e.target.value);
90+
setLetterRequest({ title: e.target.value });
7191
}}
72-
value={letterTitle}
92+
value={letterRequest.title}
7393
/>
7494
</div>
7595
<div className="mt-9 flex grow">
7696
<textarea
7797
className={twMerge(
7898
`body-r basic-theme min-h-full w-full px-6`,
79-
`${FONT_TYPE_OBJ[fontType]}`,
99+
`${FONT_TYPE_OBJ[letterRequest.fontType]}`,
80100
)}
81101
placeholder="클릭해서 내용을 작성하세요"
82102
onChange={(e) => {
83-
handleResizeHeight();
84-
setLetterText(e.target.value);
103+
setLetterRequest({ ...letterRequest, content: e.target.value });
85104
}}
86-
value={letterText}
105+
value={letterRequest.content}
87106
></textarea>
88107
</div>
89108
</div>

src/pages/Write/OptionSlide.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ function OptionSlide({ prevLetter }: { prevLetter: PrevLetter[] }) {
1414
const optionRef = useRef<HTMLDivElement>(null);
1515

1616
useEffect(() => {
17+
const handleOutsideClick = (e: MouseEvent) => {
18+
const target = e.target as HTMLElement;
19+
if (target && !slideRef.current?.contains(target)) {
20+
setSlideActive(false);
21+
}
22+
};
23+
document.body.addEventListener('click', handleOutsideClick);
24+
1725
const handleSlideButton = () => {
1826
// ref가 처음 높이를 못 받아오는거 같아서 비동기로 후처리함
1927
if (slideRef.current) {
@@ -25,6 +33,10 @@ function OptionSlide({ prevLetter }: { prevLetter: PrevLetter[] }) {
2533
}
2634
};
2735
handleSlideButton();
36+
37+
return () => {
38+
document.body.removeEventListener('click', handleOutsideClick);
39+
};
2840
}, [slideActive]);
2941

3042
return (

0 commit comments

Comments
 (0)