Skip to content

Conversation

@yulrang
Copy link
Contributor

@yulrang yulrang commented Nov 15, 2024

🔖 Issue Ticket

#128

✍️ Description

크루 수정하기 API 연결했습니다~

  • app 폴더 내에 컴포넌트 파일들 이동했습니다
  • API response 형태가 json이 아니어서 성공시에도 에러메시지가 뜨는 이슈가 있었습니다~ 고맙게도 백엔드에서 처리를 해주셨습니다!
  • fetchApi 유틸함수에서 에러메시지 표시되도록 살짝 수정했습니다~!
  • 수정할 때 샘플 이미지일 경우 샘플이미지가 선택되도록 했고, 업로드된 이미지 일 경우 프리뷰에 뜨도록 했습니다.
  • 에러케이스 발견시 말씀해주시면 감사하겠습니다!!! 🙏
  • 크루 만들기에 로컬스토리지 중간저장 기능 추가
  • 중간저장 위해 이미지 업로드 API 연결지점 기존 폼 제출 시점에서 파일 업로드 시점으로 변경

✅ Checklist

PR

  • Branch Convention 확인

    feat/ 피쳐, fix/ 버그 수정, refactor/ 개선

  • Base Branch 확인
  • 적절한 Label 지정
  • Assignee 및 Reviewer 지정

Test

  • 로컬 작동 확인

Screenshot 2024-11-15 at 17 08 29

Summary by CodeRabbit

릴리스 노트

  • 신규 기능

    • 기존 크루 관리 기능에 크루 생성 및 편집 기능 추가.
    • 사용자가 크루를 생성하고 수정할 수 있는 새로운 훅(useCreateCrewQuery, useEditCrewQuery) 추가.
    • 크루 상세 페이지에서 이미지 업로드 및 로딩 상태 관리 기능 향상.
    • CreateCrewForm 컴포넌트에 생성 및 편집 모드 구분 기능 추가.
    • DropDown 컴포넌트의 스타일 및 상태 관리 로직 개선.
    • SpeedInsights 컴포넌트를 통해 성능 모니터링 기능 추가.
  • 버그 수정

    • 로딩 및 오류 처리 로직 개선으로 사용자 경험 향상.
  • 문서화

    • 새로운 인터페이스(EditCrewResponseTypes, EditCrewRequestTypes) 추가로 데이터 구조 명확화.
  • 리팩토링

    • 여러 컴포넌트에서 상태 관리 및 렌더링 로직 간소화.

@coderabbitai
Copy link

coderabbitai bot commented Nov 15, 2024

Walkthrough

이 변경 사항은 crew 관리 기능을 향상시키기 위한 여러 파일의 수정으로 구성됩니다. createCreweditCrew 기능이 추가되어 크루 엔티티의 생성 및 수정을 지원하며, 관련 API 훅인 useCreateCrewQueryuseEditCrewQuery가 도입되었습니다. 또한, 여러 컴포넌트에서 상태 관리 및 오류 처리 로직이 개선되어 사용자 경험을 향상시키고 있습니다.

Changes

파일 경로 변경 요약
src/_apis/crew/crew.ts editCrew 함수 추가, createCrew 함수 내부 로직 수정
src/_queries/crew/crew-detail-queries.ts useCreateCrewQuery, useEditCrewQuery 훅 추가
src/app/(crew)/crew/create/page.tsx useCreateCrewQuery 훅 사용으로 변경, 제출 처리 로직 수정
src/app/(crew)/crew/detail/[id]/edit/page.tsx useEditCrewQuery 훅 사용 추가, 이미지 업로드 처리 로직 수정
src/app/(crew)/my-crew/hosted/page.tsx 상태 관리 개선: isLoading, error 사용
src/app/(crew)/my-crew/joined/page.tsx 상태 관리 개선: isLoading, error 사용
src/app/(crew)/page.tsx 상태 관리 개선: isLoading, error 사용
src/components/common/crew-list/crew-card-list.stories.tsx 상태 관리 개선: isLoading, error 사용
src/components/common/input/file-input-wrap/file-input/file-input.stories.tsx fileValue 상태 타입 수정
src/components/common/input/file-input-wrap/file-input/index.tsx FileInputProps 인터페이스 수정, 샘플 이미지 처리 로직 추가
src/hooks/use-infinite-scroll.ts 반환 값에서 status 제거, isLoading, error 추가
src/types/create-crew.d.ts EditCrewResponseTypes, EditCrewRequestTypes 인터페이스 추가
src/types/crew-card.d.ts CrewDetail 인터페이스에 선택적 속성 추가
src/utils/api.ts ApiError 클래스 및 fetchApi 함수의 오류 처리 로직 수정
src/data/region.json "지역 전체" 항목 제거 및 여러 지역에 "전체" 항목 추가
src/app/(crew)/_components/category/category-container/index.tsx 컴포넌트의 import 경로 수정
src/app/(crew)/crew/create/_components/create-crew-form/index.tsx CreateCrewFormProps 인터페이스 수정
src/app/(crew)/crew/detail/[id]/_components/create-gathering/create-gathering-modal/presenter.tsx CreateGatheringForm 컴포넌트의 import 경로 수정
src/app/(crew)/crew/detail/[id]/_components/create-gathering/index.tsx CreateGatheringModalContainer 컴포넌트의 import 경로 수정
src/components/common/gathering-card/container.tsx GatheringDetailModalContainer 컴포넌트의 import 경로 수정
src/components/common/gathering-card/scheduled-gathering-card/container.tsx GatheringDetailModalContainer 컴포넌트의 import 경로 수정 및 dummyModalData 확장
package.json "react-hook-form-persist": "^3.0.0" 의존성 추가
src/app/(crew)/crew/create/_components/create-crew-form/create-crew-form.stories.tsx CreateCrewForm 스토리에서 type prop 추가

Possibly related PRs

Suggested labels

fix, refactor

Suggested reviewers

  • HaeJungg
  • minkyung5x5
  • Rangbyeolang

🐰 변화의 순간, 우리 크루가 더 강해져요,
편집과 생성, 모든 걸 다루며,
오류는 사라지고, 행복만 남아,
함께하는 여정, 기쁨이 넘쳐요!
🥕✨

Warning

There were issues while running some tools. Please review the errors and either fix the tool’s configuration or disable the tool if it’s a critical failure.

🔧 eslint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/app/layout.tsx

