Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
84b1fe4
feat(mypage): add leave group dropdown menu to MyMeetingCard
shinwokkang Mar 1, 2026
c99ade1
feat(mypage): integrate leave club API in MyMeetingCard
shinwokkang Mar 1, 2026
75897ff
feat(mypage): use custom ConfirmModal for leave group action
shinwokkang Mar 1, 2026
e452267
refactor(mypage): apply review feedback for leave group feature
shinwokkang Mar 1, 2026
9adfdde
Merge pull request #160 from checkmo2025/feat/159-mypage-ui-api
shinwokkang Mar 2, 2026
84ab6fc
refactor(login): improve login modal usability
shinwokkang Mar 2, 2026
d44db89
refactor(login): re-apply usability improvements after user edit
shinwokkang Mar 2, 2026
def3c57
refator: rollback
shinwokkang Mar 2, 2026
78afab4
refactor(login): improve usability with eye icons and outside click fix
shinwokkang Mar 2, 2026
0b0869d
feat: implement homepage infinite scroll, nested comments api, and sh…
shinwokkang Mar 2, 2026
a5e53be
fix: adjust homepage UI layout and fix nested replies rendering bug
shinwokkang Mar 2, 2026
1c4159e
fix: adjust story homepage block and reply input visual indication
shinwokkang Mar 2, 2026
9447cab
fix: book story card UI blurred cover bg and nested reply depth 2 limit
shinwokkang Mar 2, 2026
4e74ac9
feat: implement club book stories infinite scroll on stories page
shinwokkang Mar 2, 2026
b1c24e3
feat: implement book like api and integrate into components
shinwokkang Mar 2, 2026
63ffd0b
refactor: apply optimistic UI and throttle to book like api
shinwokkang Mar 2, 2026
be6ec53
fix: replace onSettled invalidation with onSuccess cache update for b…
shinwokkang Mar 2, 2026
1205cc9
feat: implement my liked books api and infinite scroll in mypage
shinwokkang Mar 2, 2026
9b71cd8
refactor: vercel 에러 확인
shinwokkang Mar 5, 2026
f1106a5
refactor: 리뷰 내용 수정
shinwokkang Mar 5, 2026
147b7a7
refactor: 답글
shinwokkang Mar 5, 2026
2be32f6
Merge pull request #166 from checkmo2025/refactor/162-Feedback
shinwokkang Mar 5, 2026
52551f5
feat: 팔로우 팔로잉 수 구현 및 소식 문의하기 URL 구현
shinwokkang Mar 5, 2026
b992537
chore: 리뷰 내용 수정
shinwokkang Mar 5, 2026
f954dd3
Merge pull request #167 from checkmo2025/refactor/165-mypage-subscrib…
shinwokkang Mar 5, 2026
a5722ef
refactor: 다른 사람 프로필 조회 구독자, 구독중 수 추가
shinwokkang Mar 5, 2026
aa00906
다른 사람 구독중 구독자 UI 및 API구현
shinwokkang Mar 5, 2026
6fb373c
feat: 상대방 서재 조회 및 낙관적 업데이트
shinwokkang Mar 5, 2026
624c857
refactor: 리뷰 내용 수정 및 서재 쿼리 수정
shinwokkang Mar 5, 2026
c2a8809
Merge pull request #169 from checkmo2025/feat/168-other-profile-subsc…
shinwokkang Mar 5, 2026
4a7727d
feat: 홈화면 소식 API 구현
shinwokkang Mar 5, 2026
b9ed33d
feat: 소식 페이지 소식 조회 구현
shinwokkang Mar 5, 2026
21e22ac
feat: 검색바 내려올 때 Floating 안보이도록 구현
shinwokkang Mar 5, 2026
d2555b5
refactor: 홈화면에서 소식 레이아웃 구조 변경
shinwokkang Mar 5, 2026
1071ddb
feat: 책 이야기 카드 UX 개선
shinwokkang Mar 5, 2026
779aea4
feat: 알라딘 랭킹 더보러 가기 Link 구현
shinwokkang Mar 5, 2026
ed66ffe
feat: 내 서재 쿼리 공유
shinwokkang Mar 5, 2026
b408956
refactor: 리뷰 내용 반영
shinwokkang Mar 5, 2026
88224df
feat: JSON형식 간략화 추가
shinwokkang Mar 5, 2026
be60ee2
feat: 설정 닉네임 변경 불가능 하도록 수정
shinwokkang Mar 5, 2026
153cab4
chore: vercel 에러 해결
shinwokkang Mar 5, 2026
ba79578
Merge pull request #171 from checkmo2025/feat/170-news-api
shinwokkang Mar 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions build.sh

