Skip to content

Commit 7662866

Browse files
authored
feat: 편지 게시글 공유 기능 구현 (mock api) (#53)
* feat: 공유 게시글 목록 조회 기능 구현 * feat: 공유 게시글 상세 조회 기능 구현 * feat: 게시글 공유 요청 수락/거절 기능 구현 * perf: NewLetterModal의 불필요한 api 호출 제거 * fix: HomeRight의 useEffect 의존성 배열에 fetchIncomingLetters 추가 * fix: 공유 편지 미리보기에서 텍스트와 버튼이 겹쳐보이는 오류 해결 * refactor: hasNewLetters 로컬 상태 제거 및 arrivedCount 전역 상태로 대체 * fix: 특정 모바일 크기에서 내 편지함이 가려져 잘 안 보이는 오류 해결 * fix: 특정 모바일 크기에서 게시판이 잘 안 보이는 오류 해결 * design: 홈 페이지 공유 요청 모달에 스크롤 기능 추가 * style: 불필요한 console.log 삭제 * refactor: 테스트용 코드 삭제 * refactor: 'Letter' 인터페이스 이름을 'ShareLetter'로 변경 * fix: selector로 필요한 값만 불러오도록 수정하여 불필요한 렌더링 방지
1 parent 285cdae commit 7662866

File tree

9 files changed

+199
-76
lines changed

9 files changed

+199
-76
lines changed

src/apis/share.ts

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

3+
//공유 게시글 상세 페이지 편지
4+
interface ShareLetter {
5+
id: number;
6+
content: string;
7+
writerZipCode: string;
8+
receiverZipCode: string;
9+
}
10+
11+
// 공유 게시글 목록 조회 타입
12+
export interface SharePost {
13+
writerZipCode: number;
14+
receiverZipCode: number;
15+
content: string;
16+
createdAt: string;
17+
active: boolean;
18+
sharePostId: number;
19+
sharePostContent: string;
20+
letters: ShareLetter[];
21+
}
22+
23+
// 페이징 포함
24+
export interface SharePostResponse {
25+
content: SharePost[];
26+
currentPage: number;
27+
size: number;
28+
totalElements: number;
29+
totalPages: number;
30+
}
31+
32+
// 편지 공유 수락 / 거절
33+
export interface SharePostApproval {
34+
shareProposalId: number;
35+
status: 'APPROVED' | 'REJECTED';
36+
sharePostId: number;
37+
}
38+
39+
// 공유 게시글 목록 조회
40+
export const getSharePostList = async (
41+
page: number = 1,
42+
size: number = 10,
43+
): Promise<SharePostResponse> => {
44+
try {
45+
const response = await client.get('/api/share-posts', {
46+
params: { page, size },
47+
});
48+
console.log(`🌟공유 게시글 목록`, response.data);
49+
50+
return response.data;
51+
} catch (error) {
52+
console.error('❌ 편지 공유 게시글 목록을 조회하던 중 에러가 발생했습니다', error);
53+
throw new Error('편지 공유 게시글 목록 조회 실패');
54+
}
55+
};
56+
57+
// 공유 게시글 상세 조회
58+
export const getSharePostDetail = async (sharePostId: number): Promise<SharePost> => {
59+
try {
60+
const response = await client.get(`/api/share-posts/${sharePostId}`);
61+
console.log(`🔥공유 게시글 상세 데이터`, response.data);
62+
return response.data.data;
63+
} catch (error) {
64+
console.error('❌ 편지 공유 게시글을 상세 조회하던 중 에러가 발생했습니다', error);
65+
throw new Error('편지 공유 게시글 상세 조회 실패');
66+
}
67+
};
68+
69+
// 공유 요청 보내기
370
export const postShareProposals = async (
471
letterIds: number[],
572
requesterId: number,
@@ -9,13 +76,31 @@ export const postShareProposals = async (
976
try {
1077
const response = await client.post('/api/share-proposals', {
1178
letterIds: letterIds,
12-
requesterId: requesterId,
13-
recipientId: recipientId,
14-
message: message,
79+
requesterId,
80+
recipientId,
81+
message,
1582
});
1683
if (!response) throw new Error('error while fetching mailbox data');
17-
return response;
84+
return response.data;
85+
} catch (error) {
86+
console.error('❌ 공유 요청 보내기 중 에러가 발생했습니다', error);
87+
throw new Error('공유 요청 실패');
88+
}
89+
};
90+
91+
// 편지 공유 수락 / 거절
92+
export const postShareProposalApproval = async (
93+
shareProposalId: number,
94+
action: 'approve' | 'reject',
95+
): Promise<SharePostApproval> => {
96+
try {
97+
const response = await client.patch(`/api/share-proposal/${shareProposalId}/${action}`);
98+
return response.data;
1899
} catch (error) {
19-
console.error(error);
100+
console.error(
101+
`❌ 편지 공유 ${action === 'approve' ? '수락' : '거절'} 중 에러가 발생했습니다`,
102+
error,
103+
);
104+
throw new Error(`편지 공유 ${action === 'approve' ? '수락' : '거부'} 실패`);
20105
}
21106
};

src/pages/Home/components/GoToLetterBoard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import goToLetterBoard from '@/assets/images/go-to-letter-board.png';
44

55
const GoToLetterBoard = () => {
66
return (
7-
<div className="absolute bottom-48 left-[calc(var(--vh)*36)] z-9 flex w-full">
7+
<div className="absolute bottom-48 left-[calc(var(--vh)*33)] z-9 flex w-full">
88
<div className="text-left">
99
<p className="text-gray-60 body-r mb-1 ml-2">게시판</p>
1010
<Link to="/board/letter">

src/pages/Home/components/GoToLetterBox.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@ import { Link } from 'react-router';
22

33
import goToLetterBoxNewLetters from '@/assets/images/go-to-letter-box-new-letters.png';
44
import goToLetterBox from '@/assets/images/go-to-letter-box.png';
5+
import { useIncomingLettersStore } from '@/stores/incomingLettersStore';
56

67
const GoToLetterBox = () => {
7-
//TODO : hasNewLetters 전역으로 상태 관리하기
8-
const hasNewLetters = true;
8+
const arrivedCount = useIncomingLettersStore((state) => state.arrivedCount);
9+
910
return (
10-
<div className="absolute bottom-10 left-5 z-9 flex w-fit">
11+
<div className="absolute bottom-15 left-5 z-9 flex">
1112
<div className="text-left">
1213
<p className="text-gray-60 body-r mb-1 ml-2">내 편지함</p>
1314
<Link to="/letter/box">
1415
<img
15-
src={hasNewLetters ? goToLetterBoxNewLetters : goToLetterBox}
16+
src={arrivedCount ? goToLetterBoxNewLetters : goToLetterBox}
1617
alt="go to letter box"
1718
className="w-[206.5px]"
1819
/>

src/pages/Home/components/HomeRight.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const HomeRight = () => {
1111
const { arrivedCount, fetchIncomingLetters } = useIncomingLettersStore();
1212
useEffect(() => {
1313
fetchIncomingLetters();
14-
}, []);
14+
}, [fetchIncomingLetters]);
1515

1616
return (
1717
<div className="flex h-screen w-full max-w-150 min-w-[300px] flex-shrink-0 grow snap-start flex-col items-center overflow-x-hidden pt-5">

src/pages/Home/components/NewLetterModal.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
1-
import { useEffect } from 'react';
2-
31
import { useIncomingLettersStore } from '@/stores/incomingLettersStore';
42

53
//TODO: 내 편지함 상세 조회에서 해당 편지를 조회하면 arrivedCount가 1 감소하도록
64
const NewLetterModal = () => {
7-
const { arrivedCount, fetchIncomingLetters } = useIncomingLettersStore();
8-
9-
useEffect(() => {
10-
fetchIncomingLetters();
11-
}, []);
5+
const arrivedCount = useIncomingLettersStore((state) => state.arrivedCount);
126

137
return (
148
<p className="text-gray-60 body-b absolute top-30 mb-10 w-fit animate-pulse rounded-full bg-white px-6 py-4">

src/pages/Home/components/ShowIncomingLettersModal.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useEffect } from 'react';
22
import { useNavigate } from 'react-router';
3+
34
import ModalBackgroundWrapper from '@/components/ModalBackgroundWrapper';
45
import ModalOverlay from '@/components/ModalOverlay';
56
import { useIncomingLettersStore } from '@/stores/incomingLettersStore';
@@ -22,7 +23,7 @@ const ShowIncomingLettersModal = ({ onClose }: ShowIncomingLettersModalProps) =>
2223

2324
useEffect(() => {
2425
fetchIncomingLetters();
25-
});
26+
}, []);
2627

2728
return (
2829
<ModalOverlay closeOnOutsideClick onClose={onClose}>

src/pages/Home/components/ShowShareAccessModal.tsx

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import React from 'react';
1+
import React, { useEffect, useState } from 'react';
22
import { useNavigate } from 'react-router';
33

4+
import { getSharePostDetail, getSharePostList } from '@/apis/share';
5+
import { SharePostResponse } from '@/apis/share';
46
import ModalBackgroundWrapper from '@/components/ModalBackgroundWrapper';
57
import ModalOverlay from '@/components/ModalOverlay';
68

@@ -9,21 +11,35 @@ interface ShowShareAccessModalProps {
911
onClose: () => void;
1012
}
1113

12-
const DUMMY_SHARE_ACCESS = [
13-
{ id: 1, zip_code: '235EA' },
14-
{ id: 2, zip_code: '711PO' },
15-
{ id: 3, zip_code: '105CF' },
16-
{ id: 4, zip_code: '299EB' },
17-
];
18-
1914
const ShowShareAccessModal = ({ onClose }: ShowShareAccessModalProps) => {
2015
const navigate = useNavigate();
2116

22-
const handleNavigation = (accessId: number) => {
23-
navigate(`/board/letter/${accessId}`, {
24-
state: { isShareLetterPreview: true },
25-
});
17+
const [sharePosts, setSharePosts] = useState<SharePostResponse>();
18+
19+
useEffect(() => {
20+
const fetchPosts = async () => {
21+
try {
22+
const data = await getSharePostList(1, 10);
23+
setSharePosts(data);
24+
} catch (error) {
25+
console.error('❌ 게시글 목록을 불러오는 데 실패했습니다.', error);
26+
}
27+
};
28+
29+
fetchPosts();
30+
}, []);
31+
32+
const handleNavigation = async (sharePostId: number) => {
33+
try {
34+
const postDetail = await getSharePostDetail(sharePostId);
35+
navigate(`/board/letter/${sharePostId}`, {
36+
state: { postDetail, isShareLetterPreview: true },
37+
});
38+
} catch (error) {
39+
console.error('❌ 게시글 상세 페이지로 이동하는 데에 실패했습니다.', error);
40+
}
2641
};
42+
2743
return (
2844
<ModalOverlay closeOnOutsideClick onClose={onClose}>
2945
<div className="flex h-full flex-col items-center justify-center">
@@ -39,14 +55,14 @@ const ShowShareAccessModal = ({ onClose }: ShowShareAccessModalProps) => {
3955
허락 여부를 체크해주세요!
4056
</p>
4157
</div>
42-
<div className="mt-6 flex w-[251px] flex-col gap-[10px]">
43-
{DUMMY_SHARE_ACCESS.map((access) => (
58+
<div className="mt-6 flex max-h-60 min-h-auto w-[251px] flex-col gap-[10px] overflow-y-scroll [&::-webkit-scrollbar]:hidden">
59+
{sharePosts?.content.map((post) => (
4460
<button
4561
className="text-gray-80 body-m flex h-10 w-full items-center justify-between gap-1 rounded-lg bg-white p-3"
46-
key={access.id}
47-
onClick={() => handleNavigation(access.id)}
62+
key={post.sharePostId}
63+
onClick={() => handleNavigation(post.sharePostId)}
4864
>
49-
<p>{access.zip_code}님의 공유 요청</p>
65+
<p>{post.writerZipCode}님의 공유 요청</p>
5066
</button>
5167
))}
5268
</div>

src/pages/LetterBoardDetail/components/Letter.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@ import MemoWrapper from '@/components/MemoWrapper';
22

33
interface LetterProps {
44
letter: {
5-
receiver: string;
5+
receiverZipCode: string;
66
content: string;
7-
sender: string;
7+
writerZipCode: string;
88
};
9-
isSender?: boolean;
9+
isWriter?: boolean;
1010
}
1111

12-
const Letter = ({ letter, isSender = false }: LetterProps) => {
12+
const Letter = ({ letter, isWriter = false }: LetterProps) => {
1313
return (
14-
<MemoWrapper isSender={isSender}>
14+
<MemoWrapper isSender={isWriter}>
1515
<div className="flex flex-col gap-2 text-black">
16-
<p className="body-sb">To. {letter.receiver}</p>
16+
<p className="body-sb">To. {letter.receiverZipCode}</p>
1717
<p className="body-r leading-[26px] whitespace-pre-wrap">{letter.content}</p>
18-
<p className="body-m place-self-end">From. {letter.sender}</p>
18+
<p className="body-m place-self-end">From. {letter.writerZipCode}</p>
1919
</div>
2020
</MemoWrapper>
2121
);

0 commit comments

Comments
 (0)