From a4d10791a6b1a0e290c59582501dc578eb8c308f Mon Sep 17 00:00:00 2001 From: heeyongKim <166043860+heeeeyong@users.noreply.github.com> Date: Sun, 3 Aug 2025 15:53:12 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20axios=20API=20=ED=81=B4=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EC=96=B8=ED=8A=B8=20=EB=B0=8F=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=EC=9C=A0=ED=8B=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + src/api/index.ts | 87 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 src/api/index.ts diff --git a/.gitignore b/.gitignore index a547bf36..880e7b52 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ node_modules dist dist-ssr *.local +.env.local # Editor directories and files .vscode/* diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 00000000..18d14021 --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,87 @@ +import axios, { type AxiosResponse, type AxiosError } from 'axios'; + +// 하드코딩된 액세스 토큰 +const ACCESS_TOKEN = + 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjEsImlhdCI6MTc1NDIwMTY4OCwiZXhwIjoxNzU2NzkzNjg4fQ.oOyJ7JI_t2-Xq1-gfAv4ZaYNrbyplvqdxhCk76-Txe4'; + +// 토큰 관리 유틸리티 +export const TokenManager = { + setAccessToken: (token: string) => localStorage.setItem('accessToken', token), + getAccessToken: (): string | null => localStorage.getItem('accessToken'), + setRefreshToken: (token: string) => localStorage.setItem('refreshToken', token), + getRefreshToken: (): string | null => localStorage.getItem('refreshToken'), + clearTokens: () => { + localStorage.removeItem('accessToken'); + localStorage.removeItem('refreshToken'); + }, + hasValidToken: (): boolean => !!localStorage.getItem('accessToken'), +}; + +// API 기본 설정 +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL; + +// 환경변수 확인용 +console.log('API_BASE_URL:', API_BASE_URL); + +// axios 인스턴스 생성 +export const apiClient = axios.create({ + baseURL: API_BASE_URL, + timeout: 10000, + headers: { + 'Content-Type': 'application/json', + }, +}); + +// 요청 인터셉터 +apiClient.interceptors.request.use( + config => { + // 로컬스토리지에서 토큰 먼저 확인 + const token = TokenManager.getAccessToken(); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } else { + // 토큰이 없으면 하드코딩된 토큰 사용 (개발용) + config.headers.Authorization = ACCESS_TOKEN; + } + return config; + }, + error => Promise.reject(error), +); + +// 응답 인터셉터 - 토큰 만료 처리 및 에러 처리 +apiClient.interceptors.response.use( + (response: AxiosResponse) => response, + (error: AxiosError) => { + const { status } = error.response || {}; + + // 에러 로깅 + console.error('API Error:', status, error.message); + + // 토큰 만료 또는 인증 실패 시 로그인 페이지로 리다이렉트 + if (status === 401) { + // alert('토큰이 만료되었거나 유효하지 않습니다. 로그인 페이지로 이동합니다.'); + + // 현재 페이지가 로그인 페이지가 아닌 경우에만 리다이렉트 + if (window.location.pathname !== '/') { + // alert('로그인이 필요합니다. 로그인 페이지로 이동합니다.'); + window.location.href = '/'; + } + } + + // 권한 없음 (403) 에러 처리 + if (status === 403) { + console.warn('접근 권한이 없습니다.'); + alert('접근 권한이 없습니다.'); + } + + // 서버 에러 (500번대) 처리 + if (status && status >= 500) { + console.error('서버 오류가 발생했습니다.'); + alert('서버에 문제가 발생했습니다. 잠시 후 다시 시도해주세요.'); + } + + return Promise.reject(error); + }, +); + +export default apiClient; From fac5f78ad1dbc2a6a44b43e8922abe9538a4e67f Mon Sep 17 00:00:00 2001 From: heeyongKim <166043860+heeeeyong@users.noreply.github.com> Date: Sun, 3 Aug 2025 15:53:28 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20=EC=A0=84=EC=B2=B4=20=ED=94=BC?= =?UTF-8?q?=EB=93=9C=20=EC=A1=B0=ED=9A=8C=20=EB=B0=8F=20=EB=82=B4=20?= =?UTF-8?q?=ED=94=BC=EB=93=9C=20=EC=A1=B0=ED=9A=8C=20API=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/feeds/getMyFeed.ts | 65 +++++++++++ src/api/feeds/getTotalFeed.ts | 53 +++++++++ src/components/common/Post/PostBody.tsx | 10 +- src/components/common/Post/PostFooter.tsx | 14 ++- src/components/common/Post/PostHeader.tsx | 37 +++--- src/components/common/Post/Reply.tsx | 10 +- src/components/common/Post/SubReply.tsx | 10 +- src/components/feed/FeedDetailPostBody.tsx | 18 +-- src/data/postData.ts | 95 ++++++++-------- src/pages/feed/Feed.tsx | 126 ++++++++++++++++++++- src/types/post.ts | 26 ++--- 11 files changed, 358 insertions(+), 106 deletions(-) create mode 100644 src/api/feeds/getMyFeed.ts create mode 100644 src/api/feeds/getTotalFeed.ts diff --git a/src/api/feeds/getMyFeed.ts b/src/api/feeds/getMyFeed.ts new file mode 100644 index 00000000..325eb978 --- /dev/null +++ b/src/api/feeds/getMyFeed.ts @@ -0,0 +1,65 @@ +import { apiClient } from '../index'; +import type { PostData } from '@/types/post'; + +export interface MyFeedItem extends PostData { + feedId: number; + postDate: string; + isbn: number; + bookTitle: string; + bookAuthor: string; + contentBody: string; + contentsUrl: string[]; + likeCount: number; + commentCount: number; + isPublic: boolean; +} + +// API 응답 데이터 타입 +export interface MyFeedData { + feedList: MyFeedItem[]; + nextCursor: string; + isLast: boolean; +} + +// API 응답 타입 +export interface MyFeedResponse { + success: boolean; + code: number; + message: string; + data: MyFeedData; +} + +// 요청 파라미터 타입 +export interface GetMyFeedParams { + cursor?: string; // 첫 페이지는 null 또는 없음, 다음 페이지부터는 nextCursor 값 사용 +} + +// 내 피드 조회 API 함수 +export const getMyFeeds = async (params?: GetMyFeedParams): Promise => { + const queryParams = new URLSearchParams(); + + // cursor가 있을 때만 쿼리 파라미터에 추가 + if (params?.cursor) { + queryParams.append('cursor', params.cursor); + } + + const url = `/feeds/mine${queryParams.toString() ? `?${queryParams.toString()}` : ''}`; + + const response = await apiClient.get(url); + return response.data; +}; + +/* +// 첫 페이지 조회 +const firstPage = await getMyFeeds(); + +// 다음 페이지 조회 (nextCursor 사용) +const nextPage = await getMyFeeds({ + cursor: firstPage.data.nextCursor +}); + +// 마지막 페이지인지 확인 +if (firstPage.data.isLast) { + console.log('더 이상 불러올 데이터가 없습니다.'); +} +*/ diff --git a/src/api/feeds/getTotalFeed.ts b/src/api/feeds/getTotalFeed.ts new file mode 100644 index 00000000..81e44649 --- /dev/null +++ b/src/api/feeds/getTotalFeed.ts @@ -0,0 +1,53 @@ +import { apiClient } from '../index'; +import type { PostData } from '@/types/post'; + +// API 응답 데이터 타입 +export interface TotalFeedData { + feedList: PostData[]; + nextCursor: string; + isLast: boolean; +} + +// API 응답 타입 +export interface TotalFeedResponse { + success: boolean; + code: number; + message: string; + data: TotalFeedData; +} + +// 요청 파라미터 타입 +export interface GetTotalFeedParams { + cursor?: string; // 첫 페이지는 null 또는 없음, 다음 페이지부터는 nextCursor 값 사용 +} + +export const getTotalFeeds = async (params?: GetTotalFeedParams): Promise => { + const queryParams = new URLSearchParams(); + + // cursor가 있을 때만 쿼리 파라미터에 추가 + if (params?.cursor) { + queryParams.append('cursor', params.cursor); + } + + const url = `/feeds${queryParams.toString() ? `?${queryParams.toString()}` : ''}`; + + const response = await apiClient.get(url); + return response.data; +}; + +/* +사용 방법: + +// 첫 페이지 조회 +const firstPage = await getTotalFeeds(); + +// 다음 페이지 조회 (nextCursor 사용) +const nextPage = await getTotalFeeds({ + cursor: firstPage.data.nextCursor +}); + +// 마지막 페이지인지 확인 +if (firstPage.data.isLast) { + console.log('더 이상 불러올 데이터가 없습니다.'); +} +*/ diff --git a/src/components/common/Post/PostBody.tsx b/src/components/common/Post/PostBody.tsx index 6131d6b2..0e48816f 100644 --- a/src/components/common/Post/PostBody.tsx +++ b/src/components/common/Post/PostBody.tsx @@ -48,12 +48,12 @@ const PostBody = ({ bookTitle, isbn, bookAuthor, - postContent, + contentBody, feedId, - images = [], + contentsUrl = [], }: PostBodyProps) => { const navigate = useNavigate(); - const hasImage = images.length > 0; + const hasImage = contentsUrl.length > 0; const handlePostClick = (feedId: number) => { // if (!isClickable) return; @@ -65,10 +65,10 @@ const PostBody = ({ handlePostClick(feedId)}> -
{postContent}
+
{contentBody}
{hasImage && (
- {images.map((src, i) => ( + {contentsUrl.map((src: string, i: number) => ( ))}
diff --git a/src/components/common/Post/PostFooter.tsx b/src/components/common/Post/PostFooter.tsx index 9dff82c9..03b794bb 100644 --- a/src/components/common/Post/PostFooter.tsx +++ b/src/components/common/Post/PostFooter.tsx @@ -43,27 +43,31 @@ const Container = styled.div<{ isDetail: boolean }>` `; interface PostFooterProps { - initialLikeCount: number; + likeCount: number; commentCount: number; feedId: number; isMyFeed: boolean; + isSaved?: boolean; + isLiked?: boolean; isPublic?: boolean; isDetail?: boolean; } const PostFooter = ({ - initialLikeCount, + likeCount: initialLikeCount, commentCount, feedId, isMyFeed, - isPublic, + isSaved = false, + isLiked = false, + isPublic = true, isDetail = false, }: PostFooterProps) => { const navigate = useNavigate(); - const [liked, setLiked] = useState(false); + const [liked, setLiked] = useState(isLiked); const [likeCount, setLikeCount] = useState(initialLikeCount); - const [saved, setSaved] = useState(false); + const [saved, setSaved] = useState(isSaved); const handleLike = () => { setLiked(!liked); diff --git a/src/components/common/Post/PostHeader.tsx b/src/components/common/Post/PostHeader.tsx index 4145c9f9..bf854e72 100644 --- a/src/components/common/Post/PostHeader.tsx +++ b/src/components/common/Post/PostHeader.tsx @@ -1,41 +1,44 @@ import { useNavigate } from 'react-router-dom'; import styled from '@emotion/styled'; interface PostHeaderProps { - profileImgUrl: string; - userName: string; - userTitle: string; - titleColor: string; - createdAt: string; - userId: number; + creatorProfileImageUrl?: string; + creatorNickname?: string; + alias?: string; + titleColor?: string; + postDate: string; + creatorId?: number; type?: 'post' | 'reply'; } const PostHeader = ({ - profileImgUrl, - userName, - userTitle, - titleColor, - createdAt, - userId, + creatorProfileImageUrl, + creatorNickname, + alias, + titleColor = '#FFFFFF', // 기본값 설정 + postDate, + creatorId, type = 'post', }: PostHeaderProps) => { const navigate = useNavigate(); const handleClick = () => { - navigate(`/otherfeed/${userId}`); + if (creatorId) { + navigate(`/otherfeed/${creatorId}`); + } }; + return (
- 칭호 이미지 + 칭호 이미지
-
{userName}
+
{creatorNickname}
- {userTitle} + {alias}
-
{createdAt}
+
{postDate}
); }; diff --git a/src/components/common/Post/Reply.tsx b/src/components/common/Post/Reply.tsx index 88e590e0..54dae1ec 100644 --- a/src/components/common/Post/Reply.tsx +++ b/src/components/common/Post/Reply.tsx @@ -36,12 +36,12 @@ const Reply = ({ return ( diff --git a/src/components/common/Post/SubReply.tsx b/src/components/common/Post/SubReply.tsx index 93098386..f95c3fd2 100644 --- a/src/components/common/Post/SubReply.tsx +++ b/src/components/common/Post/SubReply.tsx @@ -41,12 +41,12 @@ const SubReply = ({ diff --git a/src/components/feed/FeedDetailPostBody.tsx b/src/components/feed/FeedDetailPostBody.tsx index faff1c93..3755556e 100644 --- a/src/components/feed/FeedDetailPostBody.tsx +++ b/src/components/feed/FeedDetailPostBody.tsx @@ -72,18 +72,22 @@ const TagContainer = styled.div` } `; +interface FeedDetailPostBodyProps extends PostBodyProps { + tags?: string[]; // API에 없지만 컴포넌트에서 사용 +} + const FeedDetailPostBody = ({ bookTitle, isbn, bookAuthor, - postContent, - images = [], + contentBody, + contentsUrl = [], tags = [], -}: PostBodyProps) => { +}: FeedDetailPostBodyProps) => { const [isImageViewerOpen, setIsImageViewerOpen] = useState(false); const [selectedImageIndex, setSelectedImageIndex] = useState(0); - const hasImage = images.length > 0; + const hasImage = contentsUrl.length > 0; const hasTag = tags.length > 0; const handleImageClick = (index: number) => { @@ -99,10 +103,10 @@ const FeedDetailPostBody = ({ -
{postContent}
+
{contentBody}
{hasImage && (
- {images.map((src, i) => ( + {contentsUrl.map((src: string, i: number) => ( {`이미지 handleImageClick(i)} /> ))}
@@ -122,7 +126,7 @@ const FeedDetailPostBody = ({
{isImageViewerOpen && ( { const navigate = useNavigate(); const [activeTab, setActiveTab] = useState(tabs[0]); + // 전체 피드 상태 + const [totalFeedPosts, setTotalFeedPosts] = useState([]); + const [totalLoading, setTotalLoading] = useState(false); + const [totalNextCursor, setTotalNextCursor] = useState(''); + const [totalIsLast, setTotalIsLast] = useState(false); + + // 내 피드 상태 + const [myFeedPosts, setMyFeedPosts] = useState([]); + const [myLoading, setMyLoading] = useState(false); + const [myNextCursor, setMyNextCursor] = useState(''); + const [myIsLast, setMyIsLast] = useState(false); + const handleSearchButton = () => { navigate('/feed/search'); }; + // 전체 피드 로드 함수 + const loadTotalFeeds = async (cursor?: string) => { + try { + setTotalLoading(true); + const response = await getTotalFeeds(cursor ? { cursor } : undefined); + + if (cursor) { + // 다음 페이지 데이터 추가 + setTotalFeedPosts(prev => [...prev, ...response.data.feedList]); + } else { + // 첫 페이지 데이터 설정 + setTotalFeedPosts(response.data.feedList); + } + + setTotalNextCursor(response.data.nextCursor); + setTotalIsLast(response.data.isLast); + } catch (error) { + console.error('전체 피드 로드 실패:', error); + // 에러 시 mockPosts 사용 (fallback) + setTotalFeedPosts(mockPosts); + } finally { + setTotalLoading(false); + } + }; + + // 내 피드 로드 함수 + const loadMyFeeds = async (cursor?: string) => { + try { + setMyLoading(true); + const response = await getMyFeeds(cursor ? { cursor } : undefined); + + if (cursor) { + // 다음 페이지 데이터 추가 + setMyFeedPosts(prev => [...prev, ...response.data.feedList]); + } else { + // 첫 페이지 데이터 설정 + setMyFeedPosts(response.data.feedList); + } + + setMyNextCursor(response.data.nextCursor); + setMyIsLast(response.data.isLast); + } catch (error) { + console.error('내 피드 로드 실패:', error); + // 에러 시 mockPosts 사용 (fallback) + setMyFeedPosts(mockPosts); + } finally { + setMyLoading(false); + } + }; + + // 다음 페이지 로드 (무한 스크롤용) + const loadMoreFeeds = useCallback(() => { + if (activeTab === '피드') { + if (!totalIsLast && !totalLoading && totalNextCursor) { + loadTotalFeeds(totalNextCursor); + } + } else { + if (!myIsLast && !myLoading && myNextCursor) { + loadMyFeeds(myNextCursor); + } + } + }, [activeTab, totalIsLast, totalLoading, totalNextCursor, myIsLast, myLoading, myNextCursor]); + + // 무한스크롤 구현 + useEffect(() => { + const handleScroll = () => { + const isLoading = activeTab === '피드' ? totalLoading : myLoading; + const isLastPage = activeTab === '피드' ? totalIsLast : myIsLast; + + // 로딩 중이거나 마지막 페이지면 return + if (isLoading || isLastPage) return; + + // 스크롤이 하단 근처에 도달했는지 확인 (하단에서 200px 이전) + const scrollTop = window.pageYOffset || document.documentElement.scrollTop; + const windowHeight = window.innerHeight; + const documentHeight = document.documentElement.scrollHeight; + + if (scrollTop + windowHeight >= documentHeight - 200) { + loadMoreFeeds(); + } + }; + + // 스크롤 이벤트 리스너 추가 + window.addEventListener('scroll', handleScroll); + + // 컴포넌트 언마운트 시 이벤트 리스너 제거 + return () => { + window.removeEventListener('scroll', handleScroll); + }; + }, [activeTab, totalLoading, myLoading, totalIsLast, myIsLast, loadMoreFeeds]); + useEffect(() => { window.scrollTo(0, 0); }, [activeTab]); + useEffect(() => { + // 탭별로 API 호출 + if (activeTab === '피드') { + loadTotalFeeds(); + } else if (activeTab === '내 피드') { + loadMyFeeds(); + } + }, [activeTab]); + return ( {activeTab === '피드' ? ( - + <> + + ) : ( - + <> + + )} diff --git a/src/types/post.ts b/src/types/post.ts index 6226e700..8ddebd1b 100644 --- a/src/types/post.ts +++ b/src/types/post.ts @@ -1,19 +1,19 @@ export interface PostData { - profileImgUrl: string; - userName: string; - userId: number; - userTitle: string; - titleColor: string; - createdAt: string; - bookTitle: string; + feedId: number; + creatorId?: number; + creatorNickname?: string; + creatorProfileImageUrl?: string; + alias?: string; + postDate: string; isbn: number; + bookTitle: string; bookAuthor: string; - postContent: string; - feedId: number; - initialLikeCount: number; + contentBody: string; + contentsUrl: string[]; + likeCount: number; commentCount: number; - images?: string[]; - tags?: string[]; + isSaved?: boolean; + isLiked?: boolean; isPublic?: boolean; } @@ -30,7 +30,7 @@ export interface FeedPostProps extends PostData { export type PostBodyProps = Pick< PostData, - 'bookTitle' | 'bookAuthor' | 'postContent' | 'feedId' | 'images' | 'tags' | 'isbn' + 'bookTitle' | 'bookAuthor' | 'contentBody' | 'feedId' | 'contentsUrl' | 'isbn' >; // 대댓글(SubReply) From a7fdea24746ae6c53d0824f2a5e743d309e11ca7 Mon Sep 17 00:00:00 2001 From: heeyongKim <166043860+heeeeyong@users.noreply.github.com> Date: Sun, 3 Aug 2025 16:02:52 +0900 Subject: [PATCH 3/8] =?UTF-8?q?fix:=20=EB=A6=AC=ED=94=84=EB=A0=88=EC=8B=9C?= =?UTF-8?q?=20=ED=86=A0=ED=81=B0=20=EA=B4=80=EB=A0=A8=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EC=A3=BC=EC=84=9D=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/api/index.ts b/src/api/index.ts index 18d14021..a2bc9589 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -8,11 +8,11 @@ const ACCESS_TOKEN = export const TokenManager = { setAccessToken: (token: string) => localStorage.setItem('accessToken', token), getAccessToken: (): string | null => localStorage.getItem('accessToken'), - setRefreshToken: (token: string) => localStorage.setItem('refreshToken', token), - getRefreshToken: (): string | null => localStorage.getItem('refreshToken'), + // setRefreshToken: (token: string) => localStorage.setItem('refreshToken', token), + // getRefreshToken: (): string | null => localStorage.getItem('refreshToken'), clearTokens: () => { localStorage.removeItem('accessToken'); - localStorage.removeItem('refreshToken'); + // localStorage.removeItem('refreshToken'); }, hasValidToken: (): boolean => !!localStorage.getItem('accessToken'), }; From d13e70ea5fe10dc26d85c78cece25ee16930e7b9 Mon Sep 17 00:00:00 2001 From: heeyongKim <166043860+heeeeyong@users.noreply.github.com> Date: Sun, 3 Aug 2025 16:06:14 +0900 Subject: [PATCH 4/8] =?UTF-8?q?fix:=20getMyFeed=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/feeds/getMyFeed.ts | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/api/feeds/getMyFeed.ts b/src/api/feeds/getMyFeed.ts index 325eb978..f0ed5259 100644 --- a/src/api/feeds/getMyFeed.ts +++ b/src/api/feeds/getMyFeed.ts @@ -1,22 +1,9 @@ import { apiClient } from '../index'; import type { PostData } from '@/types/post'; -export interface MyFeedItem extends PostData { - feedId: number; - postDate: string; - isbn: number; - bookTitle: string; - bookAuthor: string; - contentBody: string; - contentsUrl: string[]; - likeCount: number; - commentCount: number; - isPublic: boolean; -} - // API 응답 데이터 타입 export interface MyFeedData { - feedList: MyFeedItem[]; + feedList: PostData[]; nextCursor: string; isLast: boolean; } From e65b876f5544867069b9a813a61f24e5df3ab9b4 Mon Sep 17 00:00:00 2001 From: heeyongKim <166043860+heeeeyong@users.noreply.github.com> Date: Sun, 3 Aug 2025 16:14:05 +0900 Subject: [PATCH 5/8] =?UTF-8?q?fix:=20searchedbook=20=EB=AA=A9=EC=97=85=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mocks/searchBook.mock.ts | 43 ++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/src/mocks/searchBook.mock.ts b/src/mocks/searchBook.mock.ts index b1539f02..b35cfec1 100644 --- a/src/mocks/searchBook.mock.ts +++ b/src/mocks/searchBook.mock.ts @@ -36,40 +36,41 @@ export const mockSearchBook = { ], posts: [ { - userId: 1, + feedId: 80, + creatorId: 1, + creatorNickname: 'userName', + creatorProfileImageUrl: 'https://placehold.co/24x24', + alias: 'userTitle', + postDate: '12시간 전', isbn: 3, - profileImgUrl: 'https://placehold.co/24x24', - userName: 'userName', - userTitle: 'userTitle', - titleColor: '#FF8BAC', - createdAt: '12시간 전', bookTitle: '제목입니다', bookAuthor: '작가입니다', - postContent: '내용입니다…', - postId: '55', - initialLikeCount: 125, + contentBody: '내용입니다…', + contentsUrl: ['https://placehold.co/100x100', 'https://placehold.co/100x100'], + likeCount: 125, commentCount: 125, - images: ['https://placehold.co/100x100', 'https://placehold.co/100x100'], + isSaved: false, + isLiked: true, isPublic: true, - feedId: 80, }, { - userId: 2, + feedId: 90, + creatorId: 2, + creatorNickname: 'userName', + creatorProfileImageUrl: 'https://placehold.co/24x24', + alias: 'userTitle', + postDate: '12시간 전', isbn: 3, - profileImgUrl: 'https://placehold.co/24x24', - userName: 'userName', - userTitle: 'userTitle', - titleColor: '#FF8BAC', - createdAt: '12시간 전', bookTitle: '제목입니다제목입니다제목입니다', bookAuthor: '작가입니다', - postContent: + contentBody: '내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다', - postId: '56', - initialLikeCount: 125, + contentsUrl: [], + likeCount: 125, commentCount: 125, + isSaved: true, + isLiked: false, isPublic: false, - feedId: 90, }, ], }; From 13e2742a315f9003a00e11363029535cb4a31c3a Mon Sep 17 00:00:00 2001 From: heeyongKim <166043860+heeeeyong@users.noreply.github.com> Date: Sun, 3 Aug 2025 16:21:57 +0900 Subject: [PATCH 6/8] =?UTF-8?q?fix:=20isbn=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/feed/BookInfoCard.tsx | 2 +- src/data/bookData.ts | 6 +++--- src/data/postData.ts | 8 ++++---- src/mocks/searchBook.mock.ts | 4 ++-- src/pages/mypage/SavePage.tsx | 4 ++-- src/types/book.ts | 2 +- src/types/post.ts | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/components/feed/BookInfoCard.tsx b/src/components/feed/BookInfoCard.tsx index 8c850c92..59e10fcc 100644 --- a/src/components/feed/BookInfoCard.tsx +++ b/src/components/feed/BookInfoCard.tsx @@ -49,7 +49,7 @@ const BookContainer = styled.div` interface BookInfoCardProps { bookTitle: string; bookAuthor: string; - isbn: number; + isbn: string; } const BookInfoCard = ({ bookTitle, bookAuthor, isbn }: BookInfoCardProps) => { diff --git a/src/data/bookData.ts b/src/data/bookData.ts index 6c9e61c1..d62214fe 100644 --- a/src/data/bookData.ts +++ b/src/data/bookData.ts @@ -2,21 +2,21 @@ import type { Book } from '@/types/book'; export const mockBooks: Book[] = [ { - isbn: 11234131, + isbn: '1123413112345', title: '채식주의자', author: '한강', publisher: '창비', coverUrl: 'https://image.yes24.com/goods/17122707/XL', }, { - isbn: 21234123, + isbn: '2123412355152', title: '채소 마스터 클래스', author: '백지혜', publisher: '세미콜론', coverUrl: 'https://image.yes24.com/goods/109378551/XL', }, { - isbn: 31324122, + isbn: '3132412299090', title: '채소 식탁', author: '김경민', publisher: '래디시', diff --git a/src/data/postData.ts b/src/data/postData.ts index c8e43fd3..108a5101 100644 --- a/src/data/postData.ts +++ b/src/data/postData.ts @@ -10,7 +10,7 @@ export const mockPosts: PostData[] = [ creatorProfileImageUrl: 'https://placehold.co/24x24', alias: 'userTitle', postDate: '12시간 전', - isbn: 111155544, + isbn: '1111555441234', bookTitle: '제목입니다', bookAuthor: '작가입니다', contentBody: '내용입니다…', @@ -28,7 +28,7 @@ export const mockPosts: PostData[] = [ creatorProfileImageUrl: 'https://placehold.co/24x24', alias: 'userTitle', postDate: '12시간 전', - isbn: 111152544, + isbn: '1111525445613', bookTitle: '제목입니다제목입니다제목입니다', bookAuthor: '작가입니다', contentBody: @@ -47,7 +47,7 @@ export const mockPosts: PostData[] = [ creatorProfileImageUrl: 'https://placehold.co/24x24', alias: 'userTitle', postDate: '12시간 전', - isbn: 111152544, + isbn: '111152544099', bookTitle: '제목입니다제목입니다제목입니다', bookAuthor: '작가입니다', contentBody: @@ -69,7 +69,7 @@ export const mockFeedPost: FeedPostProps = { creatorProfileImageUrl: 'https://placehold.co/54x54', alias: '문학가', postDate: '2025.01.12', - isbn: 111155546, + isbn: '111155541766', bookTitle: '채식주의자', bookAuthor: '한강', contentBody: diff --git a/src/mocks/searchBook.mock.ts b/src/mocks/searchBook.mock.ts index b35cfec1..8e202754 100644 --- a/src/mocks/searchBook.mock.ts +++ b/src/mocks/searchBook.mock.ts @@ -42,7 +42,7 @@ export const mockSearchBook = { creatorProfileImageUrl: 'https://placehold.co/24x24', alias: 'userTitle', postDate: '12시간 전', - isbn: 3, + isbn: '3', bookTitle: '제목입니다', bookAuthor: '작가입니다', contentBody: '내용입니다…', @@ -60,7 +60,7 @@ export const mockSearchBook = { creatorProfileImageUrl: 'https://placehold.co/24x24', alias: 'userTitle', postDate: '12시간 전', - isbn: 3, + isbn: '3', bookTitle: '제목입니다제목입니다제목입니다', bookAuthor: '작가입니다', contentBody: diff --git a/src/pages/mypage/SavePage.tsx b/src/pages/mypage/SavePage.tsx index ef80ff3c..f7ead048 100644 --- a/src/pages/mypage/SavePage.tsx +++ b/src/pages/mypage/SavePage.tsx @@ -19,7 +19,7 @@ const SavePage = () => { const navigate = useNavigate(); const [activeTab, setActiveTab] = useState(tabs[0]); - const [savedBooks, setSavedBooks] = useState<{ [isbn: number]: boolean }>({}); + const [savedBooks, setSavedBooks] = useState<{ [isbn: string]: boolean }>({}); const handleBack = () => { navigate('/mypage'); @@ -29,7 +29,7 @@ const SavePage = () => { window.scrollTo(0, 0); }, [activeTab]); - const handleSaveToggle = (isbn: number) => { + const handleSaveToggle = (isbn: string) => { setSavedBooks(prev => ({ ...prev, [isbn]: !prev[isbn], diff --git a/src/types/book.ts b/src/types/book.ts index 99031060..0974cc82 100644 --- a/src/types/book.ts +++ b/src/types/book.ts @@ -1,5 +1,5 @@ export interface Book { - isbn: number; + isbn: string; title: string; author: string; coverUrl: string; diff --git a/src/types/post.ts b/src/types/post.ts index 8ddebd1b..178ce0c5 100644 --- a/src/types/post.ts +++ b/src/types/post.ts @@ -5,7 +5,7 @@ export interface PostData { creatorProfileImageUrl?: string; alias?: string; postDate: string; - isbn: number; + isbn: string; bookTitle: string; bookAuthor: string; contentBody: string; From 8712bb4732efe2a482aebf72ccd4ef7cea5e72e7 Mon Sep 17 00:00:00 2001 From: heeyongKim <166043860+heeeeyong@users.noreply.github.com> Date: Sun, 3 Aug 2025 22:20:46 +0900 Subject: [PATCH 7/8] =?UTF-8?q?design:=20=EB=B0=98=EC=9D=91=ED=98=95=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/feed/MyFeed.tsx | 1 + src/components/feed/TotalFeed.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/components/feed/MyFeed.tsx b/src/components/feed/MyFeed.tsx index 7f941a73..0e487cc4 100644 --- a/src/components/feed/MyFeed.tsx +++ b/src/components/feed/MyFeed.tsx @@ -6,6 +6,7 @@ import { colors, typography } from '@/styles/global/global'; import TotalBar from './TotalBar'; const Container = styled.div` + height: 100vh; padding-top: 136px; padding-bottom: 155px; background-color: var(--color-black-main); diff --git a/src/components/feed/TotalFeed.tsx b/src/components/feed/TotalFeed.tsx index 1740b4d1..8fb160d5 100644 --- a/src/components/feed/TotalFeed.tsx +++ b/src/components/feed/TotalFeed.tsx @@ -5,6 +5,7 @@ import type { FeedListProps } from '../../types/post'; import { colors, typography } from '@/styles/global/global'; const Container = styled.div` + height: 100vh; padding-top: 136px; padding-bottom: 155px; background-color: var(--color-black-main); From 262da2b43b46135007cb02b5f7982be0b2564445 Mon Sep 17 00:00:00 2001 From: heeyongKim <166043860+heeeeyong@users.noreply.github.com> Date: Sun, 3 Aug 2025 23:46:47 +0900 Subject: [PATCH 8/8] =?UTF-8?q?design:=20=EB=B0=98=EC=9D=91=ED=98=95=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/feed/MyFeed.tsx | 2 +- src/components/feed/TotalFeed.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/feed/MyFeed.tsx b/src/components/feed/MyFeed.tsx index 0e487cc4..036f82cf 100644 --- a/src/components/feed/MyFeed.tsx +++ b/src/components/feed/MyFeed.tsx @@ -14,7 +14,7 @@ const Container = styled.div` const EmptyState = styled.div` display: flex; - height: 473px; + height: 100%; padding: 32px 0 20px 0; justify-content: center; align-items: center; diff --git a/src/components/feed/TotalFeed.tsx b/src/components/feed/TotalFeed.tsx index 8fb160d5..d5401afc 100644 --- a/src/components/feed/TotalFeed.tsx +++ b/src/components/feed/TotalFeed.tsx @@ -13,7 +13,7 @@ const Container = styled.div` const EmptyState = styled.div` display: flex; - height: 473px; + height: 100%; padding: 32px 0 20px 0; justify-content: center; align-items: center;