Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
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
27 changes: 27 additions & 0 deletions src/_apis/crew/my-crew-hosted-list.ts
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;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

에러 처리 로직 개선이 필요합니다

현재 에러 처리에는 몇 가지 문제가 있습니다:

  1. 에러 발생 시 조용히 null을 반환하여 호출자가 에러 상황을 인지하기 어렵습니다
  2. 콘솔 로그만 남기고 실제 에러 처리가 없습니다
  3. 다양한 에러 상황(네트워크 오류, 인증 오류 등)에 대한 구체적인 처리가 없습니다

다음과 같이 개선하는 것을 제안드립니다:

  } 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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
}
return null;
}
} catch (error) {
if (error instanceof Error) {
throw new Error(`내가 주최한 크루 목록을 가져오는데 실패했습니다: ${error.message}`);
}
throw new Error('알 수 없는 에러가 발생했습니다');
}
}

27 changes: 27 additions & 0 deletions src/_apis/crew/my-crew-joined-list.ts
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;
}
29 changes: 19 additions & 10 deletions src/_queries/crew/crew-list-queries.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { getCrewList } from '@/src/_apis/crew/crew-list';
import { ConditionTypes, MainCrewListResponse, PageableTypes } from '@/src/types/crew-card';
import { getCrewList } from '../../_apis/crew/crew-list';

export function useGetCrewListQuery(condition: ConditionTypes) {
export function useGetCrewListQuery({
condition,
pageable,
}: {
condition: ConditionTypes;
pageable: PageableTypes;
}) {
const { size, sort = ['string'] } = pageable;
return {
queryKey: [
condition.keyword,
Expand All @@ -11,14 +18,16 @@ export function useGetCrewListQuery(condition: ConditionTypes) {
condition.sortType,
],
queryFn: ({ pageParam = 0 }) =>
getCrewList(condition, { page: pageParam, size: 6, sort: [condition.sortType] }).then(
(response) => {
if (response === undefined) {
throw new Error('Response is null');
}
return response;
},
),
getCrewList(condition, {
page: pageParam,
size,
sort,
}).then((response) => {
if (response === undefined) {
throw new Error('Response is undefined');
}
return response;
}),
getNextPageParam: (lastPage: MainCrewListResponse, allPages: MainCrewListResponse[]) =>
lastPage.hasNext ? allPages.length : undefined,
};
Expand Down
18 changes: 18 additions & 0 deletions src/_queries/crew/my-crew-hosted-list-query.ts
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,
};
}
18 changes: 18 additions & 0 deletions src/_queries/crew/my-crew-joined-list-query.ts
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
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

정렬 파라미터를 더 유연하게 설정하세요.

현재 정렬 파라미터가 ['string']으로 고정되어 있어 유연성이 떨어집니다. 이를 외부에서 입력받을 수 있도록 변경하는 것이 좋겠습니다. 예를 들어, 기본값을 ['createdAt,desc']로 설정하고, 필요에 따라 변경할 수 있도록 합니다.

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,
};
}
23 changes: 23 additions & 0 deletions src/app/(crew)/my-crew/hosted/page.tsx
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>
);
}
23 changes: 23 additions & 0 deletions src/app/(crew)/my-crew/joined/page.tsx
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>
);
}
41 changes: 41 additions & 0 deletions src/app/(crew)/my-crew/layout.tsx
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
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

에러 처리 개선이 필요합니다

targetRoute가 없는 경우에 대한 처리가 누락되어 있습니다. 사용자에게 적절한 피드백을 제공하는 것이 좋습니다.

다음과 같이 수정해보세요:

 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: 사용자에게 에러 메시지 표시
+  }
 };

