Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ff9d3f5
Squashed commit of the following:
yulrang Nov 11, 2024
bc4056a
[WIP] Feat: GetCrewList API 연결
yulrang Nov 11, 2024
6ac757f
Merge branch 'develop' into Feat/98/CrewAPI
yulrang Nov 11, 2024
0936ad1
✨ Feat: 백엔드 api 연결
yulrang Nov 11, 2024
e64877f
🐛 Fix: 처음 로딩시 카테고리 초기화
yulrang Nov 11, 2024
ab7d7a9
🐛 Fix: 카테고리 동작 개선
yulrang Nov 12, 2024
b4a70ff
🐛 Fix: 지역 필터링 동작 개선
yulrang Nov 12, 2024
22f3307
✨ Feat: 지역 전체 필터 추가
yulrang Nov 12, 2024
60a3b12
Merge branch 'develop' into Feat/98/CrewAPI
yulrang Nov 12, 2024
1026125
🚨 Fix: 타입 개선, 빌드 오류 수정
yulrang Nov 12, 2024
56bea37
🚧 Chore: workflow 수정
yulrang Nov 12, 2024
1d430b1
♻️ Refactor 코드 개선
yulrang Nov 12, 2024
3a7ce8e
🐛 Fix: 코드 개선
yulrang Nov 12, 2024
ecd941d
✨ Feat: 검색 버튼 추가
yulrang Nov 12, 2024
9a3f685
🐛 Fix: 동작 개선
yulrang Nov 12, 2024
89632d1
🚧 Chore: 경로 변경
yulrang Nov 12, 2024
04225e6
Merge branch 'develop' into Feat/98/CrewAPI
yulrang Nov 12, 2024
ae1729a
🐛 Fix: 콘솔에러 추가
yulrang Nov 12, 2024
b4b356c
⚡️ Fix: 검색 엔터방식으로 변경
yulrang Nov 12, 2024
67886c3
🐛 Fix: 동작개선, 에러메시지 보이기
yulrang Nov 12, 2024
3213309
Merge branch 'Feat/98/CrewAPI' of https://github.com/CodeitFESI4-Team…
yulrang Nov 12, 2024
b9b5cb5
💄 Design: 스타일 수정
yulrang Nov 12, 2024
5687085
🐛 Fix: 에러처리 추가
yulrang Nov 13, 2024
c3382b1
🚨 Fix: 빌드 오류 수정
yulrang Nov 13, 2024
be1480d
🐛 Fix: 타입 수정
yulrang Nov 13, 2024
3c567f3
🐛 Fix: 디폴트이미지 삭제, 데브툴 조건부렌더링
yulrang Nov 13, 2024
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
28 changes: 28 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"@storybook/nextjs": "^8.3.5",
"@storybook/react": "^8.3.5",
"@storybook/testing-library": "^0.2.2",
"@tanstack/react-query-devtools": "^5.59.20",
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

React Query와 DevTools 버전이 호환되지 않습니다

@tanstack/react-query-devtools@^5.59.20은 @tanstack/react-query@^5.59.20을 peer dependency로 요구하지만, 현재 프로젝트는 ^5.59.16을 사용하고 있습니다. 다음 중 하나의 조치가 필요합니다:

  • @tanstack/react-query를 ^5.59.20으로 업그레이드
  • @tanstack/react-query-devtools를 ^5.59.16으로 다운그레이드
🔗 Analysis chain

React Query DevTools 의존성이 적절히 추가되었습니다.

개발 도구로서 devDependencies에 올바르게 배치되었습니다. 다만 몇 가지 고려사항이 있습니다:

  1. 버전 호환성을 확인해주세요
  2. 재현 가능한 빌드를 위해 정확한 버전을 고정하는 것을 고려해보세요

패키지 버전 호환성을 확인하기 위해 다음 스크립트를 실행하세요:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify version compatibility between react-query and its devtools

