Skip to content

Commit f50d008

Browse files
committed
feat: 게시판 데이터 바인딩
- 게시판에 무한 스크롤 반영 - 게시판 데이터 바인딩 - myPage와 게시판 화면 분리 - header에 myPage 링크
1 parent 6a95bdb commit f50d008

File tree

9 files changed

+241
-60
lines changed

9 files changed

+241
-60
lines changed

src/App.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import LetterBoxDetailPage from './pages/LetterBoxDetail';
1818
import LetterDetailPage from './pages/LetterDetail';
1919
import LoginPage from './pages/Login';
2020
import MyPage from './pages/MyPage';
21+
import MyBoardPage from './pages/MyPage/components/MyBoardPage';
2122
import NotFoundPage from './pages/NotFound';
2223
import NotificationsPage from './pages/Notifications';
2324
import OnboardingPage from './pages/Onboarding';
@@ -57,7 +58,7 @@ const App = () => {
5758
</Route>
5859
<Route path="mypage" element={<Layout />}>
5960
<Route index element={<MyPage />} />
60-
<Route path="board" element={<LetterBoardPage />} />
61+
<Route path="board" element={<MyBoardPage />} />
6162
<Route path="notifications" element={<NotificationsPage />} />
6263
</Route>
6364
</Route>

src/apis/myPage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const fetchMyPageInfo = async () => {
1212

1313
export const getMySharePostList = async () => {
1414
try {
15-
const response = await client.get('/api/share-proposal/inbox');
15+
const response = await client.get('/api/share-proposals/inbox');
1616
if (!response) throw new Error('error while fetching my share post list');
1717
return response.data;
1818
} catch (error) {

src/apis/share.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,7 @@ export interface SharePostApproval {
3838
}
3939

4040
// 공유 게시글 목록 조회
41-
export const getSharePostList = async (
42-
page: number = 1,
43-
size: number = 10,
44-
): Promise<SharePostResponse> => {
41+
export const getSharePostList = async (page: number = 1, size: number = 10) => {
4542
try {
4643
const response = await client.get('/api/share-posts', {
4744
params: { page, size },
@@ -70,14 +67,12 @@ export const getSharePostDetail = async (sharePostId: number): Promise<SharePost
7067
// 공유 요청 보내기
7168
export const postShareProposals = async (
7269
letterIds: number[],
73-
requesterId: number,
7470
recipientId: number,
7571
message: string,
7672
) => {
7773
try {
7874
const response = await client.post('/api/share-proposals', {
7975
letterIds: letterIds,
80-
requesterId,
8176
recipientId,
8277
message,
8378
});
@@ -105,3 +100,27 @@ export const postShareProposalApproval = async (
105100
throw new Error(`편지 공유 ${action === 'approve' ? '수락' : '거부'} 실패`);
106101
}
107102
};
103+
104+
// 편지 좋아요 추가, 취소
105+
export const postSharePostLike = async (sharePostId: number) => {
106+
try {
107+
const response = await client.post(`/api/share-posts/${sharePostId}/likes`);
108+
if (!response) throw new Error('error while posting like');
109+
return response.data;
110+
} catch (error) {
111+
console.error('❌ 편지 좋아요 중 에러가 발생했습니다', error);
112+
throw new Error('편지 좋아요 실패');
113+
}
114+
};
115+
116+
// 편지 좋아요 갯수
117+
export const getSharePostLikeCount = async (sharePostId: number) => {
118+
try {
119+
const response = await client.get(`/api/share-posts/${sharePostId}/likes`);
120+
if (!response) throw new Error('error while fetching likes');
121+
return response.data;
122+
} catch (error) {
123+
console.error('❌ 편지 좋아요 갯수 조회 중 에러가 발생했습니다', error);
124+
throw new Error('편지 좋아요 갯수 조회 실패');
125+
}
126+
};

src/layouts/Header.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ const Header = () => {
1515
<Link to="/mypage/notifications">
1616
<AlarmIcon className="h-6 w-6 text-white" />
1717
</Link>
18-
<PersonIcon className="h-6 w-6 text-white" />
18+
<Link to="/mypage">
19+
<PersonIcon className="h-6 w-6 text-white" />
20+
</Link>
1921
</div>
2022
</header>
2123
);
Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
1-
import { Link } from 'react-router';
1+
import { forwardRef } from 'react';
2+
import { useNavigate } from 'react-router';
23

34
import LetterWrapper from '@/components/LetterWrapper';
45

56
interface LetterPreviewProps {
6-
id: string;
7+
id: number;
78
to: string;
89
from: string;
910
content: string;
1011
}
1112

12-
const LetterPreview = ({ id, to, from, content }: LetterPreviewProps) => {
13+
const LetterPreview = forwardRef<HTMLDivElement, LetterPreviewProps>((props, ref) => {
14+
const { id, to, from, content }: LetterPreviewProps = props;
15+
const navigate = useNavigate();
1316
return (
14-
<Link to={id}>
15-
<LetterWrapper className="px-3 py-2">
16-
<div className="caption-r flex flex-col gap-2">
17-
<p>From.{from}</p>
18-
<p className="line-clamp-2 font-light">{content}</p>
19-
<p className="place-self-end">To.{to}</p>
20-
</div>
21-
</LetterWrapper>
22-
</Link>
17+
<LetterWrapper className="px-3 py-2" ref={ref} onClick={() => navigate(`/board/letter/${id}`)}>
18+
<div className="caption-r flex flex-col gap-2">
19+
<p>From.{from}</p>
20+
<p className="line-clamp-2 font-light">{content}</p>
21+
<p className="place-self-end">To.{to}</p>
22+
</div>
23+
</LetterWrapper>
2324
);
24-
};
25+
});
2526

2627
export default LetterPreview;

src/pages/LetterBoard/index.tsx

Lines changed: 71 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,87 @@
1-
import { twMerge } from 'tailwind-merge';
1+
import { useInfiniteQuery } from '@tanstack/react-query';
2+
import { useEffect } from 'react';
3+
import { useInView } from 'react-intersection-observer';
4+
import { useNavigate } from 'react-router';
25

6+
import { getSharePostList } from '@/apis/share';
37
import BackgroundBottom from '@/components/BackgroundBottom';
48
import NoticeRollingPaper from '@/components/NoticeRollingPaper';
59
import PageTitle from '@/components/PageTitle';
610

711
import LetterPreview from './components/LetterPreview';
812

913
const LetterBoardPage = () => {
10-
const isMyBoard = window.location.pathname.includes('/mypage');
14+
const navigate = useNavigate();
15+
const { ref, inView } = useInView();
16+
17+
const fetchPostList = async (page: number = 1) => {
18+
try {
19+
const response = await getSharePostList(page);
20+
if (!response) throw new Error('게시글 목록을 불러오는데 실패했습니다.');
21+
console.log('page', response);
22+
return response as SharePostResponse;
23+
} catch (e) {
24+
console.error(e);
25+
}
26+
};
27+
28+
const { data, isLoading, isError, fetchNextPage, hasNextPage, isFetchingNextPage } =
29+
useInfiniteQuery({
30+
queryKey: ['sharePostList'],
31+
queryFn: ({ pageParam = 1 }) => fetchPostList(pageParam),
32+
enabled: true,
33+
initialPageParam: 1,
34+
getNextPageParam: (res) => {
35+
if (!res || res.currentPage >= res.totalPages) {
36+
return undefined;
37+
}
38+
return res.currentPage + 1;
39+
},
40+
staleTime: 1000 * 60 * 5,
41+
gcTime: 1000 * 60 * 10,
42+
});
43+
44+
const postLists = data?.pages.flatMap((page) => page?.content) || [];
45+
46+
useEffect(() => {
47+
if (!hasNextPage) return;
48+
if (inView && !isFetchingNextPage) {
49+
fetchNextPage();
50+
}
51+
}, [inView, hasNextPage, isFetchingNextPage, fetchNextPage]);
52+
53+
if (isError) {
54+
navigate('/notFound');
55+
}
1156

1257
return (
1358
<>
14-
<main className={twMerge('flex grow flex-col px-5 pt-20 pb-10', !isMyBoard && 'mt-[-25px]')}>
15-
{isMyBoard ? (
16-
<PageTitle className="mx-auto mb-11">내가 올린 게시물</PageTitle>
59+
<main className="mt-[-25px] flex grow flex-col px-5 pt-20 pb-10">
60+
<>
61+
<NoticeRollingPaper />
62+
<PageTitle className="mx-auto mt-4">게시판</PageTitle>
63+
<p className="text-gray-60 caption-m mt-4.5 text-center">
64+
따숨이에게 힘이 되었던 다양한 편지들을 모아두었어요
65+
</p>
66+
</>
67+
{isLoading ? (
68+
<p>loading</p>
1769
) : (
18-
<>
19-
<NoticeRollingPaper />
20-
<PageTitle className="mx-auto mt-4">게시판</PageTitle>
21-
<p className="text-gray-60 caption-m mt-4.5 text-center">
22-
따숨이에게 힘이 되었던 다양한 편지들을 모아두었어요
23-
</p>
24-
</>
70+
<section className="mt-6 grid grid-cols-2 gap-x-5 gap-y-4">
71+
{postLists.map((item, index) => {
72+
return (
73+
<LetterPreview
74+
key={index}
75+
id={item?.sharePostId || 0}
76+
to={item?.receiverZipCode || 'ERROR'}
77+
from={item?.writerZipCode || 'ERROR'}
78+
content={item?.content || 'no Data'}
79+
ref={index === postLists.length - 1 ? ref : null}
80+
/>
81+
);
82+
})}
83+
</section>
2584
)}
26-
<section className="mt-6 grid grid-cols-2 gap-x-5 gap-y-4">
27-
{Array.from({ length: 10 }).map((_, index) => (
28-
<LetterPreview
29-
key={index}
30-
id={`${index}`}
31-
to="12E21"
32-
from="12E21"
33-
content="저희가 주고 받은 행운의 편지 저희가 주고 받은 행운의 편지 저희가 주고 받은 행운의 편지
34-
저희가 주고 받은 행운의 편지"
35-
/>
36-
))}
37-
</section>
3885
</main>
3986
<BackgroundBottom />
4087
</>

src/pages/LetterBoardDetail/index.tsx

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ import { useEffect, useState } from 'react';
22
import { useLocation, useNavigate } from 'react-router';
33
import { twMerge } from 'tailwind-merge';
44

5-
import { getSharePostDetail, postShareProposalApproval, SharePost } from '@/apis/share';
5+
import {
6+
getSharePostDetail,
7+
postShareProposalApproval,
8+
SharePost,
9+
postSharePostLike,
10+
getSharePostLikeCount,
11+
} from '@/apis/share';
612
import BlurImg from '@/assets/images/landing-blur.png';
713
import ReportModal from '@/components/ReportModal';
814

@@ -32,27 +38,37 @@ const LetterBoardDetailPage = ({ confirmDisabled }: ShareLetterPreviewProps) =>
3238
const [postDetail, setPostDetail] = useState<SharePost>();
3339

3440
useEffect(() => {
35-
const fetchPostDetail = async () => {
36-
console.log('location.state:', location.state);
37-
41+
const { sharePostId } = location.state.postDetail;
42+
const fetchPostDetail = async (postId: number) => {
3843
try {
39-
if (location.state?.postDetail) {
40-
const { sharePostId } = location.state.postDetail;
41-
42-
console.log('sharePostId:', sharePostId);
44+
console.log('sharePostId:', postId);
4345

44-
const data = await getSharePostDetail(sharePostId);
46+
const data = await getSharePostDetail(postId);
4547

46-
setPostDetail(data);
47-
} else {
48-
console.warn('postDetail not found in location.state');
49-
}
48+
setPostDetail(data);
5049
} catch (error) {
5150
console.error('❌ 공유 게시글 상세 조회에 실패했습니다.', error);
5251
}
5352
};
5453

55-
fetchPostDetail();
54+
const fetchLikeCounts = async (postId: number) => {
55+
try {
56+
const response = await getSharePostLikeCount(postId);
57+
if (!response) throw new Error('error while fetching like count');
58+
console.log(response);
59+
setLikeCount(response.data.likeCount);
60+
} catch (error) {
61+
console.error('❌ 편지 좋아요 갯수를 가져오는 중 에러가 발생했습니다', error);
62+
throw new Error('편지 좋아요 갯수 가져오기 실패');
63+
}
64+
};
65+
66+
if (location.state?.postDetail) {
67+
fetchPostDetail(sharePostId);
68+
fetchLikeCounts(sharePostId);
69+
} else {
70+
console.warn('postDetail not found in location.state');
71+
}
5672
}, [location.state]);
5773

5874
const handleProposalApproval = async (
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { useQuery } from '@tanstack/react-query';
2+
import { useNavigate } from 'react-router';
3+
import { twMerge } from 'tailwind-merge';
4+
5+
import { getMySharePostList } from '@/apis/mypage';
6+
import BackgroundBottom from '@/components/BackgroundBottom';
7+
import PageTitle from '@/components/PageTitle';
8+
9+
import LetterPreview from '../../LetterBoard/components/LetterPreview';
10+
11+
const MyBoardPage = () => {
12+
const navigate = useNavigate();
13+
14+
const fetchMyPostList = async () => {
15+
try {
16+
const response = await getMySharePostList();
17+
if (!response) throw new Error('게시글 목록을 불러오는데 실패했습니다.');
18+
console.log(response);
19+
return response.data;
20+
} catch (e) {
21+
console.error(e);
22+
}
23+
};
24+
25+
const {
26+
data: postLists = [],
27+
isLoading,
28+
isError,
29+
} = useQuery({
30+
queryKey: ['sharePostList'],
31+
queryFn: () => fetchMyPostList(),
32+
enabled: true,
33+
staleTime: 1000 * 60 * 5,
34+
gcTime: 1000 * 60 * 10,
35+
});
36+
37+
if (isError) {
38+
navigate('/notFound');
39+
}
40+
return (
41+
<>
42+
<main className={twMerge('flex grow flex-col px-5 pt-20 pb-10')}>
43+
<PageTitle className="mx-auto mb-11">내가 올린 게시물</PageTitle>
44+
{isLoading ? (
45+
<p>loading</p>
46+
) : (
47+
<section className="mt-6 grid grid-cols-2 gap-x-5 gap-y-4">
48+
{postLists.map((item, index) => (
49+
<LetterPreview
50+
key={index}
51+
id={item.sharePostId}
52+
to={item.writerZipCode}
53+
from="12E21"
54+
content="저희가 주고 받은 행운의 편지 저희가 주고 받은 행운의 편지 저희가 주고 받은 행운의 편지
55+
저희가 주고 받은 행운의 편지"
56+
/>
57+
))}
58+
</section>
59+
)}
60+
</main>
61+
<BackgroundBottom />
62+
</>
63+
);
64+
};
65+
66+
export default MyBoardPage;

0 commit comments

Comments
 (0)