Oops! Something went wrong! :(

ESLint: 8.57.1

ESLint couldn't determine the plugin "react-hooks" uniquely.

  • /node_modules/eslint-plugin-react-hooks/index.js (loaded in ".eslintrc.js")
  • /node_modules/eslint-config-next/node_modules/eslint-plugin-react-hooks/index.js (loaded in ".eslintrc.js » eslint-config-next/core-web-vitals » /node_modules/eslint-config-next/index.js » plugin:react-hooks/recommended")

Please remove the "plugins" setting from either config or remove either plugin installation.

If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 5313fd4 and be51721.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • package.json (1 hunks)
  • src/app/layout.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json
🔇 Additional comments (2)
src/app/layout.tsx (2)

4-4: Vercel SpeedInsights 추가 승인

성능 모니터링을 위한 SpeedInsights 통합이 적절하게 구현되었습니다.


27-30: 컴포넌트 구조 검토 완료

MantineProvider 내부의 컴포넌트 구조가 올바르게 구성되어 있으며, SpeedInsights가 적절한 위치에 배치되었습니다.

다만, 성능 모니터링 도구의 영향을 확인하는 것이 좋습니다.

✅ Verification successful

SpeedInsights 구성이 올바르게 되어 있음을 확인했습니다

SpeedInsights가 다음과 같이 적절하게 구성되어 있습니다:

  • @vercel/speed-insights 패키지가 의존성에 정상적으로 등록됨 (v1.1.0)
  • layout.tsx에서 올바른 위치에 배치됨
  • 다른 성능 모니터링 도구와의 충돌 없음
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify SpeedInsights configuration and impact

# Check if SpeedInsights is properly configured in package.json
jq '.dependencies["@vercel/speed-insights"]' package.json

# Look for any existing performance monitoring configurations
fd -t f "speed|performance|monitoring" -E "node_modules" --exec cat {} \;

Length of output: 145


Script:

#!/bin/bash
# Check for any performance related imports and configurations
rg -l "@vercel/speed-insights" -g "!node_modules"

# Check layout.tsx for any performance related configurations
rg "SpeedInsights" -A 5 -B 5 src/app/layout.tsx

# Look for any other performance monitoring tools
rg -l "performance|monitoring|analytics" -g "!node_modules" -g "!*.md"

Length of output: 865


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@yulrang yulrang linked an issue Nov 15, 2024 that may be closed by this pull request
@github-actions
Copy link

@github-actions
Copy link

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Outside diff range and nitpick comments (24)
src/hooks/use-infinite-scroll.ts (1)

29-29: 반환값 일관성 개선

훅의 반환값이 destructuring된 값과 일치하도록 업데이트되어 타입 안정성과 사용성이 향상되었습니다. 이는 이 훅을 사용하는 컴포넌트들에서 더 예측 가능한 에러 처리가 가능하게 합니다.

다음과 같이 타입 정의를 추가하면 더 좋을 것 같습니다:

type InfiniteScrollReturn<TData> = {
  data: InfiniteData<TData> | undefined;
  error: Error | null;
  isLoading: boolean;
  ref: (node?: Element | null) => void;
  isFetchingNextPage: boolean;
  refetch: () => Promise<void>;
};
src/app/(crew)/my-crew/hosted/page.tsx (2)

9-13: 상태 관리 개선이 잘 이루어졌습니다!

status 대신 isLoadingerror를 사용하는 방식으로 변경한 것이 좋습니다. 상태를 더 명확하게 표현할 수 있게 되었습니다.

타입 안전성을 위해 다음과 같은 개선을 제안드립니다:

+type InfiniteScrollReturn = {
+  data: InfiniteData<CrewListResponse> | undefined;
+  isLoading: boolean;
+  error: Error | null;
+  ref: RefCallback<HTMLDivElement>;
+  isFetchingNextPage: boolean;
+};

-const { data, isLoading, error, ref, isFetchingNextPage } = useInfiniteScroll(
+const { data, isLoading, error, ref, isFetchingNextPage }: InfiniteScrollReturn = useInfiniteScroll(

17-24: 에러 처리를 개선하면 좋겠습니다.

로딩 상태 처리는 잘 되어 있으나, 에러 메시지와 처리 방식을 개선할 수 있습니다.

다음과 같은 개선을 제안드립니다:

-      {error && <p className="py-10 text-center">에러가 발생했습니다.</p>}
+      {error && (
+        <div className="flex flex-col items-center gap-2 py-10">
+          <p className="text-center text-red-500">
+            {error instanceof Error ? error.message : '데이터를 불러오는 중 문제가 발생했습니다.'}
+          </p>
+          <button
+            onClick={() => window.location.reload()}
+            className="mt-2 rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
+          >
+            다시 시도하기
+          </button>
+        </div>
+      )}
src/app/(crew)/my-crew/joined/page.tsx (1)

17-23: 로딩 상태에 대한 접근성 개선이 필요합니다.

로딩 인디케이터에 스크린 리더를 위한 aria 레이블을 추가하면 좋을 것 같습니다.

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

 {isLoading || isFetchingNextPage ? (
   <div className="flex justify-center py-10">
-    <Loader size="sm" />
+    <Loader size="sm" aria-label="콘텐츠를 불러오는 중입니다" />
   </div>
 ) : (
   <div ref={ref} className="h-[1px]" />
 )}
src/types/create-crew.d.ts (1)

38-38: 타입 정의 단순화를 제안드립니다

EditCrewRequestTypesCreateCrewRequestTypes를 그대로 확장만 하고 있습니다. 추가 속성이 없다면 다음과 같이 타입 별칭을 사용하는 것이 더 명확할 수 있습니다:

-export interface EditCrewRequestTypes extends CreateCrewRequestTypes {}
+export type EditCrewRequestTypes = CreateCrewRequestTypes;
src/_apis/crew/crew.ts (2)

11-18: 에러 처리를 더 구체적으로 개선하면 좋을 것 같습니다.

현재 구현은 기본적인 기능은 잘 동작하지만, 다음과 같은 개선사항을 고려해보시면 좋을 것 같습니다:

  • API 응답 상태에 따른 구체적인 에러 메시지 처리
  • 네트워크 오류와 서버 오류의 구분

다음과 같이 수정해보는 것은 어떨까요?:

 export async function createCrew(data: CreateCrewRequestTypes) {
   try {
     const response: { data: CreateCrewResponseTypes } = await fetchApi(`/api/crews`, {
       method: 'POST',
       headers: {
         'Content-Type': 'application/json',
       },
       credentials: 'include',
       body: JSON.stringify(data),
     });
     if (!response.data) {
-      throw new Error('Failed to create crew: No data received');
+      throw new Error('크루 생성 실패: 서버로부터 데이터를 받지 못했습니다');
     }
     return response.data;
   } catch (error) {
-    throw error;
+    if (error instanceof TypeError) {
+      throw new Error('크루 생성 실패: 네트워크 오류가 발생했습니다');
+    }
+    throw new Error(`크루 생성 실패: ${error.message}`);
   }
 }

Line range hint 9-42: 코드 재사용성 개선을 위한 제안

createCreweditCrew 함수가 유사한 에러 처리와 응답 처리 로직을 공유하고 있습니다. 이를 공통 유틸리티 함수로 추출하면 코드 중복을 줄이고 일관성을 향상시킬 수 있습니다.

다음과 같은 유틸리티 함수 생성을 고려해보세요:

async function handleCrewOperation<T, R>(
  operation: () => Promise<{ data: R }>,
  errorPrefix: string
): Promise<R> {
  try {
    const response = await operation();
    if (!response.data) {
      throw new Error(`${errorPrefix}: 서버로부터 데이터를 받지 못했습니다`);
    }
    return response.data;
  } catch (error) {
    if (error instanceof TypeError) {
      throw new Error(`${errorPrefix}: 네트워크 오류가 발생했습니다`);
    }
    throw new Error(`${errorPrefix}: ${error.message}`);
  }
}
🧰 Tools
🪛 Biome

[error] 25-25: The catch clause that only rethrows the original error is useless.

An unnecessary catch clause can be confusing.
Unsafe fix: Remove the try/catch clause.

(lint/complexity/noUselessCatch)


[error] 40-40: The catch clause that only rethrows the original error is useless.

An unnecessary catch clause can be confusing.
Unsafe fix: Remove the try/catch clause.

(lint/complexity/noUselessCatch)

src/types/crew-card.d.ts (1)

45-46: 선택적 필드 추가가 잘 되었습니다!

인터페이스에 추가된 선택적 필드들이 적절하게 구현되었습니다. 하지만 각 필드의 용도와 제약사항에 대한 JSDoc 문서를 추가하면 더 좋을 것 같습니다.

다음과 같이 문서화를 추가하는 것을 제안합니다:

 export interface CrewDetail {
   id: number;
   title: string;
+  /** 크루의 주요 카테고리 */
   mainCategory?: string;
+  /** 크루의 하위 카테고리 */
   subCategory?: string;
   mainLocation: string;
   subLocation: string;
   participantCount: number;
   totalCount: number;
   imageUrl: string;
   totalGatheringCount: number;
   crewMembers: CrewMember[];
   confirmed: boolean;
+  /** 크루 소개 텍스트 */
   introduce?: string;
 }

Also applies to: 55-55

src/components/common/crew-list/crew-card-list.stories.tsx (1)

55-55: 에러 처리가 개선되었습니다만, 사용자 경험을 위한 개선이 필요합니다.

에러 메시지가 너무 단순합니다. 사용자에게 더 구체적인 에러 메시지와 함께 가능한 해결 방법을 제시하는 것이 좋겠습니다.

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

-      {error && <p className="py-10 text-center">에러가 발생했습니다.</p>}
+      {error && (
+        <div className="py-10 text-center">
+          <p className="text-red-500 font-medium">데이터를 불러오는 중 문제가 발생했습니다.</p>
+          <p className="text-sm text-gray-600 mt-2">잠시 후 다시 시도해 주세요.</p>
+          <button
+            onClick={() => window.location.reload()}
+            className="mt-4 px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600"
+          >
+            새로고침
+          </button>
+        </div>
+      )}
src/utils/api.ts (1)

60-62: 오류 메시지를 더 구체적으로 개선하면 좋을 것 같습니다.

현재 구현된 일반적인 오류 메시지를 더 구체적으로 개선하여 디버깅을 용이하게 할 수 있습니다.

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

-      throw new ApiError(0, error.message || 'An unexpected error occurred');
+      throw new ApiError(0, `예상치 못한 오류가 발생했습니다: ${error.message || '알 수 없는 오류'}`);
-    throw new ApiError(0, 'Unknown error occurred');
+    throw new ApiError(0, '알 수 없는 오류가 발생했습니다. 개발자에게 문의해주세요.');
src/app/(crew)/crew/create/page.tsx (1)

32-39: 타입 안전성 개선을 제안드립니다.

newData 객체 생성 시 타입 안전성을 높일 수 있습니다. 옵셔널 체이닝과 널 병합 연산자의 사용을 좀 더 명시적으로 만들어보세요.

 const newData: CreateCrewRequestTypes = {
   title: createdData.title,
   mainCategory: createdData.mainCategory,
-  subCategory: createdData.subCategory ?? '',
+  subCategory: createdData.subCategory || '',  // 의도를 더 명확하게 표현
   imageUrl: newImageUrl ?? '',
   mainLocation: createdData.mainLocation,
-  subLocation: createdData.subLocation ?? '',
+  subLocation: createdData.subLocation || '',  // 의도를 더 명확하게 표현
   totalCount: createdData.totalCount,
   introduce: createdData.introduce,
 };
src/components/common/input/file-input-wrap/file-input/index.tsx (1)

14-19: isSample 함수의 성능을 개선할 수 있습니다.

함수가 정상적으로 동작하지만, 다음과 같이 최적화할 수 있습니다:

 const isSample = (value: File | string | null) => {
-  if (typeof value === 'string') {
-    return !!(ImgCrewSampleUrls.includes(value) || ImgGatheringSampleUrls.includes(value));
-  }
-  return false;
+  return typeof value === 'string' && 
+    (ImgCrewSampleUrls.includes(value) || ImgGatheringSampleUrls.includes(value));
 };

이렇게 수정하면:

  • 불필요한 이중 부정(!!)을 제거
  • 코드가 더 간결하고 가독성이 향상
  • 동일한 기능을 유지하면서 더 효율적인 구조
src/app/(crew)/page.tsx (1)

136-143: 로딩 및 에러 상태 처리가 개선되었습니다.

로딩 상태와 에러 처리가 더 직관적으로 구현되었습니다. 다만 몇 가지 제안사항이 있습니다:

  1. 에러 메시지를 더 구체적으로 표시하면 좋을 것 같습니다.
  2. 로딩 상태일 때 접근성을 위한 aria-label 추가를 고려해보세요.

다음과 같이 개선해보시는 건 어떨까요?:

 {isLoading || isFetchingNextPage ? (
-  <div className="flex justify-center py-10">
+  <div className="flex justify-center py-10" role="status" aria-label="데이터를 불러오는 중입니다">
     <Loader size="sm" />
   </div>
 ) : (
   <div ref={ref} className="h-[1px]" />
 )}
-{error && <p className="py-10 text-center">에러가 발생했습니다.</p>}
+{error && (
+  <p className="py-10 text-center text-red-500">
+    {error instanceof Error ? error.message : '데이터를 불러오는 중 문제가 발생했습니다. 다시 시도해 주세요.'}
+  </p>
+)}
src/app/(crew)/crew/_components/create-crew-form/index.tsx (3)

125-125: placeholder 로직 개선이 필요합니다.

현재 구현은 작동하지만, 다음과 같은 개선사항을 제안드립니다:

  1. 반복되는 조건부 로직을 하나의 유틸리티 함수로 추출
  2. null 체크 로직을 일관성있게 적용

다음과 같이 리팩토링하는 것을 추천드립니다:

const getPlaceholder = (isEdit: boolean, value: string | null, defaultText: string) => 
  isEdit && value ? value : defaultText;

// 사용 예시:
placeholder={getPlaceholder(isEdit, field.value, '메인 카테고리')}

Also applies to: 144-144, 207-207, 227-227


273-273: 문자열 길이 계산 로직 보완이 필요합니다.

옵셔널 체이닝을 사용한 것은 좋으나, 다음과 같은 보완이 필요해 보입니다:

  1. 기본값 처리
  2. 공백 문자 처리

다음과 같이 개선하는 것을 추천드립니다:

<span className="text-blue-500">
  {(introduce?.trim() || '').length}
</span>

Line range hint 1-305: 컴포넌트 구조 개선을 제안드립니다.

현재 컴포넌트가 너무 많은 책임을 가지고 있어 다음과 같은 구조적 개선을 제안드립니다:

  1. 폼 검증 로직을 별도의 훅으로 분리
  2. 에러 처리 로직을 중앙화
  3. 파일 업로드 로직을 별도의 컴포넌트로 분리

이를 통해 코드의 재사용성과 유지보수성을 높일 수 있습니다.

다음과 같은 구조로 리팩토링하는 것을 추천드립니다:

// hooks/useCrewFormValidation.ts
export const useCrewFormValidation = () => {
  // 폼 검증 로직
};

// components/CrewImageUpload.tsx
export const CrewImageUpload = () => {
  // 이미지 업로드 로직
};

// utils/errorHandler.ts
export const handleFormError = () => {
  // 중앙화된 에러 처리 로직
};
src/_queries/crew/crew-detail-queries.ts (5)

30-31: 에러 객체의 타입 안전성 보장

onError에서 error.message를 사용하고 있는데, error 객체의 구조가 항상 동일하지 않을 수 있습니다. 에러 메시지를 안전하게 출력하기 위해 에러 객체의 타입을 확인하거나 기본 메시지를 제공하는 것이 좋습니다.

수정 제안:

-onError: (error) => {
-  toast.error(error.message);
+onError: (error: any) => {
+  const message = error instanceof Error ? error.message : '오류가 발생했습니다.';
+  toast.error(message);
 },

49-50: 에러 객체의 타입 안전성 보장

onError에서 error.message를 참조하고 있는데, error 객체의 구조가 항상 동일하지 않을 수 있습니다. 에러 메시지를 안전하게 처리하기 위해 타입 가드나 기본 메시지를 사용하는 것이 좋습니다.

수정 제안:

-onError: (error) => {
-  toast.error(error.message);
+onError: (error: any) => {
+  const message = error instanceof Error ? error.message : '오류가 발생했습니다.';
+  toast.error(message);
 },

44-46: router 체크의 불필요성 제거

useRouter 훅은 항상 유효한 router 객체를 반환하므로 if (router) 조건문은 불필요합니다. useCreateCrewQuery와 일관성을 유지하기 위해 조건문을 제거하는 것이 좋습니다.

수정 제안:

- if (router) {
-   router.push(`/crew/detail/${id}`);
- }
+ router.push(`/crew/detail/${id}`);

51-51: retry 옵션의 일관성 유지

useEditCrewQuery에서 retry: false로 설정되어 있지만, useCreateCrewQuery에서는 retry 옵션이 설정되어 있지 않습니다. 두 함수에서 retry 옵션에 대해 일관된 설정을 하는 것이 좋습니다.


15-33: 중복된 로직을 재사용하여 코드 간결화

useCreateCrewQueryuseEditCrewQuery 함수에서 유사한 로직이 반복되고 있습니다. 공통 부분을 추출하여 재사용 가능한 훅이나 함수로 분리하면 코드의 유지보수성이 향상됩니다.

Also applies to: 35-53

src/app/(crew)/crew/detail/[id]/edit/page.tsx (3)

39-44: 로딩 상태 처리 로직 개선 제안

현재 isLoading 또는 isPendingtrue일 때 로더를 표시하고 있습니다. 그러나 isPending은 편집 요청에 대한 상태이며, 이 경우에도 data는 이미 로드되어 있습니다. 로딩 상태와 편집 상태를 분리하여 처리하면 사용자 경험을 향상시킬 수 있습니다.

예시 수정안:

if (isLoading)
  return (
    <div className="fixed inset-0 flex items-center justify-center">
      <Loader size="sm" />
    </div>
  );

if (isPending)
  // 편집 중임을 나타내는 UI를 표시하거나, 버튼을 비활성화하는 등 처리

58-58: CreateCrewForm에 전달되는 데이터 타입 확인 필요

data as EditCrewRequestTypes로 타입 단언을 사용하고 있습니다. 타입 안전성을 위해 가능하면 타입 단언을 지양하고, data의 타입을 정확하게 정의하거나 필요한 경우 데이터를 변환하는 것이 좋습니다.


15-15: 사용하지 않는 변수 제거 권장

const { isPending, isSuccess, mutate } = useEditCrewQuery(Number(id));에서 isSuccess 변수를 선언하고 있으나 사용하고 있지 않습니다. 불필요한 변수는 제거하여 코드의 가독성을 높일 수 있습니다.

- const { isPending, isSuccess, mutate } = useEditCrewQuery(Number(id));
+ const { isPending, mutate } = useEditCrewQuery(Number(id));
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between ab16aad and 8cc9bd6.

📒 Files selected for processing (15)
  • src/_apis/crew/crew.ts (2 hunks)
  • src/_queries/crew/crew-detail-queries.ts (1 hunks)
  • src/app/(crew)/crew/_components/create-crew-form/index.tsx (6 hunks)
  • src/app/(crew)/crew/create/page.tsx (2 hunks)
  • src/app/(crew)/crew/detail/[id]/edit/page.tsx (2 hunks)
  • src/app/(crew)/my-crew/hosted/page.tsx (1 hunks)
  • src/app/(crew)/my-crew/joined/page.tsx (1 hunks)
  • src/app/(crew)/page.tsx (2 hunks)
  • src/components/common/crew-list/crew-card-list.stories.tsx (2 hunks)
  • src/components/common/input/file-input-wrap/file-input/file-input.stories.tsx (2 hunks)
  • src/components/common/input/file-input-wrap/file-input/index.tsx (1 hunks)
  • src/hooks/use-infinite-scroll.ts (2 hunks)
  • src/types/create-crew.d.ts (2 hunks)
  • src/types/crew-card.d.ts (2 hunks)
  • src/utils/api.ts (1 hunks)
🧰 Additional context used
🪛 Biome
src/_apis/crew/crew.ts

[error] 40-40: The catch clause that only rethrows the original error is useless.

An unnecessary catch clause can be confusing.
Unsafe fix: Remove the try/catch clause.

(lint/complexity/noUselessCatch)

🔇 Additional comments (15)
src/hooks/use-infinite-scroll.ts (1)

13-13: useInfiniteQuery의 반환값 구조 개선

status 대신 isLoadingerror를 사용하도록 변경한 것이 더 명확한 상태 관리를 가능하게 합니다. 이는 컴포넌트에서 로딩 상태와 에러 상태를 더 직관적으로 처리할 수 있게 해줍니다.

이 변경으로 인한 영향을 확인하기 위해 다음 스크립트를 실행하세요:

✅ Verification successful

Let me gather more information about the implementation details to ensure there are no remaining status usages.


useInfiniteQuery 반환값 변경이 안전하게 적용되었습니다

모든 사용처에서 status 대신 isLoadingerror를 올바르게 사용하고 있으며, 다음 컴포넌트들이 이미 새로운 API를 적용하고 있습니다:

  • src/app/(crew)/my-crew/joined/page.tsx
  • src/app/(crew)/my-crew/hosted/page.tsx
  • src/app/(crew)/page.tsx
  • src/components/common/crew-list/crew-card-list.stories.tsx
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: useInfiniteScroll 훅을 사용하는 컴포넌트들을 검색하여 영향도 확인

# 기존 status 사용 여부 확인
rg "status.*useInfiniteScroll" 

# 새로운 isLoading, error 사용 패턴 확인
rg "(isLoading|error).*useInfiniteScroll"

Length of output: 561


Script:

#!/bin/bash
# Check the implementation of useInfiniteScroll hook
cat src/hooks/use-infinite-scroll.ts

# Find any remaining status references in components
rg "status.*=.*useInfiniteScroll"

Length of output: 883

src/app/(crew)/my-crew/joined/page.tsx (1)

9-13: 상태 관리 개선이 잘 이루어졌습니다!

status 대신 isLoadingerror를 분리하여 사용함으로써 상태 관리가 더 명확해졌습니다. 페이지네이션 파라미터도 적절하게 설정되어 있습니다.

src/_apis/crew/crew.ts (1)

2-7: 타입 정의가 잘 구성되어 있습니다!

크루 생성과 수정에 필요한 모든 타입들이 명확하게 정의되어 있습니다.

src/types/crew-card.d.ts (1)

45-46: 관련 인터페이스들과의 일관성 확인이 필요합니다.

새로 추가된 필드들이 MainCrewList, MyCrewList 등 관련 인터페이스들과 일관성이 있는지 확인이 필요합니다.

다음 스크립트로 관련 인터페이스들을 확인해보겠습니다:

Also applies to: 55-55

✅ Verification successful

These commands will help us verify the consistency of the newly added fields with related interfaces. Let's analyze the results to provide a proper verification.


mainCategorysubCategory 필드는 관련 인터페이스들과 일관성이 있습니다.

검증 결과, 새로 추가된 필드들은 다음 인터페이스들에서 동일한 방식으로 사용되고 있습니다:

  • MainCrewList 인터페이스: mainCategory?: string, subCategory?: string
  • CrewDetail 인터페이스: mainCategory?: string, subCategory?: string
  • CreateCrewFormTypes 인터페이스: mainCategory: string, subCategory: string | null

타입 정의가 일관되게 유지되고 있으므로 문제가 없습니다.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: 크루 관련 인터페이스들의 필드 정의를 확인합니다.

# 크루 관련 타입 정의 파일들을 검색
rg -t typescript "interface.*Crew.*{" --glob "src/types/**"

# mainCategory, subCategory, introduce 필드 사용을 검색
rg -t typescript "(mainCategory|subCategory|introduce)\??:\s*string" --glob "src/types/**"

Length of output: 289


Script:

#!/bin/bash
# Search for Crew related interfaces and their field definitions
rg -l "interface.*Crew" src/types/

Length of output: 110


Script:

#!/bin/bash
# Show content of files containing Crew interfaces
fd . src/types/ -e ts -x cat {}

Length of output: 5876


Script:

#!/bin/bash
# Search for mainCategory and subCategory fields in typescript files
rg "mainCategory|subCategory" src/types/

Length of output: 647

src/components/common/input/file-input-wrap/file-input/file-input.stories.tsx (2)

28-28: 타입 변경이 적절해 보입니다.

StaticImageData 타입을 제거하고 string 타입을 추가한 것은 실제 파일 업로드와 URL 문자열을 처리하는데 더 적합한 방식입니다.


57-59: onChange 핸들러의 응답 형식을 검증해주세요.

새로운 형식({ image: newValue })이 API의 예상 응답 형식과 일치하는지 확인이 필요합니다.

✅ Verification successful

Let me gather more information about the expected request types and the file input component usage.


FileInput의 onChange 핸들러 응답 형식이 올바르게 구현되어 있습니다.

stories 파일의 onChange 핸들러는 Storybook action 로깅을 위한 것으로, 실제 컴포넌트의 인터페이스와 일치할 필요가 없습니다. 실제 FileInput 컴포넌트는 다음과 같이 정의되어 있습니다:

  • FileInputProps.onChange: (value: File | null) => void
  • 부모 컴포넌트 FileInputWrap에서 (newValue: File | string | null) => void 형식으로 처리

따라서 stories 파일의 { image: newValue } 형식은 단순히 action 로깅을 위한 것이며, 실제 컴포넌트 사용에는 영향을 미치지 않습니다.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# API 응답 형식과 일치하는지 확인
rg -A 5 "editCrew|createCrew" --type ts

Length of output: 2313


Script:

#!/bin/bash
# Check CreateCrewRequestTypes and EditCrewRequestTypes
rg -g "*.ts" -A 10 "type CreateCrewRequestTypes|type EditCrewRequestTypes"

# Check file input component usage in crew related files
rg -g "*.tsx" "FileInput.*Crew" -A 5

Length of output: 115


Script:

#!/bin/bash
# Check type definitions in create-crew.ts
fd "create-crew.ts" --type f --exec cat {}

# Check FileInput component props and types
fd "file-input.tsx" --type f --exec cat {}

Length of output: 89


Script:

#!/bin/bash
# Search for type definitions in types directory
rg -g "*.ts" "interface|type.*Crew.*Request" -A 5

# Check FileInput component props interface
ast-grep --pattern 'interface FileInputProps {
  $$$
}'

Length of output: 10864

src/components/common/crew-list/crew-card-list.stories.tsx (2)

31-31: useInfiniteScroll 훅의 반환값 구조분해가 개선되었습니다.

상태 관리가 더 명확해졌으며, status 대신 isLoadingerror를 직접 사용하는 방식이 더 직관적입니다.


48-54: 로딩 상태 처리 로직이 단순화되었습니다.

이전의 status === 'loading' 체크 대신 isLoading 불리언 값을 직접 사용하여 코드가 더 간결해졌습니다. 무한 스크롤의 로딩 상태도 함께 처리되어 있어 적절합니다.

src/utils/api.ts (1)

Line range hint 3-13: 구현이 잘 되어있습니다!

타입 안정성이 잘 보장되어 있으며, 유효성 검사 오류를 처리하기 위한 구조가 명확합니다.

src/app/(crew)/crew/create/page.tsx (2)

6-6: 커스텀 쿼리 훅 도입이 잘 되었습니다!

기존의 mutation 로직을 커스텀 훅으로 분리하여 코드의 재사용성과 관심사 분리가 잘 이루어졌습니다.


23-23: mutation 로직 단순화가 잘 되었습니다!

useCreateCrewQuery 훅을 사용하여 복잡한 mutation 로직을 깔끔하게 추상화했습니다.

src/components/common/input/file-input-wrap/file-input/index.tsx (2)

5-6: 샘플 이미지 URL 임포트가 적절히 추가되었습니다.

샘플 이미지 URL을 관리하기 위한 임포트가 올바르게 추가되었으며, 프로젝트의 임포트 패턴을 잘 따르고 있습니다.


9-9: 인터페이스 타입 정의가 개선되었습니다.

StaticImageData 타입을 제거하고 File | string | null로 단순화한 것이 적절합니다. 이는 실제 사용 사례와 더 잘 부합합니다.

src/app/(crew)/page.tsx (1)

38-38: 상태 관리 패턴이 개선되었습니다! 👍

useInfiniteScroll 훅의 반환값을 status 대신 isLoadingerror로 분리한 것이 좋은 개선사항입니다. 이는 더 명확한 상태 관리를 가능하게 합니다.

src/app/(crew)/crew/_components/create-crew-form/index.tsx (1)

14-14: 타입 정의가 적절히 확장되었습니다.

EditCrewResponseTypes를 추가하고 CreateCrewFormProps의 data 타입을 유니온 타입으로 확장한 것이 적절해 보입니다. 이를 통해 생성과 수정 기능을 모두 지원할 수 있게 되었습니다.

Also applies to: 18-18

<div ref={ref} className="h-[1px]" />
)}
{status === 'error' && <p className="py-10 text-center">에러가 발생했습니다.</p>}
{error && <p className="py-10 text-center">에러가 발생했습니다.</p>}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

에러 처리를 더 상세하게 개선하면 좋겠습니다.

현재는 일반적인 에러 메시지만 표시되고 있습니다. 사용자에게 더 구체적인 정보와 해결 방법을 제공하면 좋을 것 같습니다.

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

- {error && <p className="py-10 text-center">에러가 발생했습니다.</p>}
+ {error && (
+   <div className="py-10 text-center">
+     <p className="text-red-500 mb-2">데이터를 불러오는 중 문제가 발생했습니다.</p>
+     <button
+       onClick={() => window.location.reload()}
+       className="text-blue-500 underline"
+     >
+       다시 시도하기
+     </button>
+   </div>
+ )}
📝 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
{error && <p className="py-10 text-center">에러가 발생했습니다.</p>}
{error && (
<div className="py-10 text-center">
<p className="text-red-500 mb-2">데이터를 불러오는 중 문제가 발생했습니다.</p>
<button
onClick={() => window.location.reload()}
className="text-blue-500 underline"
>
다시 시도하기
</button>
</div>
)}

Comment on lines +16 to +25
export interface EditCrewResponseTypes {
title: string;
mainCategory: string;
subCategory: string;
mainLocation: string;
subLocation: string;
totalCount: number;
introduce: string;
imageUrl: string;
}
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

⚠️ Potential issue

Based on the gathered evidence, I can now provide the final response:

타입 정의가 실제 사용 사례와 일치하지 않습니다

코드베이스 분석 결과:

  • subCategorysubLocation은 폼에서 null 값을 허용하고 있습니다 (setValue('subCategory', null) 사용 확인)
  • 생성 페이지에서도 초기값으로 null을 사용합니다
  • 하지만 EditCrewResponseTypes에서는 이 필드들이 필수 문자열로 정의되어 있습니다

수정 제안:

  • EditCrewResponseTypes의 타입을 다음과 같이 수정해야 합니다:
subCategory: string | null;
subLocation: string | null;
🔗 Analysis chain

타입 일관성 검토가 필요합니다

CreateCrewFormTypes와 비교했을 때 몇 가지 불일치가 있습니다:

  • subCategorysubLocationstring | null 대신 string으로 정의되어 있습니다
  • imageUrlFile | string | null 대신 string으로만 정의되어 있습니다

이러한 차이점이 의도적인 것인지 확인해주시기 바랍니다.

다음 스크립트로 실제 API 응답에서 null 값이 반환되는지 확인할 수 있습니다:


Let me gather more information about how these types are used in the codebase.


Let me check one more aspect to understand the form submission and validation logic.


Let me try one last search with corrected syntax:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if the API returns null values for these fields

# Search for API response handling code
rg -A 5 "EditCrewResponse" 

Length of output: 1662


Script:

#!/bin/bash
# Check how EditCrewResponseTypes is used in API calls and data handling

# Search for API endpoints and response handling
rg -A 10 "crews.*edit" src/_apis

# Search for form handling with these types
ast-grep --pattern 'const $_ = async ($_: $_) => {
  $$$
  EditCrewResponse
  $$$
}'

# Check if there are any null checks for these fields
rg "subCategory.*null|subLocation.*null" src/app

Length of output: 478


Script:

#!/bin/bash
# Check form validation and submission logic

# Look for form validation rules
rg -A 5 "subCategory|subLocation" src/app/(crew)/crew/_components/create-crew-form

# Check API response handling
rg -A 10 "crews/\$|crews/[0-9]" src/_apis

# Look for any transformation of these fields
ast-grep --pattern 'const $_ = {
  $$$
  subCategory: $_,
  $$$
}'

