Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,5 @@ jobs:

- name: Build test
run: npm run build
env:
NEXT_PUBLIC_API_BASE_URL: ${{secrets.NEXT_PUBLIC_API_BASE_URL}}
9 changes: 9 additions & 0 deletions next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,14 @@ const nextConfig = {
},
];
},

async rewrites() {
return [
{
source: '/api/:path*',
destination: `${process.env.NEXT_PUBLIC_API_BASE_URL}/:path*`,
},
];
},
};
export default nextConfig;
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
Binary file added public/assets/images/img-default.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 11 additions & 10 deletions src/_apis/crew/get-crew-list.ts
Original file line number Diff line number Diff line change
@@ -1,24 +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,
): Promise<MainCrewListResponse> {
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}`,
`/crews/search?keyword=${keyword}&mainLocation=${mainLocation}&mainCategory=${mainCategory}&subCategory=${subCategory}&sortType=${sortType}&page=${page}&size=${size}&sort=${sort}`,
Copy link

@coderabbitai coderabbitai bot Nov 12, 2024

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

쿼리 파라미터의 URL 인코딩 필요

쿼리 파라미터를 직접 문자열로 결합하면 특수 문자나 예기치 않은 입력으로 인해 오류가 발생할 수 있습니다. URLSearchParams를 사용하여 파라미터를 적절히 인코딩하는 것을 권장합니다.

수정 제안:

+    const params = new URLSearchParams({
+      keyword,
+      mainLocation,
+      mainCategory,
+      subCategory,
+      sortType,
+      page: page.toString(),
+      size: size.toString(),
+      sort: sort.join(','),
+    });
+    const response = await fetchApi<CrewCardInformResponse>(
+      `/crews/search?${params.toString()}`,
       {
         method: 'GET',
         headers: {
           'Content-Type': 'application/json',
         },
         credentials: 'include', // 인증 정보를 요청에 포함
       },
     );

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

Copy link
Contributor

Choose a reason for hiding this comment

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

params.toString 사용해서 코드 수정하면 좋을 것 같습니다!

Copy link

Choose a reason for hiding this comment

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

@minkyung5x5, 네, params.toString()을 사용하여 코드를 수정하면 좋을 것 같습니다! 수정해 주셔서 감사합니다.


If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!

{
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('크루 리스트를 불러오는데 실패했습니다.');
}
Expand Down
28 changes: 13 additions & 15 deletions src/_queries/crew-queries.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
import { MainCrewList } from '@/src/types/crew-card';
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] }),
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