This file was deleted.

8 changes: 4 additions & 4 deletions src/app/(main)/books/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import SearchBookResult from "@/components/base-ui/Search/search_bookresult";
import { DUMMY_STORIES } from "@/data/dummyStories";
import BookStoryCardLarge from "@/components/base-ui/BookStory/bookstory_card_large";
import { useBookDetailQuery } from "@/hooks/queries/useBookQueries";
import { useToggleBookLikeMutation } from "@/hooks/mutations/useBookMutations";

export default function BookDetailPage() {
const params = useParams();
const router = useRouter();
const isbn = params.id as string;
const [liked, setLiked] = useState(false);

const { data: bookData, isLoading, isError } = useBookDetailQuery(isbn);
const { mutate: toggleLike } = useToggleBookLikeMutation();

// 관련된 책 이야기들 (더미 데이터에서 필터링)
const relatedStories = useMemo(() => {
Expand Down Expand Up @@ -51,8 +51,8 @@ export default function BookDetailPage() {
title={bookData.title}
author={bookData.author}
detail={bookData.description}
liked={liked}
onLikeChange={setLiked}
liked={bookData.likedByMe || false}
onLikeChange={() => toggleLike(isbn)}
onPencilClick={() => {
router.push(`/stories/new?isbn=${isbn}`);
}}
Expand Down
121 changes: 69 additions & 52 deletions src/app/(main)/news/page.tsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,52 @@
"use client";

import { useEffect, useMemo } from "react";
import Image from "next/image";
import NewsList from "@/components/base-ui/News/news_list";
import TodayRecommendedBooks from "@/components/base-ui/News/today_recommended_books";
import FloatingFab from "@/components/base-ui/Float";
import { useRecommendedBooksQuery } from "@/hooks/queries/useBookQueries";
import { useMemo } from "react";

const DUMMY_NEWS = [
{
id: 1,
imageUrl: "/news_sample.svg",
title: "책 읽는 한강공원",
content:
"소식내용소식내용소식내용소식내용소식내용소식내용소식내용소식내용소식내용소식내용",
date: "2025-10-09",
},
{
id: 2,
imageUrl: "/news_sample.svg",
title: "책 읽는 한강공원",
content:
"소식내용소식내용소식내용소식내용소식내용소식내용소식내용소식내용소식내용소식내용",
date: "2025-10-09",
},
{
id: 3,
imageUrl: "/news_sample.svg",
title: "책 읽는 한강공원",
content:
"소식내용소식내용소식내용소식내용소식내용소식내용소식내용소식내용소식내용소식내용",
date: "2025-10-09",
},
{
id: 4,
imageUrl: "/news_sample.svg",
title: "책 읽는 한강공원",
content:
"소식내용소식내용소식내용소식내용소식내용소식내용소식내용소식내용소식내용소식내용",
date: "2025-10-09",
},
];
import { useInfiniteNewsQuery } from "@/hooks/queries/useNewsQueries";
import { useInView } from "react-intersection-observer";
import { EXTERNAL_LINKS } from "@/constants/links";
import { Book } from "@/types/book";

export default function NewsPage() {
const { data: recommendedData, isLoading: isLoadingRecommended } = useRecommendedBooksQuery();
const {
data: newsData,
isLoading: isLoadingNews,
fetchNextPage,
hasNextPage,
isFetchingNextPage
} = useInfiniteNewsQuery();

const { ref, inView } = useInView();

useEffect(() => {
if (inView && hasNextPage && !isFetchingNextPage) {
fetchNextPage();
}
}, [inView, hasNextPage, isFetchingNextPage, fetchNextPage]);

const recommendedBooks = useMemo(() => {
return (recommendedData?.detailInfoList || []).map((book) => ({
return (recommendedData?.detailInfoList || []).map((book: Book) => ({
id: book.isbn,
imgUrl: book.imgUrl,
title: book.title,
author: book.author,
likedByMe: book.likedByMe,
}));
}, [recommendedData]);

const newsList = newsData?.pages.flatMap((page) => page.basicInfoList) || [];

const isValidSrc = (src: string) => {
return src && src !== "string" && (src.startsWith("/") || src.startsWith("http://") || src.startsWith("https://"));
};
Comment on lines +44 to +46
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

isValidSrc 함수 내의 src !== "string" 조건은 매우 이례적이며, API가 유효하지 않은 데이터를 반환할 수 있음을 시사합니다. 이는 잠재적으로 백엔드의 데이터 품질 문제일 수 있습니다. 코드의 의도를 명확히 하고 향후 유지보수를 위해 이 검사가 왜 필요한지에 대한 주석을 추가하는 것이 좋습니다.

Suggested change
const isValidSrc = (src: string) => {
return src && src !== "string" && (src.startsWith("/") || src.startsWith("http://") || src.startsWith("https://"));
};
const isValidSrc = (src: string) => {
// TODO: API에서 이미지 URL로 "string"이라는 문자열이 내려오는 경우가 있어 방어 코드 추가. 추후 백엔드 수정 필요.
return src && src !== "string" && (src.startsWith("/") || src.startsWith("http://") || src.startsWith("https://"));
};


return (
<div className="mx-auto w-full max-w-[1400px] px-4 overflow-x-hidden scrollbar-hide">
<div className="mx-auto w-full max-w-[1400px] px-4 overflow-x-hidden scrollbar-hide pb-20">
<div className="flex justify-center items-center mt-7 mb-3 t:mb-6">
<div
className="relative w-full h-[297px] t:h-[468px]"
Expand All @@ -72,34 +63,60 @@ export default function NewsPage() {
</div>
</div>

{/* 오늘의 추천 */}
{/* 오늘의 추천 (모바일) */}
{!isLoadingRecommended && recommendedBooks.length > 0 && (
<TodayRecommendedBooks books={recommendedBooks} className="d:hidden" />
)}

{/* 뉴스 리스트 */}
<div className="flex flex-col gap-4 items-center w-full max-w-[1040px] mx-auto">
{DUMMY_NEWS.map((news) => (
<NewsList
key={news.id}
id={news.id}
imageUrl={news.imageUrl}
title={news.title}
content={news.content}
date={news.date}
/>
))}
</div>
{/* 뉴스 리스트 섹션 */}
<section className="mt-8 w-full max-w-[1040px] mx-auto">
<h2 className="mb-4 text-xl font-bold text-Zinc-800">새로운 소식</h2>

{isLoadingNews ? (
<div className="flex items-center justify-center py-20">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary-2" />
</div>
) : newsList.length === 0 ? (
<div className="flex flex-col items-center justify-center py-20 bg-gray-50 rounded-lg">
<p className="text-gray-400">등록된 소식이 없습니다.</p>
</div>
) : (
<div
className="flex flex-col gap-4 overflow-y-auto scrollbar-hide pr-2"
style={{ maxHeight: "calc(100vh - 400px)", minHeight: "400px" }}
>
{newsList.map((news) => (
<NewsList
key={news.newsId}
id={news.newsId}
imageUrl={isValidSrc(news.thumbnailUrl) ? news.thumbnailUrl : "/news_sample.svg"}
title={news.title}
content={news.description}
date={news.publishStartAt}
/>
))}

{/* 무한 스크롤 트리거 */}
<div ref={ref} className="h-10 flex items-center justify-center">
{isFetchingNextPage && (
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-primary-1" />
)}
</div>
</div>
)}
</section>

<div className="w-full my-8 border-b-4 border-Gray-1"></div>

{/* 오늘의 추천 (데스크톱) */}
{!isLoadingRecommended && recommendedBooks.length > 0 && (
<TodayRecommendedBooks books={recommendedBooks} className="hidden d:flex" />
)}

<FloatingFab
iconSrc="/icons_calling.svg"
iconAlt="문의하기"
onClick={() => window.open(EXTERNAL_LINKS.INQUIRY_FORM_URL, "_blank")}
/>
</div>
);
Expand Down
Loading