Length of output: 219


Script:

#!/bin/bash
# Check form validation and submission logic

# Look for form validation rules
rg -A 5 "subCategory|subLocation" "src/app/\(crew\)/crew/_components/create-crew-form"

# Check API response handling
rg -A 10 "editCrew|updateCrew" src/_apis

# Look for any data transformations
rg -A 5 "data: EditCrewResponse" src

Length of output: 857

Comment on lines +29 to +42
export async function editCrew(id: number, data: EditCrewRequestTypes) {
try {
await fetchApi(`/api/crews/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include', // Include authentication credentials
body: JSON.stringify(data),
});
} catch (error) {
throw error;
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

editCrew 함수의 구현을 개선해야 합니다.

다음과 같은 중요한 개선사항들이 필요합니다:

  1. 응답 처리가 누락되어 있습니다
  2. 에러 처리가 미흡합니다
  3. 반환 타입이 명시되어 있지 않습니다

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

-export async function editCrew(id: number, data: EditCrewRequestTypes) {
+export async function editCrew(id: number, data: EditCrewRequestTypes): Promise<EditCrewResponseTypes> {
   try {
-    await fetchApi(`/api/crews/${id}`, {
+    const response: { data: EditCrewResponseTypes } = await fetchApi(`/api/crews/${id}`, {
       method: 'PUT',
       headers: {
         'Content-Type': 'application/json',
       },
       credentials: 'include',
       body: JSON.stringify(data),
     });
+    if (!response.data) {
+      throw new Error('크루 수정 실패: 서버로부터 데이터를 받지 못했습니다');
+    }
+    return response.data;
   } catch (error) {
-    throw error;
+    if (error instanceof TypeError) {
+      throw new Error('크루 수정 실패: 네트워크 오류가 발생했습니다');
+    }
+    throw new Error(`크루 수정 실패: ${error.message}`);
   }
 }

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

🧰 Tools
🪛 Biome

[error] 40-40: The catch clause that only rethrows the original error is useless.

An unnecessary catch clause can be confusing.
Unsafe fix: Remove the try/catch clause.

(lint/complexity/noUselessCatch)

Comment on lines 25 to 41
const handleSubmit = async (createdData: CreateCrewFormTypes) => {
let newImageUrl = createdData.imageUrl as string;
if (createdData.imageUrl instanceof File) {
const imgResponse = await getImageUrl(createdData.imageUrl, 'CREW');
newImageUrl = imgResponse?.imageUrl as string;
}
const newData: CreateCrewRequestTypes = {
title: data.title,
mainCategory: data.mainCategory,
subCategory: data.subCategory ?? '',
title: createdData.title,
mainCategory: createdData.mainCategory,
subCategory: createdData.subCategory ?? '',
imageUrl: newImageUrl ?? '',
mainLocation: data.mainLocation,
subLocation: data.subLocation ?? '',
totalCount: data.totalCount,
introduce: data.introduce,
mainLocation: createdData.mainLocation,
subLocation: createdData.subLocation ?? '',
totalCount: createdData.totalCount,
introduce: createdData.introduce,
};

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

이미지 업로드 실패에 대한 에러 처리가 필요합니다.

getImageUrl 호출 시 발생할 수 있는 에러에 대한 처리가 없습니다. 이미지 업로드 실패 시 사용자에게 적절한 피드백을 제공해야 합니다.

 const handleSubmit = async (createdData: CreateCrewFormTypes) => {
   let newImageUrl = createdData.imageUrl as string;
   if (createdData.imageUrl instanceof File) {
-    const imgResponse = await getImageUrl(createdData.imageUrl, 'CREW');
-    newImageUrl = imgResponse?.imageUrl as string;
+    try {
+      const imgResponse = await getImageUrl(createdData.imageUrl, 'CREW');
+      if (!imgResponse?.imageUrl) {
+        throw new Error('이미지 URL을 받아오지 못했습니다.');
+      }
+      newImageUrl = imgResponse.imageUrl;
+    } catch (error) {
+      console.error('이미지 업로드 실패:', error);
+      // TODO: 에러 처리 로직 추가 (예: 토스트 메시지 표시)
+      return;
+    }
   }

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

};
export default function FileInput({ value, isBlur, onChange }: FileInputProps) {
const [preview, setPreview] = useState<string | null>(typeof value === 'string' ? value : null);
const [preview, setPreview] = useState<string | null>(isSample(value) ? null : (value as string));
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

타입 안전성을 개선할 수 있습니다.

현재 구현에서 value as string 타입 캐스팅은 잠재적으로 안전하지 않을 수 있습니다. 다음과 같이 개선할 것을 제안합니다:

- const [preview, setPreview] = useState<string | null>(isSample(value) ? null : (value as string));
+ const [preview, setPreview] = useState<string | null>(() => {
+   if (value instanceof File) return null;
+   if (typeof value === 'string') {
+     return isSample(value) ? null : value;
+   }
+   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
const [preview, setPreview] = useState<string | null>(isSample(value) ? null : (value as string));
const [preview, setPreview] = useState<string | null>(() => {
if (value instanceof File) return null;
if (typeof value === 'string') {
return isSample(value) ? null : value;
}
return null;
});

if (data === null || data === undefined) {
return;
}
queryClient.invalidateQueries({ queryKey: ['crewLists', 'crewDetail'] });
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

쿼리 무효화 시 올바른 사용법 적용

queryClient.invalidateQueries({ queryKey: ['crewLists', 'crewDetail'] });로 여러 쿼리를 무효화하려고 하고 있습니다. 그러나 이 방식은 의도한 대로 동작하지 않을 수 있습니다. 각각의 쿼리를 별도로 무효화하는 것이 좋습니다.

수정 제안:

-queryClient.invalidateQueries({ queryKey: ['crewLists', 'crewDetail'] });
+queryClient.invalidateQueries(['crewLists']);
+queryClient.invalidateQueries(['crewDetail']);
📝 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
queryClient.invalidateQueries({ queryKey: ['crewLists', 'crewDetail'] });
queryClient.invalidateQueries(['crewLists']);
queryClient.invalidateQueries(['crewDetail']);

mutationFn: (data: EditCrewRequestTypes) => editCrew(id, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['crewDetail'] });
toast.success('크루 정보가 수정되었습니다.');
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

쿼리 무효화 시 정확한 키 사용

현재 queryClient.invalidateQueries({ queryKey: ['crewDetail'] });로 쿼리를 무효화하고 있습니다. 그러나 crewDetail 쿼리는 id를 포함하므로 정확한 쿼리 키를 사용하여 무효화해야 합니다.

수정 제안:

-queryClient.invalidateQueries({ queryKey: ['crewDetail'] });
+queryClient.invalidateQueries(['crewDetail', id]);

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

Comment on lines 18 to 24
const handleEdit = async (editedData: CreateCrewFormTypes) => {
let newImageUrl = editedData.imageUrl as string;

const handleEdit = () => {
// TODO : PATCH API 연결
if (editedData.imageUrl instanceof File && newImageUrl !== data.imageUrl) {
const imgResponse = await getImageUrl(editedData.imageUrl, 'CREW');
newImageUrl = imgResponse?.imageUrl as string;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

TypeScript 타입 캐스팅 관련 오류 수정 필요

editedData.imageUrlFile 타입일 때, newImageUrl을 문자열로 단언(as string)하고 있습니다. 그러나 실제로는 File 객체이기 때문에 타입 불일치로 인한 오류가 발생할 수 있습니다. 이로 인해 newImageUrl !== data.imageUrl 비교에서 예상치 못한 결과가 나올 수 있습니다. 정확한 타입 체크와 변환이 필요합니다.

수정을 위한 제안:

const handleEdit = async (editedData: CreateCrewFormTypes) => {
-   let newImageUrl = editedData.imageUrl as string;
+   let newImageUrl: string;

    if (editedData.imageUrl instanceof File && newImageUrl !== data.imageUrl) {
      const imgResponse = await getImageUrl(editedData.imageUrl, 'CREW');
      newImageUrl = imgResponse?.imageUrl as string;
    } else {
+     newImageUrl = editedData.imageUrl as string;
    }
    const newData: EditCrewRequestTypes = {
      // ... 생략 ...
    };
    mutate(newData);
};

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

Copy link
Contributor

@minkyung5x5 minkyung5x5 left a comment

Choose a reason for hiding this comment

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

수고많으셨습니다!!! 짱짱

@github-actions
Copy link

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (2)
src/app/(crew)/page.tsx (1)

135-141: 로딩 상태에 대한 접근성 개선이 필요합니다.

로딩 인디케이터에 스크린 리더를 위한 aria-label을 추가하는 것이 좋겠습니다.

 {isLoading || isFetchingNextPage ? (
   <div className="flex justify-center py-10">
-    <Loader size="sm" />
+    <Loader size="sm" aria-label="콘텐츠를 불러오는 중입니다" />
   </div>
 ) : (
   <div ref={ref} className="h-[1px]" />
 )}
src/data/region.json (1)

중복된 지역 코드와 빈 값 문제가 발견되었습니다.

검증 결과 다음과 같은 문제점들이 확인되었습니다:

  • 여러 지역에서 동일한 지역 코드가 중복 사용되고 있습니다:
    • buk (북구) - 4개 지역
    • dong (동구) - 6개 지역
    • jung (중구) - 6개 지역
    • nam (남구) - 4개 지역
    • seo (서구) - 5개 지역
    • gangseo (강서구) - 2개 지역
    • goseong (고성군) - 2개 지역

각 지역의 고유성을 보장하기 위해 도시명을 접두어로 추가하는 등의 방식으로 지역 코드를 수정해야 합니다.
예: busan-jung, seoul-jung

🔗 Analysis chain

Line range hint 1-321: 데이터 일관성 검증이 필요합니다.

지역 데이터의 완전성과 정확성을 확인하기 위해 검증이 필요합니다.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# 모든 지역에 "전체" 옵션이 올바르게 추가되었는지 확인
echo "지역별 '전체' 옵션 확인:"
jq -r '.[] | select(.areas[0].label != "전체") | .main.label' src/data/region.json

# 모든 지역의 value 필드가 비어있지 않은지 확인
echo -e "\n빈 value 필드 확인:"
jq -r '.[] | select(.main.value == "") | .main.label' src/data/region.json

# 중복된 value 값이 없는지 확인
echo -e "\n중복 value 확인:"
jq -r '[.[] | .areas[] | .value] | group_by(.) | map(select(length > 1)) | .[]' src/data/region.json

Length of output: 810

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 8cc9bd6 and 9b8b2f8.

📒 Files selected for processing (4)
  • src/app/(crew)/crew/create/page.tsx (2 hunks)
  • src/app/(crew)/crew/detail/[id]/edit/page.tsx (2 hunks)
  • src/app/(crew)/page.tsx (2 hunks)
  • src/data/region.json (16 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app/(crew)/crew/create/page.tsx
🔇 Additional comments (6)
src/app/(crew)/crew/detail/[id]/edit/page.tsx (3)

5-9: 필요한 의존성들이 올바르게 추가되었습니다.

로딩 상태 처리와 이미지 업로드 기능을 위한 필수 컴포넌트와 유틸리티들이 적절하게 임포트되었습니다.


15-15: 훅 사용이 적절합니다.

useEditCrewQuery 훅이 올바르게 설정되었으며, 필요한 상태값들을 적절히 구조분해하고 있습니다.


39-44: 로딩 상태 처리가 잘 구현되었습니다.

isLoading과 isPending 상태를 모두 고려하여 사용자에게 적절한 피드백을 제공하고 있습니다.

src/app/(crew)/page.tsx (1)

Line range hint 37-47: 상태 관리 패턴이 개선되었습니다! 👍

status 대신 isLoadingerror를 사용하는 방식으로 변경된 것이 더 명확하고 현대적인 React 패턴에 부합합니다.

src/data/region.json (2)

5-5: 데이터 구조 개선이 일관성 있게 적용되었습니다.

모든 지역에 "전체" 옵션이 추가되어 일관된 데이터 구조를 가지게 되었습니다. 이는 사용자 인터페이스에서 지역 선택 기능을 더 직관적으로 만들어줄 것입니다.

Also applies to: 36-36, 57-57, 71-71, 86-86, 97-97, 108-108, 119-119, 125-125, 154-154, 178-178, 195-195, 216-216, 236-236, 264-264, 293-293, 317-317


118-120: 세종특별자치시의 데이터 구조가 개선되었습니다.

이전에 비어있던 areas 배열에 "전체" 옵션이 추가되어 다른 지역들과 동일한 구조를 갖게 되었습니다.

Comment on lines 21 to 24
if (editedData.imageUrl instanceof File && newImageUrl !== data.imageUrl) {
const imgResponse = await getImageUrl(editedData.imageUrl, 'CREW');
newImageUrl = imgResponse?.imageUrl as string;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

이미지 업로드 응답에 대한 오류 처리가 필요합니다.

getImageUrl 응답이 실패하거나 imageUrl이 undefined인 경우에 대한 처리가 누락되어 있습니다.

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

  if (editedData.imageUrl instanceof File && newImageUrl !== data.imageUrl) {
    const imgResponse = await getImageUrl(editedData.imageUrl, 'CREW');
+   if (!imgResponse?.imageUrl) {
+     throw new Error('이미지 업로드에 실패했습니다.');
+   }
    newImageUrl = imgResponse?.imageUrl as string;
  }
📝 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
if (editedData.imageUrl instanceof File && newImageUrl !== data.imageUrl) {
const imgResponse = await getImageUrl(editedData.imageUrl, 'CREW');
newImageUrl = imgResponse?.imageUrl as string;
}
if (editedData.imageUrl instanceof File && newImageUrl !== data.imageUrl) {
const imgResponse = await getImageUrl(editedData.imageUrl, 'CREW');
if (!imgResponse?.imageUrl) {
throw new Error('이미지 업로드에 실패했습니다.');
}
newImageUrl = imgResponse?.imageUrl as string;
}

<h2 className="text-2xl font-bold text-gray-900 md:text-3.5xl">크루 수정하기</h2>
</div>
<CreateCrewForm isEdit data={initialValue} onEdit={handleEdit} />
<CreateCrewForm isEdit data={data as EditCrewRequestTypes} onEdit={handleEdit} />
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

타입 캐스팅이 안전하지 않습니다.

CreateCrewFormProps 인터페이스에 따르면 data prop은 CreateCrewFormTypes | EditCrewResponseTypes 타입을 받도록 정의되어 있는데, EditCrewRequestTypes로 캐스팅하는 것은 타입 안전성을 해칠 수 있습니다.

  • src/app/(crew)/crew/_components/create-crew-form/index.tsx에서 정의된 인터페이스와 일치하도록 타입을 수정해야 합니다.
  • 타입 캐스팅 대신 EditCrewResponseTypes 타입으로 데이터를 변환하는 로직을 추가하거나, 컴포넌트의 타입 정의를 수정해야 합니다.
🔗 Analysis chain

타입 캐스팅의 안전성 검증이 필요합니다.

data를 EditCrewRequestTypes로 캐스팅하기 전에 타입 가드를 사용하여 안전성을 보장하는 것이 좋습니다.

다음 스크립트로 CreateCrewForm 컴포넌트의 props 타입을 확인해보세요:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# CreateCrewForm 컴포넌트의 props 타입 정의 확인
ast-grep --pattern 'interface $interface {
  $$$
  data?: $type;
  $$$
}'

# EditCrewRequestTypes 타입 정의 확인
ast-grep --pattern 'type EditCrewRequestTypes = {
  $$$
}'

Length of output: 136


Script:

#!/bin/bash
# Search for CreateCrewForm component props interface
rg -A 10 "interface.*CreateCrewFormProps" 

# Search for EditCrewRequestTypes type definition
rg -A 10 "type EditCrewRequestTypes"

# Search for CreateCrewForm component implementation
ast-grep --pattern 'const CreateCrewForm = ($props: $type) => {
  $$$
}'

Length of output: 1122


Script:

#!/bin/bash
# Search for EditCrewResponseTypes and CreateCrewFormTypes definitions
rg -A 5 "type (EditCrewResponseTypes|CreateCrewFormTypes)"

# Search for actual usage of data prop in CreateCrewForm
ast-grep --pattern 'const { data, $$ } = $props'

# Search for type validation or guards
rg -A 3 "data as|is.*data"

Length of output: 12144

<div ref={ref} className="h-[1px]" />
)}
{status === 'error' && <p className="py-10 text-center">에러가 발생했습니다.</p>}
{error && <p className="py-10 text-center">에러가 발생했습니다.</p>}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

에러 처리를 더 자세하게 개선해 주세요.

현재 에러 메시지가 너무 단순합니다. 사용자에게 더 구체적인 정보와 문제 해결 방법을 제공하면 좋겠습니다.

- {error && <p className="py-10 text-center">에러가 발생했습니다.</p>}
+ {error && (
+   <div className="flex flex-col items-center py-10 text-center">
+     <p className="mb-2 text-red-500">데이터를 불러오는 중 문제가 발생했습니다.</p>
+     <p className="mb-4 text-sm text-gray-600">잠시 후 다시 시도해 주세요.</p>
+     <Button
+       onClick={() => window.location.reload()}
+       className="btn-filled px-4 py-2 text-sm"
+     >
+       새로고침
+     </Button>
+   </div>
+ )}

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

@github-actions
Copy link

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (7)
src/app/(crew)/crew/detail/[id]/edit/page.tsx (2)

5-9: 불필요한 import 순서 최적화 제안

import 문이 기능별로 잘 구분되어 있습니다. 다만 가독성을 위해 다음과 같은 순서로 정리하는 것을 제안드립니다:

  1. React 관련 import
  2. 외부 라이브러리 import
  3. 내부 API/쿼리 import
  4. 컴포넌트 import
  5. 타입 import
  6. 에셋 import

39-44: 로딩 상태 UI 개선 필요

현재 로딩 상태 UI가 너무 단순합니다. 사용자 경험 향상을 위해 다음 사항들을 고려해보세요:

  1. 로딩 중임을 알리는 텍스트 추가
  2. 배경 오버레이 추가
  3. 접근성 고려 (aria-label 등)
  if (isLoading || isPending)
    return (
-     <div className="fixed inset-0 flex items-center justify-center">
-       <Loader size="sm" />
+     <div className="fixed inset-0 flex items-center justify-center bg-black/50">
+       <div className="flex flex-col items-center gap-2 rounded-lg bg-white p-4">
+         <Loader size="sm" aria-label="로딩 중" />
+         <p className="text-sm text-gray-600">크루 정보를 수정하는 중입니다...</p>
+       </div>
      </div>
    );
src/components/common/gathering-card/scheduled-gathering-card/container.tsx (2)

Line range hint 31-52: 더미 데이터의 타입 안정성 개선 필요

dummyModalData 객체가 실제 API 응답 타입과 일치하는지 확인이 필요합니다. 특히 새로 추가된 gatheringCaptainparticipant 필드의 타입이 백엔드 API 스펙과 일치하는지 검증해야 합니다.

다음과 같은 개선을 제안드립니다:

interface GatheringDetailType {
  crewId: number;
  id: number;
  title: string;
  introduce: string;
  dateTime: string;
  location: string;
  currentCount: number;
  totalCount: number;
  imageUrl: string;
  liked: boolean;
  gatheringCaptain: boolean;
  participant: boolean;
  participants: Array<{
    id: number;
    profileImageUrl: string;
    nickname: string;
    email: string;
  }>;
}

const dummyModalData: GatheringDetailType = {
  // ... 현재 데이터 ...
};

Line range hint 71-89: 모달 상태 관리 로직 개선 제안

현재 모달의 열림/닫힘 상태가 단순 boolean 값으로 관리되고 있습니다. 모달 상태와 관련된 로직을 커스텀 훅으로 분리하면 재사용성과 유지보수성이 향상될 것 같습니다.

다음과 같은 리팩토링을 제안드립니다:

const useModal = (initialState = false) => {
  const [isOpened, setIsOpened] = useState(initialState);
  
  const open = useCallback(() => setIsOpened(true), []);
  const close = useCallback(() => setIsOpened(false), []);
  
  return { isOpened, open, close };
};

// 컴포넌트 내부에서 사용
const { isOpened, open, close } = useModal();
src/app/(crew)/crew/create/_components/create-crew-form/index.tsx (3)

125-125: 드롭다운 플레이스홀더 로직 개선이 필요합니다.

현재 각 드롭다운마다 비슷한 조건부 로직이 반복되고 있습니다. 코드의 재사용성과 유지보수성을 높이기 위한 개선이 필요합니다.

다음과 같은 리팩토링을 제안드립니다:

+const getPlaceholder = (isEdit: boolean, value: string | null, defaultText: string) => {
+  return isEdit && value ? value : defaultText;
+};

// 사용 예시:
-placeholder={isEdit ? field.value : '메인 카테고리'}
+placeholder={getPlaceholder(isEdit, field.value, '메인 카테고리')}

Also applies to: 144-144, 207-207, 227-227


273-273: 소개글 길이 표시 로직의 안정성 개선이 필요합니다.

현재 옵셔널 체이닝으로 null 체크를 하고 있지만, 더 안정적인 구현이 가능합니다.

다음과 같은 개선을 제안드립니다:

-<span className="text-blue-500">{introduce?.length}</span>/100
+<span className="text-blue-500">{(introduce || '').length}</span>/100

이렇게 하면 introduceundefinednull일 때도 안전하게 0을 표시할 수 있습니다.


Line range hint 35-42: 폼 유효성 검사 규칙의 중앙화가 필요합니다.

현재 유효성 검사 규칙이 컴포넌트 내부에 산재되어 있습니다. 이는 규칙의 재사용과 일관성 유지를 어렵게 만들 수 있습니다.

다음과 같은 구조적 개선을 제안드립니다:

  1. 별도의 validation 스키마 파일을 생성하여 유효성 검사 규칙을 중앙 관리
  2. 스키마를 react-hook-form의 resolver로 사용
  3. 이를 통해 폼 유효성 검사의 재사용성과 유지보수성 향상

예시 구조:

// validations/crew-form.ts
export const crewFormValidationSchema = {
  title: {
    required: '필수 입력사항입니다.',
    maxLength: { value: 20, message: '20자 이내로 입력해주세요.' }
  },
  // ... 다른 필드들의 유효성 검사 규칙
};
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 9b8b2f8 and 0776981.

📒 Files selected for processing (9)
  • src/app/(crew)/_components/category/category-container/index.tsx (1 hunks)
  • src/app/(crew)/crew/create/_components/create-crew-form/index.tsx (6 hunks)
  • src/app/(crew)/crew/create/page.tsx (2 hunks)
  • src/app/(crew)/crew/detail/[id]/_components/create-gathering/create-gathering-modal/presenter.tsx (1 hunks)
  • src/app/(crew)/crew/detail/[id]/_components/create-gathering/index.tsx (1 hunks)
  • src/app/(crew)/crew/detail/[id]/edit/page.tsx (2 hunks)
  • src/app/(crew)/page.tsx (3 hunks)
  • src/components/common/gathering-card/container.tsx (1 hunks)
  • src/components/common/gathering-card/scheduled-gathering-card/container.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • src/app/(crew)/_components/category/category-container/index.tsx
  • src/app/(crew)/crew/detail/[id]/_components/create-gathering/index.tsx
  • src/components/common/gathering-card/container.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app/(crew)/page.tsx
🔇 Additional comments (9)
src/app/(crew)/crew/detail/[id]/_components/create-gathering/create-gathering-modal/presenter.tsx (2)

Line range hint 11-46: 모달 구현이 잘 되어있습니다.

다음과 같은 장점들이 있습니다:

  • GatheringDetailModalProps 인터페이스를 통한 타입 안정성 확보
  • Mantine 컴포넌트의 적절한 활용
  • 일관된 스타일링 접근 방식
  • 깔끔한 프레젠터 패턴 구현

2-2: 임포트 경로 변경이 적절해 보입니다.

컴포넌트의 새로운 위치가 기능적 맥락에 더 잘 부합하며, 관련 컴포넌트들이 같은 디렉토리에 위치하도록 구성되어 있습니다.

새로운 경로의 유효성을 확인하기 위해 다음 스크립트를 실행합니다:

✅ Verification successful

임포트 경로가 정확하게 설정되어 있음을 확인했습니다.

컴포넌트 파일이 src/app/(crew)/crew/detail/[id]/_components/create-gathering/create-gathering-form/ 경로에 올바르게 존재하며, 모든 관련 임포트 참조가 정상적으로 이루어지고 있습니다. 컴포넌트의 위치가 기능적 맥락에 맞게 잘 구성되어 있습니다.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: 새로운 컴포넌트 위치 확인

# 컴포넌트 파일의 존재 여부 확인
fd "create-gathering-form.tsx" src/app/\(crew\)/crew/detail/

# 다른 파일에서의 임포트 참조 확인
rg "CreateGatheringForm" --type tsx

Length of output: 124


Script:

#!/bin/bash
# Check if the component file exists
fd "create-gathering-form" src/

# Check imports across the codebase
rg "CreateGatheringForm" src/

Length of output: 3955

src/app/(crew)/crew/create/page.tsx (3)

6-6: API 로직 분리가 잘 되었습니다!

커스텀 훅을 사용하여 API 로직을 분리한 것이 좋은 구조입니다.


27-29: 이미지 업로드 에러 처리 관련 이전 리뷰 코멘트가 여전히 유효합니다.


37-37: subLocation 처리 로직 검증이 필요합니다.

'전체' 값을 빈 문자열로 변환하는 로직이 추가되었습니다. 이 변경이 백엔드 API와 올바르게 동작하는지 확인해주세요.

#!/bin/bash
# Description: Verify subLocation handling in API calls

# Check API implementation for subLocation handling
ast-grep --pattern 'export const createCrew = async ($$$) => {
  $$$
  subLocation: $$$,
  $$$
}'
src/app/(crew)/crew/detail/[id]/edit/page.tsx (1)

58-58: 타입 캐스팅 대신 타입 가드 사용 권장

이전 리뷰 코멘트에서 지적된 타입 캐스팅 문제가 여전히 존재합니다. 타입 안전성을 위해 타입 가드를 사용하는 것이 좋습니다.

- <CreateCrewForm isEdit data={data as EditCrewRequestTypes} onEdit={handleEdit} />
+ <CreateCrewForm 
+   isEdit 
+   data={isEditCrewRequestType(data) ? data : undefined} 
+   onEdit={handleEdit} 
+ />

타입 가드 함수를 추가하세요:

function isEditCrewRequestType(data: unknown): data is EditCrewRequestTypes {
  return (
    data !== null &&
    typeof data === 'object' &&
    'title' in data &&
    'mainCategory' in data
    // ... 필요한 프로퍼티 검사 추가
  );
}
src/components/common/gathering-card/scheduled-gathering-card/container.tsx (2)

Line range hint 21-89: TODO 주석들의 구현 우선순위 확인 필요

현재 파일에 여러 개의 미구현 기능이 TODO 주석으로 표시되어 있습니다:

  1. modalData 연결
  2. 좋아요 토글 로직
  3. retry/refetch 구현

이러한 미구현 기능들이 현재 PR의 범위에 포함되어야 하는지, 아니면 후속 작업으로 진행할 예정인지 명확히 해주시면 좋겠습니다.

이러한 기능들의 구현을 도와드릴까요? 특히 다음 부분들에 대해 도움을 드릴 수 있습니다:

  • useQuery를 사용한 modalData 연결
  • 좋아요 토글 API 연동
  • 에러 처리와 재시도 로직

2-2: 임포트 경로 변경이 적절한지 확인 필요

새로운 경로 구조가 프로젝트의 라우팅 구조와 일치하는지 확인이 필요합니다. 특히 [id] 동적 라우팅이 올바르게 구현되어 있는지 검증해야 합니다.

✅ Verification successful

Let me verify the routing structure and component implementation to ensure completeness.


임포트 경로가 정상적으로 구현되어 있음을 확인했습니다

해당 컴포넌트(GatheringDetailModalContainer)가 새로운 경로에 올바르게 구현되어 있으며, Next.js의 동적 라우팅 구조([id])와도 일치합니다. 컴포넌트의 기능(참여, 취소, 삭제)이 모두 정상적으로 구현되어 있어 임포트 경로 변경은 적절합니다.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: 새로운 임포트 경로의 유효성 확인

# 해당 컴포넌트 파일의 존재 여부 확인
fd -t f "container.tsx" "src/app/(crew)/crew/detail/[id]/_components/gathering-detail-modal"

# 관련된 라우트 설정 확인
fd -t f "page.tsx" "src/app/(crew)/crew/detail"

Length of output: 308


Script:

#!/bin/bash
# Check the implementation of the modal component
cat "src/app/(crew)/crew/detail/[id]/_components/gathering-detail-modal/container.tsx"

# Check the implementation of the pages using this component
cat "src/app/(crew)/crew/detail/[id]/page.tsx"

Length of output: 3480

src/app/(crew)/crew/create/_components/create-crew-form/index.tsx (1)

14-14: 타입 시스템이 적절하게 확장되었습니다.

CreateCrewFormProps 인터페이스에서 data 프로퍼티가 생성과 수정 모두를 지원하도록 타입이 확장되었습니다. 이는 컴포넌트의 재사용성을 높이는 좋은 접근 방식입니다.

Also applies to: 18-18

let newImageUrl = data.imageUrl as string;
if (data.imageUrl instanceof File) {
const imgResponse = await getImageUrl(data.imageUrl, 'CREW');
const { isPending, mutate } = useCreateCrewQuery();
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

에러 처리가 누락되어 있습니다

useCreateCrewQuery 훅의 구현을 확인한 결과, onError 핸들러가 구현되어 있지 않습니다. 이는 다음과 같은 문제를 초래할 수 있습니다:

  • 사용자에게 오류 발생 시 적절한 피드백이 제공되지 않음
  • 네트워크 오류나 서버 오류 발생 시 적절한 처리가 되지 않음

수정 제안:

  • useMutation 설정에 onError 핸들러를 추가하여 에러 상황을 적절히 처리하도록 구현
  • 사용자에게 오류 메시지를 표시하는 UI 피드백 추가
🔗 Analysis chain

에러 처리 검증이 필요합니다.

useCreateCrewQuery 훅에서 에러 처리가 적절히 구현되어 있는지 확인이 필요합니다.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify error handling implementation in useCreateCrewQuery hook

# Check error handling in the query hook
ast-grep --pattern 'export function useCreateCrewQuery() {
  $$$
  onError($$$) {
    $$$
  }
  $$$
}'

Length of output: 328


Script:

#!/bin/bash
# Let's first find the hook file
fd "useCreateCrewQuery" --type f

# Then examine its content
rg "useCreateCrewQuery" -A 10 -B 10

Length of output: 4026

const { id } = useParams();
const { data, isLoading, error } = useGetCrewDetailQuery(Number(id));

const { isPending, mutate } = useEditCrewQuery(Number(id));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

useEditCrewQuery 훅의 에러 처리가 누락되었습니다

useEditCrewQuery에서 반환되는 error 상태를 구조 분해하여 에러 처리를 추가하는 것이 좋습니다.

- const { isPending, mutate } = useEditCrewQuery(Number(id));
+ const { isPending, mutate, error: editError } = useEditCrewQuery(Number(id));
+ 
+ if (editError) {
+   // 에러 처리 로직 추가
+   return <div>수정 중 오류가 발생했습니다.</div>;
+ }

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

Comment on lines 18 to 36
const handleEdit = async (editedData: CreateCrewFormTypes) => {
let newImageUrl = editedData.imageUrl as string;

const handleEdit = () => {
// TODO : PATCH API 연결
if (editedData.imageUrl instanceof File && newImageUrl !== data.imageUrl) {
const imgResponse = await getImageUrl(editedData.imageUrl, 'CREW');
newImageUrl = imgResponse?.imageUrl as string;
}
const newData: EditCrewRequestTypes = {
title: editedData.title,
mainCategory: editedData.mainCategory,
subCategory: editedData.subCategory ?? '',
imageUrl: newImageUrl ?? '',
mainLocation: editedData.mainLocation,
subLocation: editedData.subLocation === '전체' ? '' : (editedData.subLocation ?? ''),
totalCount: editedData.totalCount,
introduce: editedData.introduce,
};

mutate(newData);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

handleEdit 함수의 타입 안전성 및 에러 처리 개선 필요

  1. 이전 리뷰 코멘트에서 지적된 타입 캐스팅 문제가 아직 해결되지 않았습니다.
  2. 이미지 업로드 실패에 대한 에러 처리가 필요합니다.
  3. 옵셔널 체이닝 연산자 사용 시 기본값 처리가 불완전합니다.

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

const handleEdit = async (editedData: CreateCrewFormTypes) => {
-  let newImageUrl = editedData.imageUrl as string;
+  let newImageUrl: string;

+  try {
    if (editedData.imageUrl instanceof File && editedData.imageUrl !== data.imageUrl) {
      const imgResponse = await getImageUrl(editedData.imageUrl, 'CREW');
-     newImageUrl = imgResponse?.imageUrl as string;
+     if (!imgResponse?.imageUrl) {
+       throw new Error('이미지 업로드에 실패했습니다.');
+     }
+     newImageUrl = imgResponse.imageUrl;
+   } else {
+     newImageUrl = typeof editedData.imageUrl === 'string' ? editedData.imageUrl : data.imageUrl;
    }

    const newData: EditCrewRequestTypes = {
      title: editedData.title,
      mainCategory: editedData.mainCategory,
-     subCategory: editedData.subCategory ?? '',
+     subCategory: editedData.subCategory || '',
      imageUrl: newImageUrl,
      mainLocation: editedData.mainLocation,
-     subLocation: editedData.subLocation === '전체' ? '' : (editedData.subLocation ?? ''),
+     subLocation: editedData.subLocation === '전체' ? '' : (editedData.subLocation || ''),
      totalCount: editedData.totalCount,
      introduce: editedData.introduce,
    };

    mutate(newData);
+  } catch (error) {
+    console.error('크루 수정 중 오류 발생:', error);
+    // 에러 처리 로직 추가 (예: 토스트 메시지 표시)
+  }
};
📝 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 handleEdit = async (editedData: CreateCrewFormTypes) => {
let newImageUrl = editedData.imageUrl as string;
const handleEdit = () => {
// TODO : PATCH API 연결
if (editedData.imageUrl instanceof File && newImageUrl !== data.imageUrl) {
const imgResponse = await getImageUrl(editedData.imageUrl, 'CREW');
newImageUrl = imgResponse?.imageUrl as string;
}
const newData: EditCrewRequestTypes = {
title: editedData.title,
mainCategory: editedData.mainCategory,
subCategory: editedData.subCategory ?? '',
imageUrl: newImageUrl ?? '',
mainLocation: editedData.mainLocation,
subLocation: editedData.subLocation === '전체' ? '' : (editedData.subLocation ?? ''),
totalCount: editedData.totalCount,
introduce: editedData.introduce,
};
mutate(newData);
const handleEdit = async (editedData: CreateCrewFormTypes) => {
let newImageUrl: string;
try {
if (editedData.imageUrl instanceof File && editedData.imageUrl !== data.imageUrl) {
const imgResponse = await getImageUrl(editedData.imageUrl, 'CREW');
if (!imgResponse?.imageUrl) {
throw new Error('이미지 업로드에 실패했습니다.');
}
newImageUrl = imgResponse.imageUrl;
} else {
newImageUrl = typeof editedData.imageUrl === 'string' ? editedData.imageUrl : data.imageUrl;
}
const newData: EditCrewRequestTypes = {
title: editedData.title,
mainCategory: editedData.mainCategory,
subCategory: editedData.subCategory || '',
imageUrl: newImageUrl,
mainLocation: editedData.mainLocation,
subLocation: editedData.subLocation === '전체' ? '' : (editedData.subLocation || ''),
totalCount: editedData.totalCount,
introduce: editedData.introduce,
};
mutate(newData);
} catch (error) {
console.error('크루 수정 중 오류 발생:', error);
// 에러 처리 로직 추가 (예: 토스트 메시지 표시)
}
};

@github-actions
Copy link

@github-actions
Copy link

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (5)
src/app/(crew)/crew/create/_components/create-crew-form/create-crew-form.stories.tsx (1)

Line range hint 1-38: 스토리 커버리지 개선이 필요합니다

현재는 생성 모드에 대한 스토리만 있습니다. 수정 모드에 대한 스토리도 추가하여 두 가지 사용 케이스를 모두 문서화하는 것이 좋겠습니다.

다음과 같은 수정 모드 스토리 추가를 제안드립니다:

export const EditMode = Template.bind({});
EditMode.args = {
  type: 'edit',
  data: {
    ...initialValue,
    title: '기존 크루명',
    // 다른 필드들도 샘플 데이터로 채워주세요
  }
};
package.json (1)

37-37: 폼 데이터 지속성 관련 문서화 제안

로컬 스토리지에 저장되는 폼 데이터의 보안 및 관리 방안에 대한 문서화가 필요합니다.

다음 사항들을 고려하여 문서화해주세요:

  • 저장되는 데이터의 범위와 민감도
  • 데이터 만료 정책
  • 사용자 데이터 삭제 방법
src/app/(crew)/crew/create/_components/create-crew-form/index.tsx (3)

5-5: 폼 데이터 지속성 구현이 추가되었습니다.

useFormPersist 훅을 사용하여 폼 데이터를 로컬 스토리지에 저장하는 기능이 구현되었습니다. 이는 사용자 경험을 개선하는 좋은 접근이지만, 몇 가지 고려사항이 있습니다:

  • 저장된 데이터의 수명 주기 관리가 필요합니다
  • 민감한 정보가 있을 경우 보안 위험이 있을 수 있습니다

다음과 같은 개선을 제안드립니다:

 useFormPersist('createCrew', {
   watch,
   setValue,
   storage: window.localStorage,
+  timeout: 3600000, // 1시간 후 데이터 만료
+  exclude: ['sensitiveData'], // 민감한 데이터 제외
 });

Also applies to: 46-50


138-138: 플레이스홀더 텍스트 조건부 렌더링이 개선되었습니다.

편집 모드일 때 현재 값을 플레이스홀더로 표시하는 방식은 좋은 UX 개선입니다. 하지만 조건문이 여러 곳에서 반복되어 있어 유지보수성이 저하될 수 있습니다.

공통 함수로 추출하는 것을 제안드립니다:

const getPlaceholder = (type: 'create' | 'edit', value: string | null, defaultText: string) => {
  return type === 'edit' && value ? value : defaultText;
};

Also applies to: 157-157, 220-220, 240-240


286-286: UI 텍스트가 일관성 있게 업데이트되었습니다.

버튼 텍스트와 글자 수 표시가 적절히 구현되었습니다. 하지만 이러한 UI 텍스트들은 상수로 분리하여 관리하는 것이 좋습니다.

다음과 같은 개선을 제안드립니다:

const UI_TEXT = {
  CREATE: '만들기',
  EDIT: '수정',
  CANCEL: '취소',
} as const;

Also applies to: 308-308

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 0776981 and c58777e.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • package.json (1 hunks)
  • src/app/(crew)/crew/create/_components/create-crew-form/create-crew-form.stories.tsx (1 hunks)
  • src/app/(crew)/crew/create/_components/create-crew-form/index.tsx (11 hunks)
  • src/app/(crew)/crew/create/page.tsx (2 hunks)
  • src/app/(crew)/crew/detail/[id]/edit/page.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/app/(crew)/crew/create/page.tsx
  • src/app/(crew)/crew/detail/[id]/edit/page.tsx
🔇 Additional comments (2)
package.json (1)

37-37: 패키지 상태 및 보안 확인 필요

react-hook-form-persist 패키지의 현재 상태를 확인해야 합니다.

src/app/(crew)/crew/create/_components/create-crew-form/index.tsx (1)

75-77: 서브로케이션 기본값 설정 로직에 대한 검토가 필요합니다.

빈 문자열일 경우 '전체'로 설정하는 로직이 추가되었습니다. 이는 사용자의 의도와 다를 수 있으며, 예상치 못한 동작을 유발할 수 있습니다.

다음 스크립트로 이 로직의 영향을 받는 다른 코드를 확인해보겠습니다:

✅ Verification successful

Let's examine the actual usage of '전체' in these files to understand the context better.


서브로케이션 기본값 '전체' 설정은 의도된 동작입니다.

코드베이스를 검토한 결과, '전체' 값은 다음과 같이 일관되게 처리되고 있습니다:

  • 폼 컴포넌트에서 빈 문자열을 '전체'로 변환
  • API 요청 시 '전체' 값을 다시 빈 문자열로 변환하여 서버로 전송
  • 수정 페이지에서도 동일한 로직 적용

이는 프론트엔드와 백엔드 간의 데이터 변환을 위한 의도적인 설계로 보이며, 사용자 경험에 부정적인 영향을 미치지 않습니다.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# 서브로케이션 기본값 '전체' 사용 여부 확인
rg -l "'전체'" .

Length of output: 176


Script:

#!/bin/bash
# Check the context of '전체' usage in the files
rg "'전체'" -C 3 .

Length of output: 2025


const Template: StoryFn<CreateCrewFormTypes> = function CreateCrewPageStory() {
return <CreateCrewForm data={initialValue} />;
return <CreateCrewForm data={initialValue} type="create" />;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

타입 안전성 개선이 필요합니다

type prop이 "create" | "edit" 리터럴 타입으로 제한되어야 합니다. 현재는 문자열 리터럴로 하드코딩되어 있어 타입 안전성이 보장되지 않습니다.

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

- return <CreateCrewForm data={initialValue} type="create" />;
+ const formType = 'create' as const;
+ return <CreateCrewForm data={initialValue} type={formType} />;
📝 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
return <CreateCrewForm data={initialValue} type="create" />;
const formType = 'create' as const;
return <CreateCrewForm data={initialValue} type={formType} />;

Comment on lines +19 to +20
type: 'create' | 'edit';
data: CreateCrewFormTypes | EditCrewResponseTypes;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

타입 정의가 개선되었습니다.

CreateCrewFormProps 인터페이스에 'create' | 'edit' 타입이 추가되어 폼의 용도가 명확해졌습니다. 하지만 isEdit prop이 여전히 존재하여 중복된 상태 체크가 발생할 수 있습니다.

다음과 같은 개선을 제안드립니다:

export interface CreateCrewFormProps {
  type: 'create' | 'edit';
  data: CreateCrewFormTypes | EditCrewResponseTypes;
- isEdit?: boolean; // 제거
  onEdit?: (data: CreateCrewFormTypes) => void;
  onSubmit?: (data: CreateCrewFormTypes) => void;
}

Also applies to: 27-27

@github-actions
Copy link

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (2)
src/app/(crew)/crew/create/_components/create-crew-form/index.tsx (2)

5-5: 폼 데이터 지속성 구현이 추가되었습니다.

useFormPersist 훅을 사용하여 폼 데이터를 로컬 스토리지에 저장하는 기능이 구현되었습니다. 이는 사용자 경험을 개선하는 좋은 접근이지만, 민감한 데이터 처리에 대한 고려가 필요합니다.

다음과 같은 개선사항을 고려해보세요:

  • 저장된 데이터의 만료 시간 설정
  • 폼 제출 후 저장된 데이터 삭제
  • 민감한 정보 필터링

Also applies to: 47-51


139-139: 드롭다운 플레이스홀더 로직이 개선되었습니다.

편집 모드에서 현재 값을 플레이스홀더로 표시하는 기능이 추가되었습니다. 하지만 조건부 렌더링 로직이 각 드롭다운마다 중복되어 있습니다.

다음과 같은 리팩토링을 제안합니다:

const getPlaceholder = (isEdit: boolean, value: string | null, defaultText: string) => 
  isEdit && value ? value : defaultText;

Also applies to: 158-158, 221-221, 241-241

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between c58777e and 062362b.

📒 Files selected for processing (3)
  • src/app/(crew)/crew/create/_components/create-crew-form/index.tsx (10 hunks)
  • src/app/(crew)/crew/create/page.tsx (2 hunks)
  • src/app/(crew)/crew/detail/[id]/edit/page.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app/(crew)/crew/detail/[id]/edit/page.tsx
🔇 Additional comments (5)
src/app/(crew)/crew/create/page.tsx (1)

20-34: ⚠️ Potential issue

이미지 업로드 에러 처리 관련 이전 리뷰 코멘트가 여전히 유효합니다

데이터 유효성 검증이 필요합니다

제출 전 필수 필드에 대한 유효성 검증이 누락되어 있습니다. 특히 newData 객체 생성 시 필수 값들이 존재하는지 확인이 필요합니다.

다음과 같은 검증 로직 추가를 제안합니다:

  const handleSubmit = async (createdData: CreateCrewFormTypes) => {
+   const requiredFields = ['title', 'mainCategory', 'mainLocation', 'totalCount'];
+   const missingFields = requiredFields.filter(field => !createdData[field]);
+   
+   if (missingFields.length > 0) {
+     console.error('필수 필드 누락:', missingFields);
+     // TODO: 사용자에게 필수 필드 입력 요청 메시지 표시
+     return;
+   }

    let newImageUrl = createdData.imageUrl as string;
    // ... 나머지 코드
src/app/(crew)/crew/create/_components/create-crew-form/index.tsx (4)

19-20: 타입 정의가 개선되었습니다만, 중복된 상태 체크가 있습니다.

type 프로퍼티가 추가되어 폼의 용도가 명확해졌습니다. 하지만 isEdit prop이 여전히 존재하여 중복된 상태 체크가 발생할 수 있습니다.

Also applies to: 27-28


287-287: 소개글 길이 체크 로직이 안전하게 개선되었습니다.

옵셔널 체이닝을 사용하여 introduce가 undefined일 때의 처리가 추가되었습니다. 이는 런타임 에러를 방지하는 좋은 개선입니다.


309-309: 버튼 텍스트가 명확하게 개선되었습니다.

폼의 용도에 따라 버튼 텍스트가 '만들기' 또는 '수정'으로 표시되도록 변경되었습니다. 이는 사용자 경험을 개선하는 좋은 변화입니다.


76-78: 서브로케이션 기본값 설정 로직 개선이 필요합니다.

빈 문자열일 경우 '전체'로 설정하는 로직이 추가되었습니다. 하지만 이 로직이 실제로 필요한지, 그리고 사용자에게 혼란을 줄 수 있는지 검토가 필요합니다.

✅ Verification successful

Let me gather more context about how subLocation is being used in the forms and related components.


Let me try a different search strategy to understand the subLocation handling better.


서브로케이션 '전체' 기본값 설정은 적절합니다.

코드베이스 분석 결과, 이 로직은 의도적으로 설계된 것으로 보입니다:

  • create/page.tsxedit/page.tsx에서 서브로케이션이 '전체'일 경우 빈 문자열로 변환하여 처리
  • 폼 컴포넌트에서는 반대로 빈 문자열을 '전체'로 설정
  • 이는 서버와 클라이언트 간의 일관된 데이터 처리를 위한 규약으로 보임

이 로직은 사용자 경험을 해치지 않으며, 데이터 일관성을 유지하는데 필요한 부분입니다.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# 다른 컴포넌트에서 subLocation이 '전체'로 설정된 경우의 처리 방식 확인
rg -A 3 "subLocation.*전체" 

Length of output: 1063


Script:

#!/bin/bash
# Check form validation and type definitions for subLocation
rg -A 5 "subLocation.*type|subLocation.*interface|subLocation.*Schema" 

# Check how subLocation is used in form submissions
rg -A 5 "subLocation.*submit|subLocation.*onSubmit"

# Check components that display or handle subLocation
rg -A 3 "subLocation.*render|subLocation.*select|subLocation.*option"

Length of output: 198


Script:

#!/bin/bash
# Check all references to subLocation to understand its usage
rg -A 5 "subLocation"

# Check form schema or validation related files
fd "schema|validation" --type f --exec rg -A 5 "subLocation" {}

# Look for location related component files
fd "location" --type f --exec rg -A 5 "subLocation|전체" {}

Length of output: 15892

Comment on lines 12 to 16
let savedInfo;
if (typeof window !== 'undefined') {
savedInfo = JSON.parse(localStorage.getItem('createCrew') ?? '');
}
const initialValue = { ...savedInfo };
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

로컬 스토리지 접근 시 에러 처리가 필요합니다

로컬 스토리지 접근과 JSON 파싱 시 발생할 수 있는 예외 상황에 대한 처리가 없습니다.

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

  let savedInfo;
  if (typeof window !== 'undefined') {
-   savedInfo = JSON.parse(localStorage.getItem('createCrew') ?? '');
+   try {
+     const stored = localStorage.getItem('createCrew');
+     savedInfo = stored ? JSON.parse(stored) : null;
+   } catch (error) {
+     console.error('로컬 스토리지 데이터 파싱 실패:', error);
+     savedInfo = 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
let savedInfo;
if (typeof window !== 'undefined') {
savedInfo = JSON.parse(localStorage.getItem('createCrew') ?? '');
}
const initialValue = { ...savedInfo };
let savedInfo;
if (typeof window !== 'undefined') {
try {
const stored = localStorage.getItem('createCrew');
savedInfo = stored ? JSON.parse(stored) : null;
} catch (error) {
console.error('로컬 스토리지 데이터 파싱 실패:', error);
savedInfo = null;
}
}
const initialValue = { ...savedInfo };

Comment on lines 60 to 65
<CreateCrewForm
data={initialValue}
onSubmit={handleSubmit}
type="create"
isEdit={savedInfo}
/>
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

🛠️ Refactor suggestion

isEdit prop의 타입이 올바르게 정의되어 있습니다

CreateCrewForm 컴포넌트의 prop 타입을 확인한 결과, isEditboolean 타입으로 정의되어 있습니다. 하지만 현재 savedInfo 객체를 전달하고 있어 타입 불일치가 발생합니다.

다음과 같이 수정이 필요합니다:

  • isEdit={!!savedInfo} 로 변경하여 객체를 boolean으로 변환
  • 또는 prop 이름을 savedData로 변경하고 타입도 적절히 수정
🔗 Analysis chain

prop 네이밍과 타입 검토가 필요합니다

isEdit prop에 savedInfo 객체를 전달하는 것이 직관적이지 않습니다. boolean 값을 기대하는 이름이지만 객체가 전달되고 있습니다.

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

  <CreateCrewForm
    data={initialValue}
    onSubmit={handleSubmit}
    type="create"
-   isEdit={savedInfo}
+   savedData={savedInfo}
  />

또한 CreateCrewForm 컴포넌트의 prop 타입 정의를 확인하고 업데이트하시기 바랍니다.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# CreateCrewForm 컴포넌트의 prop 타입 정의 확인
ast-grep --pattern 'interface $_ {
  $$$
  isEdit: $_
  $$$
}'

Length of output: 64


Script:

#!/bin/bash
# CreateCrewForm 컴포넌트와 관련된 타입 정의 찾기
rg -A 10 "CreateCrewForm.*Props" 

# CreateCrewForm 컴포넌트 구현부 찾기
ast-grep --pattern 'const CreateCrewForm = ($$$) => {
  $$$
}'

# isEdit prop 사용 패턴 찾기
rg "isEdit" -A 3 -B 3

Length of output: 17803

@github-actions
Copy link

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (4)
src/components/common/input/drop-down/index.tsx (1)

Line range hint 69-74: useEffect 최적화가 필요합니다

현재 구현에서 다음과 같은 잠재적인 문제가 있을 수 있습니다:

  1. valuenull일 때마다 빈 문자열로 onChange를 호출하는 것은 불필요한 리렌더링을 발생시킬 수 있습니다
  2. 부모 컴포넌트가 빈 문자열에 반응하여 상태를 업데이트하면 무한 루프가 발생할 수 있습니다

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

  useEffect(() => {
    if (value === null) {
-     onChange('');
      setCurrentValue(null);
    }
  }, [value]);

이렇게 수정하면 불필요한 상태 업데이트를 방지할 수 있습니다.

src/app/(crew)/crew/create/_components/create-crew-form/index.tsx (3)

88-99: 폼 초기화 시 사용자 확인 절차 필요

handleClear 함수 실행 시 사용자의 입력 데이터가 즉시 삭제됩니다. 사용자 경험 향상을 위해 확인 절차가 필요합니다.

다음과 같이 수정을 제안드립니다:

  const handleClear = () => {
+   if (!window.confirm('입력하신 내용이 모두 삭제됩니다. 계속하시겠습니까?')) {
+     return;
+   }
    setValue('title', '');
    setValue('mainCategory', '');
    // ... 나머지 코드
  };

316-316: 텍스트 길이 표시 로직 개선 필요

introduce?.lengthintroduceundefined일 때 0으로 표시되어야 합니다.

다음과 같이 수정을 제안드립니다:

- <span className="text-blue-500">{introduce?.length}</span>/100
+ <span className="text-blue-500">{introduce?.length ?? 0}</span>/100

Line range hint 338-346: 버튼 텍스트 상수화 필요

버튼의 텍스트가 하드코딩되어 있어 유지보수가 어려울 수 있습니다.

다음과 같이 수정을 제안드립니다:

+ const BUTTON_TEXT = {
+   create: '만들기',
+   edit: '수정',
+   cancel: '취소',
+ } as const;

- {type === 'create' ? '만들기' : '수정'}
+ {BUTTON_TEXT[type]}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 062362b and cbe2519.

📒 Files selected for processing (3)
  • src/app/(crew)/crew/create/_components/create-crew-form/index.tsx (10 hunks)
  • src/app/(crew)/crew/create/page.tsx (2 hunks)
  • src/components/common/input/drop-down/index.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app/(crew)/crew/create/page.tsx
🔇 Additional comments (2)
src/components/common/input/drop-down/index.tsx (1)

47-47: 스타일링 변경사항에 대한 검토가 필요합니다

비활성 상태의 배경색이 제거되었습니다. 이로 인해 다음 사항들을 확인해 주시기 바랍니다:

  1. 사용자가 드롭다운의 상태를 시각적으로 명확하게 인지할 수 있는지
  2. 디자인 시스템의 가이드라인과 일치하는지
  3. 접근성 측면에서 충분한 시각적 대비를 제공하는지

스타일링 변경이 디자인 시스템과 일치하는지 확인하기 위해 다음 스크립트를 실행하세요:

Also applies to: 52-52

✅ Verification successful

Let's examine the theme and tailwind configuration files to understand the design system guidelines.


스타일링 변경이 디자인 시스템과 일치합니다

검토 결과, text-gray-800 스타일은 다음과 같은 이유로 적절합니다:

  1. 디자인 시스템에서 일관되게 사용되는 색상입니다:
    • 제목, 라벨, 드롭다운 옵션 등 중요한 텍스트 요소에 사용
    • 충분한 대비를 제공하여 접근성 기준을 충족
  2. Tailwind 설정에서 gray-800#1F2937 색상으로, 읽기 쉬운 텍스트를 위한 적절한 명암비를 제공합니다.
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: 디자인 시스템 관련 파일에서 드롭다운 스타일링 규칙 확인

# 디자인 시스템 설정 파일 검색
fd -e ts -e js -e json "theme|style|design-system" src

# Tailwind 설정 파일 확인
fd "tailwind.config" .

Length of output: 120


Script:

#!/bin/bash
# Check theme.ts for dropdown related styles
cat src/styles/theme.ts

# Check tailwind config for color definitions
cat tailwind.config.ts

# Search for other dropdown components to compare styling
rg -g '*.tsx' -g '*.ts' 'text-gray-800' -A 2 -B 2

Length of output: 23137

src/app/(crew)/crew/create/_components/create-crew-form/index.tsx (1)

104-106: 조건부 로직 개선 필요

isEdittype 속성이 중복되어 사용되고 있습니다. 이전 리뷰 코멘트에서 지적된 사항이 아직 해결되지 않았습니다.

Comment on lines +78 to +86
const handleFileChange = async (
file: File | string | null,
onChange: (value: string | File) => void,
) => {
if (file instanceof File) {
const imgResponse = await getImageUrl(file, 'CREW');
onChange(imgResponse?.imageUrl || '');
}
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

파일 처리 로직에 오류 처리 추가 필요

getImageUrl API 호출 시 발생할 수 있는 오류에 대한 처리가 누락되어 있습니다.

다음과 같이 수정을 제안드립니다:

  const handleFileChange = async (
    file: File | string | null,
    onChange: (value: string | File) => void,
  ) => {
    if (file instanceof File) {
+     try {
        const imgResponse = await getImageUrl(file, 'CREW');
        onChange(imgResponse?.imageUrl || '');
+     } catch (error) {
+       console.error('이미지 업로드 실패:', error);
+       // TODO: 사용자에게 오류 메시지 표시
+     }
    }
  };
📝 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 handleFileChange = async (
file: File | string | null,
onChange: (value: string | File) => void,
) => {
if (file instanceof File) {
const imgResponse = await getImageUrl(file, 'CREW');
onChange(imgResponse?.imageUrl || '');
}
};
const handleFileChange = async (
file: File | string | null,
onChange: (value: string | File) => void,
) => {
if (file instanceof File) {
try {
const imgResponse = await getImageUrl(file, 'CREW');
onChange(imgResponse?.imageUrl || '');
} catch (error) {
console.error('이미지 업로드 실패:', error);
// TODO: 사용자에게 오류 메시지 표시
}
}
};

Comment on lines 48 to 55
if (typeof window !== 'undefined') {
// eslint-disable-next-line react-hooks/rules-of-hooks
useFormPersist('createCrew', {
watch,
setValue,
storage: window.localStorage,
});
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

폼 데이터 지속성 구현 위치 개선 필요

useFormPersist 훅이 조건부로 실행되고 있어 React Hooks 규칙을 위반할 수 있습니다. 컴포넌트 최상위 레벨에서 실행되도록 수정이 필요합니다.

다음과 같이 수정을 제안드립니다:

- if (typeof window !== 'undefined') {
-   // eslint-disable-next-line react-hooks/rules-of-hooks
-   useFormPersist('createCrew', {
-     watch,
-     setValue,
-     storage: window.localStorage,
-   });
- }

+ useEffect(() => {
+   if (typeof window !== 'undefined') {
+     useFormPersist('createCrew', {
+       watch,
+       setValue,
+       storage: window.localStorage,
+     });
+   }
+ }, [watch, setValue]);

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

@github-actions
Copy link

@github-actions
Copy link

@github-actions
Copy link

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (4)
src/app/(crew)/crew/create/_components/create-crew-form/index.tsx (4)

63-73: 폼 초기화 로직 개선이 필요합니다

초기화 과정에서 발생할 수 있는 예외 상황(예: localStorage 접근 실패)에 대한 처리가 필요합니다.

  const setInitialValues = () => {
+   try {
      setValue('title', '');
      setValue('mainCategory', '');
      setValue('subCategory', '');
      setValue('imageUrl', '');
      setValue('mainLocation', '');
      setValue('subLocation', '');
      setValue('totalCount', 4);
      setValue('introduce', '');
      localStorage.removeItem('createCrew');
+   } catch (error) {
+     console.error('폼 초기화 중 오류 발생:', error);
+   }
  };

106-108: 불필요한 리렌더링 최적화가 필요합니다

subLocation 기본값 설정 로직이 매 렌더링마다 실행될 수 있습니다. 의존성 배열에 isEditsubLocation을 추가하여 최적화하는 것이 좋습니다.

- if (isEdit && subLocation === '') {
-   setValue('subLocation', '전체');
- }
+ useEffect(() => {
+   if (isEdit && subLocation === '') {
+     setValue('subLocation', '전체');
+   }
+ }, [isEdit, subLocation, setValue]);

321-321: 소개글 길이 표시 로직 개선이 필요합니다

introduce가 undefined일 경우 길이 표시에 문제가 발생할 수 있습니다. 기본값을 제공하여 더 안정적으로 구현하는 것이 좋습니다.

- <span className="text-blue-500">{introduce?.length}</span>/100
+ <span className="text-blue-500">{(introduce || '').length}</span>/100

343-343: 버튼 텍스트를 상수로 분리하는 것이 좋습니다

폼 타입에 따른 버튼 텍스트를 상수 객체로 분리하면 유지보수성이 향상됩니다.

+ const SUBMIT_BUTTON_TEXT = {
+   create: '만들기',
+   edit: '수정'
+ } as const;

- {type === 'create' ? '만들기' : '수정'}
+ {SUBMIT_BUTTON_TEXT[type]}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between cbe2519 and 5313fd4.

📒 Files selected for processing (3)
  • src/app/(crew)/crew/create/_components/create-crew-form/index.tsx (10 hunks)
  • src/app/(crew)/crew/detail/[id]/edit/page.tsx (2 hunks)
  • src/components/common/input/drop-down/index.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/app/(crew)/crew/detail/[id]/edit/page.tsx
  • src/components/common/input/drop-down/index.tsx
🔇 Additional comments (2)
src/app/(crew)/crew/create/_components/create-crew-form/index.tsx (2)

48-52: 폼 데이터 지속성 구현이 적절합니다!

폼 타입에 따라 스토리지 키를 구분하고, 브라우저 환경을 확인하는 방식이 안전하게 구현되었습니다.


87-95: 이전 리뷰 의견이 여전히 유효합니다

파일 업로드 처리 시 발생할 수 있는 오류에 대한 처리가 필요합니다.

@github-actions
Copy link

@github-actions
Copy link

@yulrang yulrang merged commit d207114 into develop Nov 19, 2024
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

크루 수정하기 API 연결

4 participants