-
Notifications
You must be signed in to change notification settings - Fork 3
Feat/105/my crew api 연결 #109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 15 commits
2c8278b
aea88e1
91b6927
013b2de
58be236
5faea7f
26d1701
f5472c7
ff655e2
a5fbad9
e466861
9ad835f
ad03a87
4d4dcd9
e946b5e
fd76ffb
bdb86e1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| import { fetchApi } from '@/src/utils/api'; | ||
| import { MyCrewListResponse, PageableTypes } from '@/src/types/crew-card'; | ||
|
|
||
| export async function getMyCrewHostedList(pageable: PageableTypes) { | ||
| const { page, size, sort = ['string'] } = pageable; | ||
|
|
||
| try { | ||
| const response: { data: MyCrewListResponse } = await fetchApi( | ||
| `/api/crews/hosted?page=${page}&size=${size}&sort=${sort}`, | ||
| { | ||
| method: 'GET', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| credentials: 'include', // 인증 정보를 요청에 포함 | ||
| }, | ||
| ); | ||
| if (!response.data) { | ||
| throw new Error('Failed to get my crew hosted list'); | ||
| } | ||
| return response.data; | ||
| } catch (error) { | ||
| // eslint-disable-next-line no-console | ||
| console.error(error); | ||
| } | ||
| return null; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| import { fetchApi } from '@/src/utils/api'; | ||
| import { MyCrewListResponse, PageableTypes } from '@/src/types/crew-card'; | ||
|
|
||
| export async function getMyCrewJoinedList(pageable: PageableTypes) { | ||
| const { page, size, sort = ['string'] } = pageable; | ||
|
|
||
| try { | ||
| const response: { data: MyCrewListResponse } = await fetchApi( | ||
| `/api/crews/joined?page=${page}&size=${size}&sort=${sort}`, | ||
| { | ||
| method: 'GET', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| credentials: 'include', // 인증 정보를 요청에 포함 | ||
| }, | ||
| ); | ||
| if (!response.data) { | ||
| throw new Error('Failed to get my crew joined list'); | ||
| } | ||
| return response.data; | ||
| } catch (error) { | ||
| // eslint-disable-next-line no-console | ||
| console.error(error); | ||
| } | ||
| return null; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import { getMyCrewHostedList } from '@/src/_apis/crew/my-crew-hosted-list'; | ||
| import { MyCrewListResponse, PageableTypes } from '@/src/types/crew-card'; | ||
|
|
||
| export function useGetMyCrewHostedQuery({ pageable }: { pageable: PageableTypes }) { | ||
| const { size, sort = ['string'] } = pageable; | ||
| return { | ||
| queryKey: ['myCrewHosted'], | ||
| queryFn: ({ pageParam = 0 }) => | ||
| getMyCrewHostedList({ page: pageParam, size, sort }).then((response) => { | ||
| if (response === undefined) { | ||
| throw new Error('크루 목록을 불러오는데 실패했습니다.'); | ||
| } | ||
| return response; | ||
| }), | ||
| getNextPageParam: (lastPage: MyCrewListResponse, allPages: MyCrewListResponse[]) => | ||
| lastPage.hasNext ? allPages.length : undefined, | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import { getMyCrewJoinedList } from '@/src/_apis/crew/my-crew-joined-list'; | ||
| import { MyCrewListResponse, PageableTypes } from '@/src/types/crew-card'; | ||
|
|
||
| export function useGetMyCrewJoinedQuery({ pageable }: { pageable: PageableTypes }) { | ||
| const { size, sort = ['string'] } = pageable; | ||
| return { | ||
|
Comment on lines
+4
to
+6
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 정렬 파라미터를 더 유연하게 설정하세요. 현재 정렬 파라미터가 |
||
| queryKey: ['myCrewJoined'], | ||
| queryFn: ({ pageParam = 0 }) => | ||
| getMyCrewJoinedList({ page: pageParam, size, sort }).then((response) => { | ||
| if (response === undefined) { | ||
| throw new Error('크루 목록을 불러오는데 실패했습니다.'); | ||
| } | ||
| return response; | ||
| }), | ||
| getNextPageParam: (lastPage: MyCrewListResponse, allPages: MyCrewListResponse[]) => | ||
| lastPage.hasNext ? allPages.length : undefined, | ||
| }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| 'use client'; | ||
|
|
||
| import { useGetMyCrewHostedQuery } from '@/src/_queries/crew/my-crew-hosted-list-query'; | ||
| import { useInfiniteScroll } from '@/src/hooks/use-infinite-scroll'; | ||
| import CrewCardList from '@/src/components/common/crew-list/crew-card-list'; | ||
|
|
||
| export default function MyCrewParticipationPage() { | ||
| const { data, ref, isFetchingNextPage } = useInfiniteScroll( | ||
| useGetMyCrewHostedQuery({ | ||
| pageable: { page: 0, size: 6, sort: ['createdAt,desc'] }, | ||
| }), | ||
| ); | ||
| return ( | ||
| <div> | ||
| <CrewCardList | ||
| inWhere="my-crew" | ||
| data={data ?? { pages: [], pageParams: [] }} | ||
| ref={ref} | ||
| isFetchingNextPage={isFetchingNextPage} | ||
| /> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| 'use client'; | ||
|
|
||
| import { useGetMyCrewJoinedQuery } from '@/src/_queries/crew/my-crew-joined-list-query'; | ||
| import { useInfiniteScroll } from '@/src/hooks/use-infinite-scroll'; | ||
| import CrewCardList from '@/src/components/common/crew-list/crew-card-list'; | ||
|
|
||
| export default function MyCrewParticipationPage() { | ||
| const { data, ref, isFetchingNextPage } = useInfiniteScroll( | ||
| useGetMyCrewJoinedQuery({ | ||
| pageable: { page: 0, size: 6, sort: ['createdAt,desc'] }, | ||
| }), | ||
| ); | ||
| return ( | ||
| <div> | ||
| <CrewCardList | ||
| inWhere="my-crew" | ||
| data={data ?? { pages: [], pageParams: [] }} | ||
| ref={ref} | ||
| isFetchingNextPage={isFetchingNextPage} | ||
| /> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| 'use client'; | ||
|
|
||
| import { ReactNode, useEffect, useState } from 'react'; | ||
| import { usePathname, useRouter } from 'next/navigation'; | ||
| import Tabs from '@/src/components/common/tab'; | ||
|
|
||
| export default function MyCrewLayout({ children }: { children: ReactNode }) { | ||
| const router = useRouter(); | ||
| const currentPath = usePathname(); | ||
| const myCrewTabs = [ | ||
| { label: '내가 참여한 크루', id: 'joined-crew', route: '/my-crew/joined' }, | ||
| { label: '내가 만든 크루', id: 'hosted-crew', route: '/my-crew/hosted' }, | ||
| ]; | ||
| const [currentTab, setCurrentTab] = useState(myCrewTabs[0].id); | ||
|
|
||
| const handleTabClick = (id: string) => { | ||
| const targetRoute = myCrewTabs.find((tab) => tab.id === id)?.route; | ||
| if (targetRoute) router.push(targetRoute); | ||
| }; | ||
|
Comment on lines
+16
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 에러 처리 개선이 필요합니다
다음과 같이 수정해보세요: const handleTabClick = (id: string) => {
const targetRoute = myCrewTabs.find((tab) => tab.id === id)?.route;
- if (targetRoute) router.push(targetRoute);
+ if (targetRoute) {
+ router.push(targetRoute);
+ } else {
+ console.error(`탭 ID ${id}에 해당하는 경로를 찾을 수 없습니다.`);
+ // TODO: 사용자에게 에러 메시지 표시
+ }
};
|
||
|
|
||
| useEffect(() => { | ||
| const activeTabId = myCrewTabs.find((tab) => tab.route === currentPath)?.id; | ||
| if (activeTabId) setCurrentTab(activeTabId); | ||
| }, [currentPath]); | ||
|
|
||
| return ( | ||
| <div className="py-8 md:py-12.5"> | ||
| <div className="px-3 md:px-8 lg:px-11.5"> | ||
| <Tabs | ||
| variant="default" | ||
| tabs={myCrewTabs} | ||
| activeTab={currentTab} | ||
| onTabClick={(id) => { | ||
| handleTabClick(id); | ||
| }} | ||
| /> | ||
| </div> | ||
| <div className="mt-8 px-3 md:px-8 lg:px-11.5">{children}</div> | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,40 +1,7 @@ | ||
| 'use client'; | ||
|
|
||
| import { useState } from 'react'; | ||
| import Tabs from '@/src/components/common/tab'; | ||
| import { redirect } from 'next/navigation'; | ||
|
|
||
| export default function MyCrewPage() { | ||
| const myPageTabs = [ | ||
| { label: '내가 참여한 크루', id: 'joined-crew' }, | ||
| { label: '내가 만든 크루', id: 'made-crew' }, | ||
| ]; | ||
| const [currentTab, setCurrentTab] = useState(myPageTabs[0].id); | ||
|
|
||
| // TODO: fetchCrewData 함수를 사용하여 데이터를 불러오기 : 파라미터 수정 필요 | ||
| // TODO: 리스트와는 다른 데이터를 사용해야해서 우선 주석처리 했습니다. | ||
| // const { data, ref, isFetchingNextPage } = | ||
| // useInfiniteScroll<MyCrewListResponse>(useGetCrewListQuery()); | ||
|
|
||
| return ( | ||
| <div className="py-8 md:py-12.5"> | ||
| <div className="px-3 md:px-8 lg:px-11.5"> | ||
| <Tabs | ||
| variant="default" | ||
| tabs={myPageTabs} | ||
| activeTab={currentTab} | ||
| onTabClick={(id) => { | ||
| setCurrentTab(id); | ||
| }} | ||
| /> | ||
| </div> | ||
| <div className="mt-8 px-3 md:px-8 lg:px-11.5"> | ||
| {/* <CrewCardList | ||
| inWhere="my-crew" | ||
| data={data} | ||
| ref={ref} | ||
| isFetchingNextPage={isFetchingNextPage} | ||
| /> */} | ||
| </div> | ||
| </div> | ||
| ); | ||
| redirect('/my-crew/joined'); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,9 +36,14 @@ export default function PopOverCalendar({ value, onChange }: PopOverProps) { | |
| <Popover.Target> | ||
| <Button | ||
| type="button" | ||
| onClick={opened ? close : open} | ||
| onFocus={() => setInputTheme('dark')} | ||
| onBlur={() => setInputTheme('white')} | ||
| onFocus={() => { | ||
| setInputTheme('dark'); | ||
| if (!opened) open(); | ||
| }} | ||
| onBlur={() => { | ||
| setInputTheme('white'); | ||
| if (opened) close(); | ||
| }} | ||
|
Comment on lines
+39
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 포커스 및 블러 이벤트 처리 개선 필요 키보드 접근성과 사용자 경험 향상을 위해 다음 사항들을 고려해주세요:
다음과 같이 개선해보세요: + const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout | null>(null);
onFocus={() => {
setInputTheme('dark');
- if (!opened) open();
+ if (timeoutId) clearTimeout(timeoutId);
+ if (!opened) {
+ open();
+ }
}}
onBlur={() => {
setInputTheme('white');
- if (opened) close();
+ const id = setTimeout(() => {
+ if (opened) close();
+ }, 200);
+ setTimeoutId(id);
}}
+ aria-haspopup="true"
+ aria-expanded={opened}
|
||
| className="flex h-11 items-center justify-between rounded-xl border-0 bg-white px-3 py-2.5 text-base font-medium text-gray-800 hover:bg-white hover:text-gray-800 focus:bg-black focus:text-white" | ||
| > | ||
| <span>날짜 선택</span> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
에러 처리 로직 개선이 필요합니다
현재 에러 처리에는 몇 가지 문제가 있습니다:
null을 반환하여 호출자가 에러 상황을 인지하기 어렵습니다다음과 같이 개선하는 것을 제안드립니다:
} catch (error) { - // eslint-disable-next-line no-console - console.error(error); + if (error instanceof Error) { + throw new Error(`내가 주최한 크루 목록을 가져오는데 실패했습니다: ${error.message}`); + } + throw new Error('알 수 없는 에러가 발생했습니다'); } - return null;📝 Committable suggestion