Committable suggestion skipped: line range outside the PR's diff.


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>
);
}
37 changes: 2 additions & 35 deletions src/app/(crew)/my-crew/page.tsx
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');
}
13 changes: 8 additions & 5 deletions src/app/(crew)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,14 @@ export default function HomePage() {

const { data, ref, isFetchingNextPage } = useInfiniteScroll(
useGetCrewListQuery({
keyword: search,
mainLocation: handleRegionChange(region),
mainCategory,
subCategory,
sortType: sort === 'latest' ? 'LATEST' : 'POPULAR',
condition: {
keyword: search,
mainLocation: handleRegionChange(region),
mainCategory,
subCategory,
sortType: sort === 'latest' ? 'LATEST' : 'POPULAR',
},
pageable: { page: 0, size: 6, sort: ['createdAt,desc'] },
}),
);

Expand Down
1 change: 0 additions & 1 deletion src/app/_components/hero/hero-crew.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import Image from 'next/image';
import Link from 'next/link';
import { useAuthStore } from '@/src/store/use-auth-store';
import Button from '@/src/components/common/input/button';
import ImgHeroCrew from '@/public/assets/icons/ic-dumbbell.svg';

export default function HeroCrew() {
Expand Down
13 changes: 8 additions & 5 deletions src/components/common/crew-list/crew-card-list.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,14 @@ type Story = StoryObj<typeof meta>;
function RenderCrewCardList() {
const { data, ref, isFetchingNextPage } = useInfiniteScroll(
useGetCrewListQuery({
keyword: '',
mainLocation: '',
mainCategory: '',
subCategory: '',
sortType: 'LATEST',
condition: {
keyword: '',
mainLocation: '',
mainCategory: '',
subCategory: '',
sortType: 'LATEST',
},
pageable: { page: 0, size: 6, sort: ['createdAt,desc'] },
}),
);

Expand Down
20 changes: 6 additions & 14 deletions src/components/common/crew-list/crew-card-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,21 @@ import {
import CrewCard from './crew-card';

// CrewCardListProps 타입을 구분하여 정의
interface MainCrewCardListProps {
data: InfiniteData<MainCrewListResponse>;
interface CrewCardListProps {
data: InfiniteData<MainCrewListResponse | MyCrewListResponse>;
isFetchingNextPage: boolean;
inWhere?: undefined;
inWhere?: 'my-crew' | 'main-crew';
}

interface MyCrewCardListProps {
data: InfiniteData<MyCrewListResponse>;
isFetchingNextPage: boolean;
inWhere: 'my-crew';
}

// 유니온 타입으로 정의
type CrewCardListProps = MainCrewCardListProps | MyCrewCardListProps;

function CrewCardList(
{ data, isFetchingNextPage, inWhere }: CrewCardListProps,
ref: React.Ref<HTMLDivElement>,
) {
const crewDataList =
(inWhere === 'my-crew'
? data?.pages.flatMap((page) => page?.content)
: data?.pages?.flatMap((page) => page?.content)) ?? [];
? data?.pages.flatMap((page) => page?.content as MyCrewList[])
: data?.pages?.flatMap((page) => page?.content as MainCrewList[])) ?? [];

const gridColsStyle = inWhere === 'my-crew' ? '' : 'lg:grid-cols-2';

if (data?.pages[0] === undefined)
Expand Down
6 changes: 2 additions & 4 deletions src/components/common/crew-list/crew-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import IcoUser from '@/public/assets/icons/ic-user.svg';
import Profiles from './profiles';

interface CrewCardProps extends MainCrewList {
inWhere?: 'my-crew';
inWhere?: 'my-crew' | 'main-crew';
}

export default function CrewCard({
Expand All @@ -27,9 +27,7 @@ export default function CrewCard({
inWhere,
}: CrewCardProps) {
const [prefetched, setPrefetched] = useState(new Set());
// NOTE: api연결 후 되돌리기
// const CREWPAGE = `/crew/detail/${id}`;
const CREWPAGE = `/crew/detail/1`;
const CREWPAGE = `/crew/detail/${id}`;
const router = useRouter();

const handleCardClick = () => {
Expand Down
11 changes: 8 additions & 3 deletions src/components/common/input/pop-over-calendar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

포커스 및 블러 이벤트 처리 개선 필요

키보드 접근성과 사용자 경험 향상을 위해 다음 사항들을 고려해주세요:

  1. 포커스/블러 이벤트와 팝오버 상태 변경 사이에 발생할 수 있는 경쟁 상태를 방지하기 위해 타이머 추가
  2. ESC 키 처리 추가
  3. 스크린 리더 사용자를 위한 ARIA 속성 추가

다음과 같이 개선해보세요:

+ 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}

Committable suggestion skipped: line range outside the PR's diff.

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>
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/input/text-input/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { RefObject } from 'react';
import React from 'react';
import { UseFormRegisterReturn } from 'react-hook-form';
import {
TextInput as MantineTextInput,
Expand Down