# Extract and compare versions
QUERY_VERSION=$(cat package.json | jq -r '.dependencies["@tanstack/react-query"]')
DEVTOOLS_VERSION=$(cat package.json | jq -r '.devDependencies["@tanstack/react-query-devtools"]')

echo "React Query version: $QUERY_VERSION"
echo "DevTools version: $DEVTOOLS_VERSION"

# Check npm for compatibility information
npm view @tanstack/react-query-devtools@$DEVTOOLS_VERSION peerDependencies

Length of output: 679

"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/node": "^20",
"@types/react": "^18",
Expand Down
22 changes: 11 additions & 11 deletions src/_apis/crew/get-crew-list.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import { fetchApi } from '@/src/utils/api';
import { MainCrewList, MainCrewListResponse } from '@/src/types/crew-card';
import { ConditionTypes, MainCrewListResponse, PageableTypes } from '@/src/types/crew-card';

export async function getCrewList(condition: ConditionTypes, pageable: PageableTypes) {
const { keyword, mainLocation, mainCategory, subCategory, sortType } = condition;
const { page, size, sort = ['string'] } = pageable;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

sort 매개변수의 기본값이 적절하지 않습니다

sort 매개변수의 기본값으로 ['string']을 사용하는 것은 적절하지 않아 보입니다. 실제 정렬 기준을 반영하는 의미 있는 기본값으로 변경하는 것이 좋습니다.

예시:

- const { page, size, sort = ['string'] } = pageable;
+ const { page, size, sort = ['createdAt,desc'] } = pageable;
📝 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
const { page, size, sort = ['string'] } = pageable;
const { page, size, sort = ['createdAt,desc'] } = pageable;


export async function getCrewList(page: number, limit: number): Promise<MainCrewListResponse> {
try {
const response = await fetchApi<MainCrewListResponse>(
`/crews?_page=${page + 1}&_limit=${limit}`,
`/api/crews/search?keyword=${keyword}&mainLocation=${mainLocation}&mainCategory=${mainCategory}&subCategory=${subCategory}&sortType=${sortType}&page=${page}&size=${size}&sort=${sort}`,
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. URL 인코딩이 누락되어 있어 특수문자나 한글이 포함된 경우 문제가 발생할 수 있습니다.
  2. 템플릿 리터럴을 사용한 직접적인 문자열 결합은 보안상 위험할 수 있습니다.

다음과 같이 수정하는 것을 제안합니다:

-      `/api/crews/search?keyword=${keyword}&mainLocation=${mainLocation}&mainCategory=${mainCategory}&subCategory=${subCategory}&sortType=${sortType}&page=${page}&size=${size}&sort=${sort}`,
+      const params = new URLSearchParams({
+        keyword,
+        mainLocation,
+        mainCategory,
+        subCategory,
+        sortType,
+        page: page.toString(),
+        size: size.toString(),
+        sort: sort.join(','),
+      });
+      `/api/crews/search?${params.toString()}`,

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

{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include', // 인증 정보를 요청에 포함
},
);
if (!Array.isArray(response)) {
throw new Error('서버 응답이 올바른 형식이 아닙니다.');
}
const data = response as MainCrewList[];
const hasNext = data.length === limit;

return { data, hasNext };
return response;
} catch (error) {
throw new Error('크루 리스트를 불러오는데 실패했습니다.');
// eslint-disable-next-line no-console
console.error(error);
return undefined;
Comment on lines +21 to +23
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. 프로덕션 환경에서 console.error 사용
  2. 에러를 숨기고 undefined를 반환하여 문제 진단이 어려움
  3. 호출자에게 적절한 에러 정보를 제공하지 않음
  } catch (error) {
-   // eslint-disable-next-line no-console
-   console.error(error);
-   return undefined;
+   const errorMessage = error instanceof Error 
+     ? error.message 
+     : '크루 목록을 가져오는데 실패했습니다';
+   throw new Error(errorMessage);
  }
📝 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
// eslint-disable-next-line no-console
console.error(error);
return undefined;
const errorMessage = error instanceof Error
? error.message
: '크루 목록을 가져오는데 실패했습니다';
throw new Error(errorMessage);

Comment on lines +22 to +23
Copy link
Contributor

Choose a reason for hiding this comment

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

이 에러처리를 굳이 여기서 하지 않고 페이지에서 한번에 api 유틸에 있는 ApiError를 사용해서 처리해도 좋을거같습니다!

}
}
36 changes: 21 additions & 15 deletions src/_queries/crew-queries.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
import { MainCrewList } from '@/src/types/crew-card';
import { UseInfiniteQueryOptions } from '@tanstack/react-query';
import { ConditionTypes, MainCrewListResponse, PageableTypes } from '@/src/types/crew-card';
import { getCrewList } from '../_apis/crew/get-crew-list';

