From 2d5e81a53baa4165b5f4d883a14b8d60fddc1626 Mon Sep 17 00:00:00 2001 From: cloud0406 Date: Thu, 19 Dec 2024 10:07:57 +0900 Subject: [PATCH 001/209] =?UTF-8?q?=E2=9C=A8[Feat]=20tanstack=20query=20?= =?UTF-8?q?=EA=B8=B0=EB=B3=B8=20=EC=84=A4=EC=A0=95=EA=B0=92=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/utils/reactQueryProvider.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/utils/reactQueryProvider.tsx b/src/lib/utils/reactQueryProvider.tsx index 79bfbc96..05c45b1f 100644 --- a/src/lib/utils/reactQueryProvider.tsx +++ b/src/lib/utils/reactQueryProvider.tsx @@ -10,9 +10,11 @@ export default function ReactQueryProviders({ new QueryClient({ defaultOptions: { queries: { - refetchOnWindowFocus: false, // 윈도우가 다시 포커스되었을때 데이터를 refetch - refetchOnMount: false, // 데이터가 stale 상태이면 컴포넌트가 마운트될 때 refetch - retry: 1, // API 요청 실패시 재시도 하는 옵션 (설정값 만큼 재시도) + refetchOnWindowFocus: false, // 윈도우 다시 포커스되었을때 데이터 refetch X + refetchOnMount: false, // 컴포넌트 마운트될 때 데이터 refetch X + retry: 0, // API 요청 실패시 재시도 X + refetchOnReconnect: false, // 네트워크가 재연결될 때 데이터를 refetch X + retryOnMount: false, // 컴포넌트가 마운트될 때 실패한 쿼리를 재시도 X }, }, }), From d8ced7eb419ff31e3527ff49ade8c25b11fc9ce0 Mon Sep 17 00:00:00 2001 From: cloud0406 Date: Thu, 19 Dec 2024 10:17:19 +0900 Subject: [PATCH 002/209] =?UTF-8?q?=E2=9C=A8[Feat]=20staletime,=20gctime?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/utils/reactQueryProvider.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/utils/reactQueryProvider.tsx b/src/lib/utils/reactQueryProvider.tsx index 05c45b1f..226b947e 100644 --- a/src/lib/utils/reactQueryProvider.tsx +++ b/src/lib/utils/reactQueryProvider.tsx @@ -15,6 +15,8 @@ export default function ReactQueryProviders({ retry: 0, // API 요청 실패시 재시도 X refetchOnReconnect: false, // 네트워크가 재연결될 때 데이터를 refetch X retryOnMount: false, // 컴포넌트가 마운트될 때 실패한 쿼리를 재시도 X + staleTime: 1000 * 60 * 5, + gcTime: 1000 * 60 * 10, }, }, }), From a1fc17dd1c3c0bde143d89affa16a7daa2f17d1e Mon Sep 17 00:00:00 2001 From: cloud0406 Date: Thu, 19 Dec 2024 10:19:09 +0900 Subject: [PATCH 003/209] =?UTF-8?q?=F0=9F=93=A6[Chore]=20=EC=BF=BC?= =?UTF-8?q?=EB=A6=AC=ED=82=A4=20=ED=8C=A9=ED=86=A0=EB=A6=AC=20=EC=84=A4?= =?UTF-8?q?=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 14 ++++++++++++++ package.json | 1 + 2 files changed, 15 insertions(+) diff --git a/package-lock.json b/package-lock.json index 36ce19e7..73ad75be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@headlessui/react": "^2.2.0", "@hookform/resolvers": "^3.9.1", + "@lukemorales/query-key-factory": "^1.3.4", "@tanstack/react-query": "^5.61.3", "@tanstack/react-query-devtools": "^5.61.3", "@types/react-datepicker": "^6.2.0", @@ -3774,6 +3775,19 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@lukemorales/query-key-factory": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@lukemorales/query-key-factory/-/query-key-factory-1.3.4.tgz", + "integrity": "sha512-A3frRDdkmaNNQi6mxIshsDk4chRXWoXa05US8fBo4kci/H+lVmujS6QrwQLLGIkNIRFGjMqp2uKjC4XsLdydRw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@tanstack/query-core": ">= 4.0.0", + "@tanstack/react-query": ">= 4.0.0" + } + }, "node_modules/@next/env": { "version": "15.0.3", "resolved": "https://registry.npmjs.org/@next/env/-/env-15.0.3.tgz", diff --git a/package.json b/package.json index 8209897d..b58225f7 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "dependencies": { "@headlessui/react": "^2.2.0", "@hookform/resolvers": "^3.9.1", + "@lukemorales/query-key-factory": "^1.3.4", "@tanstack/react-query": "^5.61.3", "@tanstack/react-query-devtools": "^5.61.3", "@types/react-datepicker": "^6.2.0", From 3acc3143c9570d0fa54bcb7de0af16c8c17ad575 Mon Sep 17 00:00:00 2001 From: cloud0406 Date: Thu, 19 Dec 2024 11:37:15 +0900 Subject: [PATCH 004/209] =?UTF-8?q?=E2=9C=A8[Feat]=20=EC=BF=BC=EB=A6=AC?= =?UTF-8?q?=ED=82=A4=20=ED=8C=A9=ED=86=A0=EB=A6=AC=20=EC=98=88=EC=8B=9C=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 --- .../react-query/book-club/customhooks.tsx | 0 src/features/react-query/book-club/index.tsx | 0 src/features/react-query/book-club/queries.ts | 83 +++++++++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 src/features/react-query/book-club/customhooks.tsx create mode 100644 src/features/react-query/book-club/index.tsx create mode 100644 src/features/react-query/book-club/queries.ts diff --git a/src/features/react-query/book-club/customhooks.tsx b/src/features/react-query/book-club/customhooks.tsx new file mode 100644 index 00000000..e69de29b diff --git a/src/features/react-query/book-club/index.tsx b/src/features/react-query/book-club/index.tsx new file mode 100644 index 00000000..e69de29b diff --git a/src/features/react-query/book-club/queries.ts b/src/features/react-query/book-club/queries.ts new file mode 100644 index 00000000..9d5b51ea --- /dev/null +++ b/src/features/react-query/book-club/queries.ts @@ -0,0 +1,83 @@ +import { createQueryKeys } from '@lukemorales/query-key-factory'; +import apiClient from '@/lib/utils/apiClient'; + +interface BookClubFilters { + category?: string; + status?: string; + search?: string; + sort?: 'latest' | 'popular'; +} + +interface ReviewFilters { + rating?: number; + hasComment?: boolean; + sort?: 'latest' | 'rating'; +} + +export const bookClubs = createQueryKeys('bookClubs', { + all: (filters?: BookClubFilters) => ({ + queryKey: [{ filters: filters || {} }], + queryFn: (ctx) => + apiClient.get('/book-clubs', { + params: { + ...filters, + page: ctx.pageParam ?? 1, + size: 10, + }, + }), + }), + detail: (bookClubId: string) => ({ + queryKey: [bookClubId], + queryFn: () => apiClient.get(`/book-clubs/${bookClubId}`), + contextQueries: { + reviews: (filters?: ReviewFilters) => ({ + queryKey: [{ filters: filters || {} }], + queryFn: (ctx) => + apiClient.get(`/book-clubs/${bookClubId}/reviews`, { + params: { + ...filters, + page: ctx.pageParam ?? 1, + size: 10, + }, + }), + }), + likes: { + queryKey: ['likes'], + queryFn: () => apiClient.get(`/book-clubs/${bookClubId}/likes`), + }, + }, + }), + myJoined: (filters?: BookClubFilters) => ({ + queryKey: [{ filters: filters || {} }], + queryFn: (ctx) => + apiClient.get('/book-clubs/my-joined', { + params: { + ...filters, + page: ctx.pageParam ?? 1, + size: 10, + }, + }), + }), + myCreated: (filters?: BookClubFilters) => ({ + queryKey: [{ filters: filters || {} }], + queryFn: (ctx) => + apiClient.get('/book-clubs/my-created', { + params: { + ...filters, + page: ctx.pageParam ?? 1, + size: 10, + }, + }), + }), + myReviews: (filters?: ReviewFilters) => ({ + queryKey: [{ filters: filters || {} }], + queryFn: (ctx) => + apiClient.get('/book-clubs/my-reviews', { + params: { + ...filters, + page: ctx.pageParam ?? 1, + size: 10, + }, + }), + }), +}); From 88bd34b844498bd052db9c7a9eeacf588f36a7c8 Mon Sep 17 00:00:00 2001 From: cloud0406 Date: Thu, 19 Dec 2024 11:41:34 +0900 Subject: [PATCH 005/209] =?UTF-8?q?=F0=9F=92=AC[Comment]=20todo=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/react-query/book-club/queries.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/features/react-query/book-club/queries.ts b/src/features/react-query/book-club/queries.ts index 9d5b51ea..06da36fa 100644 --- a/src/features/react-query/book-club/queries.ts +++ b/src/features/react-query/book-club/queries.ts @@ -1,13 +1,13 @@ import { createQueryKeys } from '@lukemorales/query-key-factory'; import apiClient from '@/lib/utils/apiClient'; +// TODO: 추후 각자 구현하는 api 명세에 맞게 filter 타입 정의해주세요 interface BookClubFilters { category?: string; status?: string; search?: string; sort?: 'latest' | 'popular'; } - interface ReviewFilters { rating?: number; hasComment?: boolean; @@ -41,10 +41,6 @@ export const bookClubs = createQueryKeys('bookClubs', { }, }), }), - likes: { - queryKey: ['likes'], - queryFn: () => apiClient.get(`/book-clubs/${bookClubId}/likes`), - }, }, }), myJoined: (filters?: BookClubFilters) => ({ From 3751db4434b7e03e1d5d1ef639ac1aef603570b4 Mon Sep 17 00:00:00 2001 From: Minkyung Kim <97824352+wynter24@users.noreply.github.com> Date: Thu, 19 Dec 2024 11:42:25 +0900 Subject: [PATCH 006/209] =?UTF-8?q?=E2=99=BB=EF=B8=8F[Refactor]=20?= =?UTF-8?q?=EB=AA=A8=EC=9E=84=20=EB=A9=94=EC=9D=B8=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20api=20=EC=97=B0=EB=8F=99=20#175=20(#197)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨[Feat] 모임 조회 api 작성 #175 * ♻️[Refactor] BookClubParams 타입 분리 #175 * ♻️[Refactor] 검색창을 SearchSection 컴포넌트로 분리 #175 * ✨[Feat] 커스텀 훅 생성 #175 * ✨[Feat] BookClubContainer 생성 #175 * ✨[Feat] 컴포넌트에서 BookClubContainer 사용하하기 #175 * 🚚[Rename] bookclub 타입 파일명 수정 #175 * ♻️[Refactor] 탭과 bookClubType 타입 통일 #175 * ♻️[Refactor] Card 컴포넌트의 타입명 meetingType을 bookClubType으로 통일일 #175 * 💄[Design] Card 컴폰너트 location 있을 때만 표시 #175 * ♻️[Refactor] bookClubType을 api와 ui 타입으로 분리 및 전환 #175 * ♻️[Refactor] 모임 만들기 페이지 이동 경로 추가 #175 * ✨[Feat] 모임일 날짜 포맷팅 #175 * ✨[Feat] filterSection API 연결 #175 * ✨[Feat] 신청 가능 필터 기능 구현 #175 * ♻️[Refactor] 이전 변경사항 반영 #175 * 💬[Comment] 불필요한 주석 제거 #175 * ♻️[Refactor] 모임 목록 api가 연결된 컴포넌트로 교체 #175 * ♻️[Refactor] API 호출 로직에 useCallback 적용 #175 * ✅[Test] SortingButton 테스트 변수명 수정 #175 * ♻️[Refactor] 중복된 타입 재사용 #175 * ♻️[Refactor] 날짜 변환 함수 로직 수정 및 case 추가 #175 * 🔥[Remove] bookClubType 통일하는 불필요한 로직 제거 #175 * ♻️[Refactor] CategoryTabs: API와 UI 데이터 매핑 로직 제거 #175 * ♻️[Refactor] 불필요한 타입 제거 #175 * ♻️[Refactor] CategoryTabs 컴포넌트 탭 변경 로직 수정 #175 * 💬[Comment] 불필요한 주석 제거 #175 * ♻️[Refactor] ClubListSection 컴포넌트- Card에 모집마감 조건 추가 #175 * 🎨[Style] 불필요한 console.log 제거 #175 * 💬[Comment] 불필요한 주석 제거 #175 * 🎨[Style] filterCheckboxHandelr 오타 수정 #175 --- src/app/bookclub/page.tsx | 4 +- src/app/page.tsx | 4 +- src/components/card/Card.tsx | 4 +- src/components/card/ClubCard.stories.tsx | 2 +- .../sorting-button/SortingButton.test.tsx | 4 +- .../sorting-button/SortingButton.tsx | 4 +- src/features/bookclub/api/bookclubApi.ts | 7 + src/features/bookclub/api/index.ts | 1 - .../bookclub/components/BookClubMainPage.tsx | 35 ++++- .../bookclub/components/CategoryTabs.tsx | 34 ++++- .../bookclub/components/ClubListSection.tsx | 136 ++++++------------ .../bookclub/components/FilterSection.tsx | 129 ++++++++++++----- .../bookclub/components/SearchSection.tsx | 20 +++ src/features/bookclub/components/index.ts | 1 + .../bookclub/container/BookClubContainer.tsx | 93 +++++------- .../bookclub/hooks/useFetchBookClubList.ts | 68 ++++++--- src/features/bookclub/types/bookclubs.ts | 26 ++++ src/features/bookclub/types/index.ts | 1 - src/lib/utils/formatDateForUI.ts | 25 ++++ 19 files changed, 372 insertions(+), 226 deletions(-) create mode 100644 src/features/bookclub/api/bookclubApi.ts delete mode 100644 src/features/bookclub/api/index.ts create mode 100644 src/features/bookclub/components/SearchSection.tsx create mode 100644 src/features/bookclub/types/bookclubs.ts delete mode 100644 src/features/bookclub/types/index.ts create mode 100644 src/lib/utils/formatDateForUI.ts diff --git a/src/app/bookclub/page.tsx b/src/app/bookclub/page.tsx index 4477773c..a1c7aa4f 100644 --- a/src/app/bookclub/page.tsx +++ b/src/app/bookclub/page.tsx @@ -1,9 +1,9 @@ -import { BookClubMainPage } from '@/features/bookclub/components'; +import BookClubContainer from '@/features/bookclub/container/BookClubContainer'; export default function Home() { return ( <> - + ); } diff --git a/src/app/page.tsx b/src/app/page.tsx index 8abe04c8..5a7c5fed 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,9 +1,9 @@ -import { BookClubMainPage } from '@/features/bookclub/components'; +import BookClubContainer from '@/features/bookclub/container/BookClubContainer'; export default function Home() { return (
- +
); } diff --git a/src/components/card/Card.tsx b/src/components/card/Card.tsx index 05e28876..ed3df962 100644 --- a/src/components/card/Card.tsx +++ b/src/components/card/Card.tsx @@ -212,7 +212,6 @@ function Card(props: CardProps) { max, isPast, isCanceled, - // meetingType, bookClubType, onClick, onDelete, @@ -239,7 +238,7 @@ function Card(props: CardProps) {
- {location} + {location && {location}} {datetime}
@@ -436,7 +435,6 @@ function Card(props: CardProps) { datetime, isLiked, onLikeClick, - // meetingType, bookClubType, current, max, diff --git a/src/components/card/ClubCard.stories.tsx b/src/components/card/ClubCard.stories.tsx index 7e66b561..61bf5aa4 100644 --- a/src/components/card/ClubCard.stories.tsx +++ b/src/components/card/ClubCard.stories.tsx @@ -9,7 +9,7 @@ const meta = { layout: 'centered', }, argTypes: { - meetingType: { + bookClubType: { control: 'select', options: ['FREE', 'FIXED'], }, diff --git a/src/components/sorting-button/SortingButton.test.tsx b/src/components/sorting-button/SortingButton.test.tsx index edb21c90..5473d796 100644 --- a/src/components/sorting-button/SortingButton.test.tsx +++ b/src/components/sorting-button/SortingButton.test.tsx @@ -19,10 +19,10 @@ describe('SortingButton', () => { //유저가 최초 클릭 시 최신순->마감임박 순으로 전환. 'DEADLINE'으로 onClickSorting 함수 호출 await userEvent.click(button); - expect(mockOnClickSorting).toHaveBeenCalledWith('DEADLINE'); + expect(mockOnClickSorting).toHaveBeenCalledWith('END'); //유저가 두번째 클릭 시 마감임박->최신순 순으로 전환. 'NEWEST'으로 onClickSorting 함수 호출 await userEvent.click(button); - expect(mockOnClickSorting).toHaveBeenCalledWith('NEWEST'); + expect(mockOnClickSorting).toHaveBeenCalledWith('DESC'); }); it('날짜순 SortingButton 렌더링 확인', async () => { diff --git a/src/components/sorting-button/SortingButton.tsx b/src/components/sorting-button/SortingButton.tsx index a17f4d5e..22e7b977 100644 --- a/src/components/sorting-button/SortingButton.tsx +++ b/src/components/sorting-button/SortingButton.tsx @@ -19,7 +19,7 @@ function SortingButton({ setIsActive(!isActive); switch (variant) { case 'byDeadline': - sortBy = isActive ? 'NEWEST' : 'DEADLINE'; + sortBy = isActive ? 'DESC' : 'END'; break; case 'byDate': @@ -32,7 +32,7 @@ function SortingButton({ const renderLabel = () => { switch (variant) { case 'byDeadline': - return '마감임박순'; + return '마감임박'; case 'byDate': return isActive ? '오래된순' : '최신순'; diff --git a/src/features/bookclub/api/bookclubApi.ts b/src/features/bookclub/api/bookclubApi.ts new file mode 100644 index 00000000..8c480570 --- /dev/null +++ b/src/features/bookclub/api/bookclubApi.ts @@ -0,0 +1,7 @@ +import apiClient from '@/lib/utils/apiClient'; +import { BookClubParams } from '../types/bookclubs'; + +export const getBookClubs = async (params?: BookClubParams) => { + const response = await apiClient.get('/book-clubs', { params }); + return response.data.bookClubs; +}; diff --git a/src/features/bookclub/api/index.ts b/src/features/bookclub/api/index.ts deleted file mode 100644 index cb0ff5c3..00000000 --- a/src/features/bookclub/api/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/src/features/bookclub/components/BookClubMainPage.tsx b/src/features/bookclub/components/BookClubMainPage.tsx index dcc537db..86dec961 100644 --- a/src/features/bookclub/components/BookClubMainPage.tsx +++ b/src/features/bookclub/components/BookClubMainPage.tsx @@ -4,18 +4,45 @@ import { HeaderSection, FilterSection, ClubListSection, + SearchSection, } from '@/features/bookclub/components'; import CategoryTabs from './CategoryTabs'; +import { BookClub, BookClubParams } from '../types/bookclubs'; +import { Dispatch, SetStateAction } from 'react'; -function BookClubMainPage() { +interface BookClubMainPageProps { + bookClubs: BookClub[]; + setBookClubs: Dispatch>; + loading: boolean; + filters: BookClubParams; + onFilterChange: (newFilters: Partial) => void; +} + +function BookClubMainPage({ + bookClubs, + setBookClubs, + // loading, + filters, + onFilterChange, +}: BookClubMainPageProps) { return ( <>
- - + + + onFilterChange({ searchKeyword: value }) + } + /> +
- + ); } diff --git a/src/features/bookclub/components/CategoryTabs.tsx b/src/features/bookclub/components/CategoryTabs.tsx index 74f7e4fb..b7205316 100644 --- a/src/features/bookclub/components/CategoryTabs.tsx +++ b/src/features/bookclub/components/CategoryTabs.tsx @@ -1,14 +1,36 @@ import Tab from '@/components/tab/Tab'; -import { useState } from 'react'; +import { BookClubParams } from '../types/bookclubs'; + +interface CategoryTabsProps { + filters: BookClubParams; + onFilterChange: (newFilters: Partial) => void; +} + +const TAB_LABELS = { + ALL: '전체', + FREE: '자유책', + FIXED: '지정책', +} as const; + +function CategoryTabs({ filters, onFilterChange }: CategoryTabsProps) { + const activeTabKey = filters?.bookClubType || 'ALL'; + const activeTabLabel = TAB_LABELS[activeTabKey as keyof typeof TAB_LABELS]; + + const handleTabChange = (selectedLabel: string) => { + const selectedKey = ( + Object.keys(TAB_LABELS) as (keyof typeof TAB_LABELS)[] + ).find((key) => TAB_LABELS[key] === selectedLabel); + if (selectedKey) { + onFilterChange({ bookClubType: selectedKey }); + } + }; -function CategoryTabs() { - const [selectedTab, setSelectedTab] = useState('전체'); return (
setSelectedTab(item)} + items={Object.values(TAB_LABELS)} + activeTab={activeTabLabel} + onTabChange={(item) => handleTabChange(item)} tabType="MAIN_TAB" />
diff --git a/src/features/bookclub/components/ClubListSection.tsx b/src/features/bookclub/components/ClubListSection.tsx index b3af9f86..b2c00645 100644 --- a/src/features/bookclub/components/ClubListSection.tsx +++ b/src/features/bookclub/components/ClubListSection.tsx @@ -1,105 +1,63 @@ 'use client'; import Card from '@/components/card/Card'; +import { BookClub } from '../types/bookclubs'; +import { formatDateForUI } from '@/lib/utils/formatDateForUI'; import { useRouter } from 'next/navigation'; -const cardData = [ - { - clubId: 1, - imageUrl: '/images/defaultBookClub.jpg', - imageAlt: '모임 이미지', - title: '독서 모임 1', - location: '서울 강남구', - datetime: '2024-01-20 14:00', - isLiked: false, - current: 3, - max: 8, - isPast: false, - isCanceled: false, - clubStatus: 'pending' as const, - meetingType: 'OFFLINE' as const, - bookClubType: 'FIXED' as const, - - // meetingType: 'FIXED', // 정확한 리터럴 값 설정 - // status: 'pending', - }, - { - clubId: 2, - imageUrl: '/images/defaultBookClub.jpg', - imageAlt: '모임 이미지', - title: '독서 모임 1', - location: '서울 강남구', - datetime: '2024-01-20 14:00', - isLiked: false, - current: 3, - max: 8, - isPast: false, - isCanceled: false, - clubStatus: 'pending' as const, - meetingType: 'OFFLINE' as const, - bookClubType: 'FIXED' as const, - - // meetingType: 'FIXED', // 정확한 리터럴 값 설정 - // status: 'pending', - }, - { - clubId: 3, - imageUrl: '/images/defaultBookClub.jpg', - imageAlt: '모임 이미지', - title: '독서 모임 1', - location: '서울 강남구', - datetime: '2024-01-20 14:00', - isLiked: false, - current: 3, - max: 8, - isPast: false, - isCanceled: false, - clubStatus: 'pending' as const, - meetingType: 'OFFLINE' as const, - bookClubType: 'FIXED' as const, +interface ClubListSectionProps { + bookClubs: BookClub[]; +} - // meetingType: 'FIXED', // 정확한 리터럴 값 설정 - // status: 'pending', - }, - { - clubId: 4, - imageUrl: '/images/defaultBookClub.jpg', - imageAlt: '모임 이미지', - title: '독서 모임 1', - location: '서울 강남구', - datetime: '2024-01-20 14:00', - isLiked: false, - current: 3, - max: 8, - isPast: false, - isCanceled: false, - clubStatus: 'pending' as const, - meetingType: 'OFFLINE' as const, - bookClubType: 'FIXED' as const, +function ClubListSection({ bookClubs = [] }: ClubListSectionProps) { + const router = useRouter(); - // meetingType: 'FIXED', // 정확한 리터럴 값 설정 - // status: 'pending', - }, -]; + const clubStatus = ( + memberCount: number, + memberLimit: number, + endDate: string, + ) => { + if (new Date(endDate) < new Date() || memberCount === memberLimit) { + return 'closed'; + } + return 3 < memberCount ? 'confirmed' : 'pending'; + }; -function ClubListSection() { - const router = useRouter(); return (
- {cardData.map((card, index) => ( -
+ {bookClubs?.length > 0 ? ( + bookClubs.map((club) => ( router.push(`/bookclub/${card.clubId}`)} - onLikeClick={() => console.log('좋아요 클릭')} - onDelete={() => console.log('삭제 클릭')} + key={club.id} + clubId={club.id} + imageUrl={club.imageUrl || '/images/profile.png'} + imageAlt={club.title} + title={club.title} + location={club.town || ''} + datetime={formatDateForUI(club.targetDate, 'KOREAN')} + isLiked={club.isLiked} + current={club.memberCount} + max={club.memberLimit} + isPast={new Date(club.endDate) < new Date()} // 지난 모임 여부 + isCanceled={false} // 모임 취소 여부 (API 값에 따라 변경 가능) + bookClubType={club.bookClubType} + meetingType={club.meetingType} + clubStatus={clubStatus( + club.memberCount, + club.memberLimit, + club.endDate, + )} + onLikeClick={() => console.log(`${club.title} 좋아요 클릭`)} + onClick={() => router.push(`/bookclub/${club.id}`)} + onDelete={() => console.log(`${club.title} 삭제 클릭`)} /> + )) + ) : ( +
+

아직 책 모임이 없어요.

+

지금 바로 책 모임을 만들어보세요.

- ))} + )}
); } diff --git a/src/features/bookclub/components/FilterSection.tsx b/src/features/bookclub/components/FilterSection.tsx index 961d46b7..22d03246 100644 --- a/src/features/bookclub/components/FilterSection.tsx +++ b/src/features/bookclub/components/FilterSection.tsx @@ -2,49 +2,108 @@ import DropDown from '@/components/drop-down/DropDown'; import FilterCheckbox from '@/components/filter-checkbox/FilterCheckbox'; -import SearchBox from '@/components/search-box/SearchBox'; -import { useState } from 'react'; +import { ChangeEvent, Dispatch, SetStateAction, useState } from 'react'; import SortingButton from '@/components/sorting-button/SortingButton'; +import { BookClub, BookClubParams } from '../types/bookclubs'; -function FilterSection() { +interface CategoryTabsProps { + bookClubs: BookClub[]; + setBookClubs: Dispatch>; + onFilterChange: (newFilters: Partial) => void; +} + +function FilterSection({ + bookClubs, + setBookClubs, + onFilterChange, +}: CategoryTabsProps) { const [showAvailableOnly, setShowAvailableOnly] = useState(false); // 신청가능 - const [searchValue, setSearchValue] = useState(''); // 검색 + + const filterCheckboxHandler = (e: ChangeEvent) => { + const isChecked = e.target.checked; + setShowAvailableOnly(isChecked); + // onFilterChange(showAvailableOnly); // 백엔드에서 처리하는 로직 필요? + if (isChecked) { + const filteredBookClubs = bookClubs.filter( + (club) => club.memberCount < club.memberLimit, + ); + setBookClubs(filteredBookClubs); + } + }; + + const handleMemberLimitChange = (selectedValue: string | undefined) => { + let memberLimit = undefined; + + switch (selectedValue) { + case 'TWO_FOUR': + memberLimit = 4; + break; + case 'FIVE_SEVEN': + memberLimit = 7; + break; + case 'EIGHT_TEN': + memberLimit = 10; + break; + case 'OVER_ELEVEN': + memberLimit = 11; + break; + default: + memberLimit = undefined; + } + + onFilterChange({ memberLimit: memberLimit }); + }; + + const onChangeOnOffSelection = (selectedLabel: string | undefined) => { + const validValues: BookClubParams['meetingType'][] = [ + 'ALL', + 'ONLINE', + 'OFFLINE', + ]; + + if ( + selectedLabel && + validValues.includes(selectedLabel as BookClubParams['meetingType']) + ) { + onFilterChange({ + meetingType: selectedLabel as BookClubParams['meetingType'], + }); + } else { + onFilterChange({ meetingType: undefined }); + } + }; + + const onChangeOrderSelection = (order: string) => { + onFilterChange({ order: order as BookClubParams['order'] }); + }; return ( - <> -
- setSearchValue(e.target.value)} - aria-label="책 검색" + //
+
+
+ -
-
-
- {}} - aria-label="온라인/오프라인 선택" - /> - {}} - aria-label="인원 수 선택" - /> - setShowAvailableOnly(e.target.checked)} - aria-label="신청가능 필터" - /> -
- {}} - aria-label="마감임박 정렬" + +
- + +
); } diff --git a/src/features/bookclub/components/SearchSection.tsx b/src/features/bookclub/components/SearchSection.tsx new file mode 100644 index 00000000..df4b812f --- /dev/null +++ b/src/features/bookclub/components/SearchSection.tsx @@ -0,0 +1,20 @@ +import SearchBox from '@/components/search-box/SearchBox'; + +function SearchSection({ + searchValue, + onSearchChange, +}: { + searchValue: string; + onSearchChange: (value: string) => void; +}) { + return ( +
+ onSearchChange(e.target.value)} + aria-label="책 검색" + /> +
+ ); +} +export default SearchSection; diff --git a/src/features/bookclub/components/index.ts b/src/features/bookclub/components/index.ts index 030f0e28..8e5a1657 100644 --- a/src/features/bookclub/components/index.ts +++ b/src/features/bookclub/components/index.ts @@ -2,3 +2,4 @@ export { default as BookClubMainPage } from './BookClubMainPage'; export { default as HeaderSection } from './HeaderSection'; export { default as ClubListSection } from './ClubListSection'; export { default as FilterSection } from './FilterSection'; +export { default as SearchSection } from './SearchSection'; diff --git a/src/features/bookclub/container/BookClubContainer.tsx b/src/features/bookclub/container/BookClubContainer.tsx index 14123f2e..c45fc773 100644 --- a/src/features/bookclub/container/BookClubContainer.tsx +++ b/src/features/bookclub/container/BookClubContainer.tsx @@ -1,59 +1,34 @@ -// 'use client'; - -// import React, { useState } from 'react'; -// import { useFetchBookClubList } from '../hooks/useFetchBookClubList'; -// import CategoryTabs from '../components/CategoryTabs'; -// import { FilterSection } from '../components'; - -// function BookClubMainPage() { -// const { bookClubList, isLoading } = useFetchBookClubList(); - -// // const [selectedOnOff, setSelectedOnOff] = useState( -// // undefined, -// // ); - -// const [selectedSorting, setSelectedSorting] = useState( -// undefined, -// ); -// const [selectedTab, setSelectedTab] = useState('전체'); // 탭 -// const [searchValue, setSearchValue] = useState(''); // 검색 -// const [selectedMeetingType, setSelectedMeetingType] = useState('ALL'); // 온/오프라인인 -// const [selectedMemberCount, setSelectedMemberCount] = useState< -// string | undefined -// >(undefined); -// const [showAvailableOnly, setShowAvailableOnly] = useState(false); // 신청가능 - -// // 필요한 데이터 가공 -// const categoryData = Array.from( -// new Set(bookClubList.map((club) => club.meetingType)) // 중복 제거 -// ); - -// const filteredData = bookClubList.filter((club) => { -// const matchesTab = selectedTab === '전체' || club.meetingType === selectedTab; -// const matchesSearch = club.title.includes(searchValue); -// return matchesTab && matchesSearch; -// }); - -// if (isLoading) { -// return
Loading...
; -// } - -// return ( -//
-// -// -//
-// ); -// } - -// export default BookClubMainPage; +'use client'; + +import React from 'react'; +import useBookClubList from '../hooks/useFetchBookClubList'; +import { BookClubMainPage } from '../components'; + +const BookClubContainer = () => { + // 커스텀 훅에서 상태와 핸들러 가져오기 + const { bookClubs, setBookClubs, loading, filters, updateFilters } = + useBookClubList(); + + const handleFilterChange = (newFilter: Partial) => { + updateFilters(newFilter); + }; + + // 페이지네이션 핸들러 + // const handlePageChange = (page: number) => { + // goToPage(page); + // }; + + return ( + + ); +}; + +export default BookClubContainer; diff --git a/src/features/bookclub/hooks/useFetchBookClubList.ts b/src/features/bookclub/hooks/useFetchBookClubList.ts index 1a69f895..808799ad 100644 --- a/src/features/bookclub/hooks/useFetchBookClubList.ts +++ b/src/features/bookclub/hooks/useFetchBookClubList.ts @@ -1,24 +1,54 @@ -import apiClient from '@/lib/utils/apiClient'; -import { useEffect, useState } from 'react'; +import { useState, useEffect, useCallback } from 'react'; +import { getBookClubs } from '../api/bookclubApi'; +import { BookClub, BookClubParams } from '../types/bookclubs'; -export const useFetchBookClubList = () => { - const [bookClubList, setBookClubList] = useState([]); // 목록 데이터 저장 - const [isLoading, setIsLoading] = useState(false); // 로딩 상태 관리 +const useBookClubList = () => { + const [bookClubs, setBookClubs] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [filters, setFilters] = useState({ + bookClubType: 'ALL', + meetingType: 'ALL', + order: 'DESC', + page: 1, + size: 10, + searchKeyword: '', + }); + + const fetchData = useCallback(async () => { + setLoading(true); + setError(null); + try { + const data = await getBookClubs(filters); // API 호출 + setBookClubs(data); + } catch (err) { + setError(err as Error); + } finally { + setLoading(false); + } + }, [filters]); useEffect(() => { - const fetchBookClubs = async () => { - setIsLoading(true); - try { - const response = await apiClient.get('/api/v1/book-clubs'); - setBookClubList(response.data.bookClubs); - } catch (error) { - console.error(error); - } finally { - setIsLoading(false); - } - }; - fetchBookClubs(); - }, []); + fetchData(); + }, [fetchData]); + + const updateFilters = (newFilters: Partial) => { + setFilters((prevFilters) => ({ ...prevFilters, ...newFilters })); + }; - return { bookClubList, isLoading }; + const goToPage = (pageNumber: number) => { + setFilters((prevFilters) => ({ ...prevFilters, page: pageNumber })); + }; + + return { + bookClubs, + setBookClubs, + loading, + error, + filters, + updateFilters, + goToPage, + }; }; + +export default useBookClubList; diff --git a/src/features/bookclub/types/bookclubs.ts b/src/features/bookclub/types/bookclubs.ts new file mode 100644 index 00000000..3c8318c3 --- /dev/null +++ b/src/features/bookclub/types/bookclubs.ts @@ -0,0 +1,26 @@ +export interface BookClubParams { + bookClubType?: 'ALL' | 'FREE' | 'FIXED'; + meetingType?: 'ALL' | 'ONLINE' | 'OFFLINE'; + order?: 'DESC' | 'END'; + memberLimit?: number; + location?: string; + targetDate?: string; + page?: number; + size?: number; + searchKeyword?: string; +} + +export interface BookClub { + id: number; + title: string; + description: string; + meetingType: 'ONLINE' | 'OFFLINE'; + bookClubType: 'FREE' | 'FIXED'; + targetDate: string; // 모임일 + endDate: string; // 모집마감일 + memberLimit: number; + town: string | null; + memberCount: number; + isLiked: boolean; + imageUrl?: string; +} diff --git a/src/features/bookclub/types/index.ts b/src/features/bookclub/types/index.ts deleted file mode 100644 index cb0ff5c3..00000000 --- a/src/features/bookclub/types/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/src/lib/utils/formatDateForUI.ts b/src/lib/utils/formatDateForUI.ts new file mode 100644 index 00000000..9431b209 --- /dev/null +++ b/src/lib/utils/formatDateForUI.ts @@ -0,0 +1,25 @@ +export const formatDateForUI = ( + isoString: string, + outputFormat: 'KOREAN' | 'DATE_ONLY', +): string => { + const date = new Date(isoString); + + const daysOfWeek = ['일', '월', '화', '수', '목', '금', '토']; + const dayOfWeek = daysOfWeek[date.getDay()]; + + const year = date.getFullYear(); + const month = (date.getMonth() + 1).toString().padStart(2, '0'); + const day = date.getDate().toString().padStart(2, '0'); + + let hour = date.getHours(); + const minute = date.getMinutes().toString().padStart(2, '0'); + const meridiem = hour < 12 ? '오전' : '오후'; + hour = hour % 12 || 12; + + switch (outputFormat) { + case 'KOREAN': + return `${month}/${day}(${dayOfWeek}) ${meridiem} ${hour}:${minute}`; + case 'DATE_ONLY': + return `${year}.${month}.${day}`; + } +}; From 9343643338465279f34ac0143f55981399015850 Mon Sep 17 00:00:00 2001 From: cloud0406 Date: Sun, 22 Dec 2024 10:58:05 +0900 Subject: [PATCH 007/209] =?UTF-8?q?=E2=99=BB=EF=B8=8F[Refactor]=20?= =?UTF-8?q?=EB=B6=81=ED=81=B4=EB=9F=BD=20=EC=83=9D=EC=84=B1=20=EC=BB=A4?= =?UTF-8?q?=EC=8A=A4=ED=85=80=20=ED=9B=85=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../club-create/hooks/useCreateBookClub.ts | 10 ++++------ .../react-query/book-club/customhooks.tsx | 20 +++++++++++++++++++ src/features/react-query/book-club/index.tsx | 2 ++ 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/features/club-create/hooks/useCreateBookClub.ts b/src/features/club-create/hooks/useCreateBookClub.ts index aa05ae29..0a044bdf 100644 --- a/src/features/club-create/hooks/useCreateBookClub.ts +++ b/src/features/club-create/hooks/useCreateBookClub.ts @@ -2,14 +2,14 @@ import { useState } from 'react'; import { BookClubForm } from '../types'; -import { createBookClub } from '../api'; +import { useBookClubCreateMutation } from '@/features/react-query/book-club'; export const useCreateBookClub = () => { - const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); + const { mutateAsync: createBookClub, isPending } = + useBookClubCreateMutation(); const onSubmit = async (data: BookClubForm) => { - setIsLoading(true); setError(null); try { @@ -18,10 +18,8 @@ export const useCreateBookClub = () => { } catch (error) { setError('북클럽 생성에 실패했습니다.'); throw error; - } finally { - setIsLoading(false); } }; - return { onSubmit, isLoading, error }; + return { onSubmit, isLoading: isPending, error }; }; diff --git a/src/features/react-query/book-club/customhooks.tsx b/src/features/react-query/book-club/customhooks.tsx index e69de29b..84f82ebc 100644 --- a/src/features/react-query/book-club/customhooks.tsx +++ b/src/features/react-query/book-club/customhooks.tsx @@ -0,0 +1,20 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { BookClubForm } from '@/features/club-create/types'; +import { createBookClub } from '@/features/club-create/api'; +import { bookClubs } from './queries'; + +export function useBookClubCreateMutation() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (data: BookClubForm) => createBookClub(data), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: bookClubs.all().queryKey, + }); + queryClient.invalidateQueries({ + queryKey: bookClubs.myCreated().queryKey, + }); + }, + }); +} diff --git a/src/features/react-query/book-club/index.tsx b/src/features/react-query/book-club/index.tsx index e69de29b..01e8d3b3 100644 --- a/src/features/react-query/book-club/index.tsx +++ b/src/features/react-query/book-club/index.tsx @@ -0,0 +1,2 @@ +export * from './queries'; +export * from './customHooks'; From 252b230d9882fdaa39a6e48c84cf5d74e00f0d12 Mon Sep 17 00:00:00 2001 From: cloud0406 Date: Sun, 22 Dec 2024 11:17:09 +0900 Subject: [PATCH 008/209] =?UTF-8?q?=F0=9F=9A=9A[Rename]=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../react-query/book-club/{customhooks.tsx => customHooks.ts} | 0 src/features/react-query/book-club/{index.tsx => index.ts} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/features/react-query/book-club/{customhooks.tsx => customHooks.ts} (100%) rename src/features/react-query/book-club/{index.tsx => index.ts} (100%) diff --git a/src/features/react-query/book-club/customhooks.tsx b/src/features/react-query/book-club/customHooks.ts similarity index 100% rename from src/features/react-query/book-club/customhooks.tsx rename to src/features/react-query/book-club/customHooks.ts diff --git a/src/features/react-query/book-club/index.tsx b/src/features/react-query/book-club/index.ts similarity index 100% rename from src/features/react-query/book-club/index.tsx rename to src/features/react-query/book-club/index.ts From b9ab78cd16ae6effe3dd8f554a3ff688e6a1592f Mon Sep 17 00:00:00 2001 From: cloud0406 Date: Sun, 22 Dec 2024 19:03:33 +0900 Subject: [PATCH 009/209] =?UTF-8?q?=E2=99=BB=EF=B8=8F[Refactor]=20?= =?UTF-8?q?=ED=8F=BC=ED=95=84=EB=93=9C=20=EC=98=B5=EC=85=94=EB=84=90=20pro?= =?UTF-8?q?p=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/CreateClubFormField.tsx | 15 +++++++++++---- src/features/club-create/container/ImageField.tsx | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/features/club-create/components/CreateClubFormField.tsx b/src/features/club-create/components/CreateClubFormField.tsx index eebe70f2..f8ba1950 100644 --- a/src/features/club-create/components/CreateClubFormField.tsx +++ b/src/features/club-create/components/CreateClubFormField.tsx @@ -7,6 +7,7 @@ interface CreateClubFormFieldProps error?: string; currentLength?: number; maxLength?: number; + optional?: boolean; } function CreateClubFormField({ @@ -15,6 +16,7 @@ function CreateClubFormField({ error, currentLength = 0, maxLength, + optional = false, ...props }: CreateClubFormFieldProps) { const isOverMaxLength = maxLength !== undefined && currentLength > maxLength; @@ -22,12 +24,17 @@ function CreateClubFormField({ return (
- + {maxLength !== undefined && ( - {currentLength}/ + {currentLength}/ {maxLength} @@ -35,7 +42,7 @@ function CreateClubFormField({ )}
{children} - {error &&

{error}

} + {error &&

{error}

}
); } diff --git a/src/features/club-create/container/ImageField.tsx b/src/features/club-create/container/ImageField.tsx index 01325f0e..349f9a7b 100644 --- a/src/features/club-create/container/ImageField.tsx +++ b/src/features/club-create/container/ImageField.tsx @@ -16,7 +16,7 @@ function ImageField({ register, setValue, error }: ImageUploadContainerProps) { useImageField(setValue); return ( - +
Date: Sun, 22 Dec 2024 19:36:46 +0900 Subject: [PATCH 010/209] =?UTF-8?q?=E2=9C=A8[Feat]=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=EC=BD=98=20=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/icons/CameraIcon.tsx | 36 ++++++++++++++++++++++++++++++++++++ public/icons/ImageIcon.tsx | 26 ++++++++++++++++++++++++++ public/icons/index.ts | 2 ++ 3 files changed, 64 insertions(+) create mode 100644 public/icons/CameraIcon.tsx create mode 100644 public/icons/ImageIcon.tsx diff --git a/public/icons/CameraIcon.tsx b/public/icons/CameraIcon.tsx new file mode 100644 index 00000000..3e7dcf88 --- /dev/null +++ b/public/icons/CameraIcon.tsx @@ -0,0 +1,36 @@ +import { SVGProps } from 'react'; + +interface CameraIconProps extends SVGProps { + width?: number; + height?: number; +} + +function CameraIcon({ width = 50, height = 50, ...props }: CameraIconProps) { + return ( + + + + + ); +} + +export default CameraIcon; diff --git a/public/icons/ImageIcon.tsx b/public/icons/ImageIcon.tsx new file mode 100644 index 00000000..2f9d4ea7 --- /dev/null +++ b/public/icons/ImageIcon.tsx @@ -0,0 +1,26 @@ +import { SVGProps } from 'react'; + +interface ImageIconProps extends SVGProps { + width?: number; + height?: number; +} + +function ImageIcon({ width = 24, height = 24, ...props }: ImageIconProps) { + return ( + + + + ); +} + +export default ImageIcon; diff --git a/public/icons/index.ts b/public/icons/index.ts index a3f11b0e..dfc7ae55 100644 --- a/public/icons/index.ts +++ b/public/icons/index.ts @@ -13,3 +13,5 @@ export { default as HostIcon } from './HostIcon'; export { default as IcClose } from './IcClose'; export { default as EditIcon } from './EditIcon'; export { default as IcEdit } from './IcEdit'; +export { default as ImageIcon } from './ImageIcon'; +export { default as CameraIcon } from './CameraIcon'; From 20132b6dab29affcb1860346db1e0d30fd2b67b7 Mon Sep 17 00:00:00 2001 From: cloud0406 Date: Sun, 22 Dec 2024 19:37:27 +0900 Subject: [PATCH 011/209] =?UTF-8?q?=E2=9C=A8[Feat]=20=ED=9B=85=20=EB=B0=8F?= =?UTF-8?q?=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=ED=9B=84=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/CreateClubFormField.tsx | 2 +- .../club-create/container/ImageField.tsx | 56 +++++++++---------- .../club-create/hooks/useImageField.ts | 8 ++- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/features/club-create/components/CreateClubFormField.tsx b/src/features/club-create/components/CreateClubFormField.tsx index f8ba1950..47ec736a 100644 --- a/src/features/club-create/components/CreateClubFormField.tsx +++ b/src/features/club-create/components/CreateClubFormField.tsx @@ -30,7 +30,7 @@ function CreateClubFormField({ {maxLength !== undefined && ( - {currentLength}/ + {currentLength}/ ; @@ -12,42 +13,37 @@ interface ImageUploadContainerProps { } function ImageField({ register, setValue, error }: ImageUploadContainerProps) { - const { selectedFileName, handleFileChange, clearFile } = - useImageField(setValue); + const { selectedFileName, handleFileChange } = useImageField( + setValue, + register, + ); return ( -
- - + {selectedFileName ? ( + <> +
+ + + {selectedFileName} + +
+ + ) : ( +
+ + + 이미지를 첨부해 주세요 + +
+ )} + - - {selectedFileName && ( - - )}
); diff --git a/src/features/club-create/hooks/useImageField.ts b/src/features/club-create/hooks/useImageField.ts index 44bf2627..f9fedf9d 100644 --- a/src/features/club-create/hooks/useImageField.ts +++ b/src/features/club-create/hooks/useImageField.ts @@ -1,13 +1,17 @@ 'use client'; import { useState } from 'react'; -import { UseFormSetValue } from 'react-hook-form'; +import { UseFormSetValue, UseFormRegister } from 'react-hook-form'; import { BookClubForm } from '../types'; -export const useImageField = (setValue: UseFormSetValue) => { +export const useImageField = ( + setValue: UseFormSetValue, + register: UseFormRegister, +) => { const [selectedFileName, setSelectedFileName] = useState(''); const handleFileChange = (e: React.ChangeEvent) => { + register('image').onChange(e); const file = e.target.files?.[0]; if (file) { setSelectedFileName(file.name); From 9aeb6dbe50abc0978ce21657342d92331a0d0aa6 Mon Sep 17 00:00:00 2001 From: cloud0406 Date: Sun, 22 Dec 2024 19:43:12 +0900 Subject: [PATCH 012/209] =?UTF-8?q?=F0=9F=92=84[Design]=20=EB=AA=A8?= =?UTF-8?q?=EB=B0=94=EC=9D=BC=20=EB=B0=98=EC=9D=91=ED=98=95=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=20=EB=B0=8F=20=EC=83=89=EC=83=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/club-create/container/RadioButtonGroup.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/features/club-create/container/RadioButtonGroup.tsx b/src/features/club-create/container/RadioButtonGroup.tsx index bf32029f..6233304b 100644 --- a/src/features/club-create/container/RadioButtonGroup.tsx +++ b/src/features/club-create/container/RadioButtonGroup.tsx @@ -23,7 +23,7 @@ function RadioButtonGroup({ const { handleRadioChange } = useSelectAddress({ setValue, name }); return ( -
+
{options.map((option) => (