export function useGetCrewQuery() {
interface QueryParams {
pageParam?: number;
}

interface Page {
hasNext: boolean;
}

export function useGetCrewListQuery(condition: ConditionTypes) {
return {
queryKey: ['crew'],
queryFn: ({ pageParam = 0 }: QueryParams) => getCrewList(pageParam, 3),
getNextPageParam: (lastPage: Page, allPages: Page[]) =>
lastPage.hasNext ? allPages.length + 1 : undefined,
select: (data: MainCrewList[]) => data, // 그대로 반환
queryKey: [
condition.keyword,
condition.mainLocation,
condition.mainCategory,
condition.subCategory,
condition.sortType,
],
Comment on lines +7 to +13
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

queryKey에 undefined 또는 null 값이 포함되지 않도록 처리하세요.

queryKey 배열에 condition 객체의 속성들이 포함되는데, 만약 어떤 속성이 undefined 또는 null인 경우 캐싱 키가 예상대로 작동하지 않을 수 있습니다. 이를 방지하기 위해 기본 값을 지정하거나 falsy한 값을 필터링하는 것이 좋습니다.

예를 들어, undefined나 null 값을 제거하려면 다음과 같이 수정할 수 있습니다:

queryKey: [
  condition.keyword,
  condition.mainLocation,
  condition.mainCategory,
  condition.subCategory,
  condition.sortType,
].filter(Boolean),

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;
},
),
getNextPageParam: (lastPage: MainCrewListResponse, allPages: MainCrewListResponse[]) =>
lastPage.hasNext ? allPages.length : undefined,
};
}
4 changes: 2 additions & 2 deletions src/app/(crew)/my-crew/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import { useState } from 'react';
import { useGetCrewQuery } from '@/src/_queries/crew-queries';
import { useGetCrewListQuery } from '@/src/_queries/crew-queries';
import { useInfiniteScroll } from '@/src/hooks/use-infinite-scroll';
import CrewCardList from '@/src/components/common/crew-list/crew-card-list';
import Tabs from '@/src/components/common/tab';
Expand All @@ -17,7 +17,7 @@ export default function MyCrewPage() {
// TODO: fetchCrewData 함수를 사용하여 데이터를 불러오기 : 파라미터 수정 필요
// TODO: 리스트와는 다른 데이터를 사용해야해서 우선 주석처리 했습니다.
// const { data, ref, isFetchingNextPage } =
// useInfiniteScroll<MyCrewListResponse>(useGetCrewQuery());
// useInfiniteScroll<MyCrewListResponse>(useGetCrewListQuery());

return (
<div className="py-8 md:py-12.5">
Expand Down
101 changes: 22 additions & 79 deletions src/app/(crew)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,83 +1,26 @@
'use client';
import { getCrewList } from '@/src/_apis/crew/get-crew-list';
import FindCrew from '../_components/find-crew/find-crew';

import { useState } from 'react';
import Image from 'next/image';
import { Divider } from '@mantine/core';
import { useGetCrewQuery } from '@/src/_queries/crew-queries';
import regionData from '@/src/data/region.json';
import { useInfiniteScroll } from '@/src/hooks/use-infinite-scroll';
import CategoryContainer from '@/src/app/_components/category/category-container';
import HeroCrew from '@/src/app/_components/hero/hero-crew';
import CrewCardList from '@/src/components/common/crew-list/crew-card-list';
import DropDown from '@/src/components/common/input/drop-down';
import TextInput from '@/src/components/common/input/text-input';
import { MainCrewListResponse } from '@/src/types/crew-card';
import IcoSearch from '@/public/assets/icons/ic-search.svg';

export default function Home() {
const [mainCategory, setMainCategory] = useState('cardio_strength');
const [subCategory, setSubCategory] = useState('running');
const [sort, setSort] = useState<string | null>('latest');
const [region, setRegion] = useState<string | null>('all');
const [search, setSearch] = useState('');
export default async function HomePage() {
const initialData = await getCrewList(
{
keyword: '',
mainLocation: '',
mainCategory: '',
subCategory: '',
sortType: 'LATEST',
},
{
page: 0,
size: 6,
sort: ['LATEST'],
},
);
Comment on lines +5 to +18
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

'getCrewList' 호출에 대한 에러 처리 구현 권장

getCrewList 호출 시 발생할 수 있는 에러를 처리하기 위해 try-catch 블록을 추가하는 것을 권장합니다. 이는 애플리케이션의 안정성을 높여줍니다.

코드 수정 예시:

 export default async function HomePage() {
+  let initialData;
+  try {
     initialData = await getCrewList(
       {
         keyword: '',
         mainLocation: '',
         mainCategory: '',
         subCategory: '',
         sortType: 'LATEST',
       },
       {
         page: 0,
         size: 6,
         sort: ['LATEST'],
       },
     );
+  } catch (error) {
+    // 에러 처리 로직 추가
+    console.error('Crew 리스트를 가져오는 중 에러 발생:', error);
+    initialData = null;
+  }

   const infiniteData = {
     pages: [initialData],
     pageParams: [],
   };

   return <FindCrew initialData={infiniteData} />;
 }

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


const { data, ref, isFetchingNextPage } =
useInfiniteScroll<MainCrewListResponse>(useGetCrewQuery());
const infiniteData = {
pages: [initialData],
pageParams: [],
};

return (
<div className="py-8 md:py-12.5">
<div className="flex flex-col px-3 md:px-8 lg:px-11.5">
<HeroCrew />
<CategoryContainer
mainCategory={mainCategory}
subCategory={subCategory}
setMainCategory={setMainCategory}
setSubCategory={setSubCategory}
/>
</div>
<Divider mx={{ base: 0, md: 32, lg: 34 }} my={24} size={2} color="#E5E7EB" />
<div className="px-3 md:px-8 lg:px-11.5">
<div className="flex flex-col justify-between gap-2 md:flex-row md:gap-4">
<div className="flex-1">
<TextInput
value={search}
onChange={(e) => setSearch(e.target.value)}
leftSectionPointerEvents="none"
leftSection={
<Image src={IcoSearch} alt="search" width={21} height={21} className="-mr-4" />
}
placeholder="크루 이름, 위치를 검색하세요."
inputClassNames="w-full h-11 pl-12 placeholder:text-gray-500 font-pretendard text-base font-medium text-gray-800 rounded-xl"
/>
</div>
<div className="flex-0 flex justify-between gap-2 md:basis-67 md:gap-4">
<DropDown
name="region"
variant="default"
data={regionData.map((dataItem) => dataItem.main)}
placeholder="전체"
value={region}
className="w-[130px]"
onChange={setRegion}
/>
<DropDown
name="sort"
variant="sort"
data={[
{ value: 'latest', label: '최신순' },
{ value: 'best', label: '인기순' },
]}
placeholder="최신순"
value={sort}
className="w-[130px]"
onChange={setSort}
/>
</div>
</div>
</div>
<div className="mt-8 px-3 md:px-8 lg:px-11.5">
<CrewCardList data={data} ref={ref} isFetchingNextPage={isFetchingNextPage} />
</div>
</div>
);
return <FindCrew initialData={infiniteData} />;
}
30 changes: 11 additions & 19 deletions src/app/_components/category/category-container/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
'use client';

import { useEffect, useState } from 'react';
import { useState } from 'react';
import category from '@/src/data/category.json';
import InternalCategory from '@/src/app/_components/category/internal-category';
import MainCategory from '@/src/app/_components/category/main-category';
Expand All @@ -18,32 +16,26 @@ export default function CategoryContainer({
setMainCategory,
setSubCategory,
}: CategoryContainerProps) {
const [categoryIndex, setCategoryIndex] = useState(0);

useEffect(() => {
if (subCategory !== category[categoryIndex].items[0].value) {
setSubCategory(category[categoryIndex].items[0].value);
}
}, [mainCategory, categoryIndex]);

useEffect(() => {
if (mainCategory !== category[categoryIndex].title.value) {
setMainCategory(category[categoryIndex].title.value);
}
}, [subCategory]);
const [categoryIndex, setCategoryIndex] = useState<number | null>(null);

return (
<div className="flex flex-col gap-2 md:gap-4">
<MainCategory
value={mainCategory}
category={category}
onChange={setMainCategory}
onChange={(newValue) => {
setMainCategory(newValue);
setSubCategory('');
}}
onHover={setCategoryIndex}
/>
<InternalCategory
value={subCategory}
category={category[categoryIndex].items}
onChange={setSubCategory}
category={category[categoryIndex ?? 0].items}
onChange={(newValue) => {
setSubCategory(newValue);
setMainCategory(category[categoryIndex ?? 0].title.label);
}}
/>
</div>
);
Expand Down
4 changes: 2 additions & 2 deletions src/app/_components/category/internal-category/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ export default function InternalCategory({ value, category, onChange }: Internal
<li key={item.label} className="snap-align-start flex">
<Button
type="button"
onClick={() => onChange(item.value)}
className={`${pathname?.includes(item.value ?? value) && 'bg-gray-900 text-white'} h-10 min-w-28 items-center justify-center text-nowrap rounded-xl bg-gray-100 px-5 py-2 text-center text-sm font-bold text-gray-400 transition-colors hover:bg-gray-900 hover:text-white md:h-11 md:text-lg`}
onClick={() => onChange(item.label)}
className={`${pathname?.includes(item.label ?? value) && 'bg-gray-900 text-white'} h-10 min-w-28 items-center justify-center text-nowrap rounded-xl bg-gray-100 px-5 py-2 text-center text-sm font-bold text-gray-400 transition-colors hover:bg-gray-900 hover:text-white md:h-11 md:text-lg`}
>
{item.label}
</Button>
Expand Down
4 changes: 2 additions & 2 deletions src/app/_components/category/main-category/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ export default function MainCategory({ value, category, onHover, onChange }: Mai
>
<Button
onMouseEnter={() => handleHover(index)}
onClick={() => onChange(item.title.value)}
className={`${pathname?.includes(item.title.value ?? value) ? 'bg-blue-500' : ''} ${activeIndex === index ? 'text-blue-500' : 'text-gray-600'} flex flex-col items-center text-base font-semibold md:flex-row md:text-lg lg:text-xl lg:font-semibold`}
onClick={() => onChange(item.title.label)}
className={`${pathname?.includes(item.title.label ?? value) ? 'bg-blue-500' : ''} ${activeIndex === index ? 'text-blue-500' : 'text-gray-600'} flex flex-col items-center text-base font-semibold md:flex-row md:text-lg lg:text-xl lg:font-semibold`}
>
<h3 className="flex items-center gap-1 lg:gap-2">
<span className="flex">{item.title.label}</span>
Expand Down
Loading