-
Notifications
You must be signed in to change notification settings - Fork 3
Feat/128/edit crew api 연결 #129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Walkthrough이 변경 사항은 Changes
Possibly related PRs
Suggested labels
Suggested reviewers
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
src/app/layout.tsxOops! Something went wrong! :( ESLint: 8.57.1 ESLint couldn't determine the plugin "react-hooks" uniquely.
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 detailsConfiguration used: CodeRabbit UI ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
🔇 Additional comments (2)src/app/layout.tsx (2)
성능 모니터링을 위한 SpeedInsights 통합이 적절하게 구현되었습니다.
MantineProvider 내부의 컴포넌트 구조가 올바르게 구성되어 있으며, SpeedInsights가 적절한 위치에 배치되었습니다. 다만, 성능 모니터링 도구의 영향을 확인하는 것이 좋습니다. ✅ Verification successfulSpeedInsights 구성이 올바르게 되어 있음을 확인했습니다 SpeedInsights가 다음과 같이 적절하게 구성되어 있습니다:
🏁 Scripts executedThe 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? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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대신isLoading과error를 사용하는 방식으로 변경한 것이 좋습니다. 상태를 더 명확하게 표현할 수 있게 되었습니다.타입 안전성을 위해 다음과 같은 개선을 제안드립니다:
+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: 타입 정의 단순화를 제안드립니다
EditCrewRequestTypes가CreateCrewRequestTypes를 그대로 확장만 하고 있습니다. 추가 속성이 없다면 다음과 같이 타입 별칭을 사용하는 것이 더 명확할 수 있습니다:-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: 코드 재사용성 개선을 위한 제안
createCrew와editCrew함수가 유사한 에러 처리와 응답 처리 로직을 공유하고 있습니다. 이를 공통 유틸리티 함수로 추출하면 코드 중복을 줄이고 일관성을 향상시킬 수 있습니다.다음과 같은 유틸리티 함수 생성을 고려해보세요:
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: 로딩 및 에러 상태 처리가 개선되었습니다.로딩 상태와 에러 처리가 더 직관적으로 구현되었습니다. 다만 몇 가지 제안사항이 있습니다:
- 에러 메시지를 더 구체적으로 표시하면 좋을 것 같습니다.
- 로딩 상태일 때 접근성을 위한 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 로직 개선이 필요합니다.현재 구현은 작동하지만, 다음과 같은 개선사항을 제안드립니다:
- 반복되는 조건부 로직을 하나의 유틸리티 함수로 추출
- 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: 문자열 길이 계산 로직 보완이 필요합니다.옵셔널 체이닝을 사용한 것은 좋으나, 다음과 같은 보완이 필요해 보입니다:
- 기본값 처리
- 공백 문자 처리
다음과 같이 개선하는 것을 추천드립니다:
<span className="text-blue-500"> {(introduce?.trim() || '').length} </span>
Line range hint
1-305: 컴포넌트 구조 개선을 제안드립니다.현재 컴포넌트가 너무 많은 책임을 가지고 있어 다음과 같은 구조적 개선을 제안드립니다:
- 폼 검증 로직을 별도의 훅으로 분리
- 에러 처리 로직을 중앙화
- 파일 업로드 로직을 별도의 컴포넌트로 분리
이를 통해 코드의 재사용성과 유지보수성을 높일 수 있습니다.
다음과 같은 구조로 리팩토링하는 것을 추천드립니다:
// 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: 중복된 로직을 재사용하여 코드 간결화
useCreateCrewQuery와useEditCrewQuery함수에서 유사한 로직이 반복되고 있습니다. 공통 부분을 추출하여 재사용 가능한 훅이나 함수로 분리하면 코드의 유지보수성이 향상됩니다.Also applies to: 35-53
src/app/(crew)/crew/detail/[id]/edit/page.tsx (3)
39-44: 로딩 상태 처리 로직 개선 제안현재
isLoading또는isPending이true일 때 로더를 표시하고 있습니다. 그러나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
📒 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 대신 isLoading과 error를 사용하도록 변경한 것이 더 명확한 상태 관리를 가능하게 합니다. 이는 컴포넌트에서 로딩 상태와 에러 상태를 더 직관적으로 처리할 수 있게 해줍니다.
이 변경으로 인한 영향을 확인하기 위해 다음 스크립트를 실행하세요:
✅ Verification successful
Let me gather more information about the implementation details to ensure there are no remaining status usages.
useInfiniteQuery 반환값 변경이 안전하게 적용되었습니다
모든 사용처에서 status 대신 isLoading과 error를 올바르게 사용하고 있으며, 다음 컴포넌트들이 이미 새로운 API를 적용하고 있습니다:
src/app/(crew)/my-crew/joined/page.tsxsrc/app/(crew)/my-crew/hosted/page.tsxsrc/app/(crew)/page.tsxsrc/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 대신 isLoading과 error를 분리하여 사용함으로써 상태 관리가 더 명확해졌습니다. 페이지네이션 파라미터도 적절하게 설정되어 있습니다.
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.
mainCategory와 subCategory 필드는 관련 인터페이스들과 일관성이 있습니다.
검증 결과, 새로 추가된 필드들은 다음 인터페이스들에서 동일한 방식으로 사용되고 있습니다:
MainCrewList인터페이스:mainCategory?: string,subCategory?: stringCrewDetail인터페이스:mainCategory?: string,subCategory?: stringCreateCrewFormTypes인터페이스: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 대신 isLoading과 error를 직접 사용하는 방식이 더 직관적입니다.
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 대신 isLoading과 error로 분리한 것이 좋은 개선사항입니다. 이는 더 명확한 상태 관리를 가능하게 합니다.
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>} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
| {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> | |
| )} |
| export interface EditCrewResponseTypes { | ||
| title: string; | ||
| mainCategory: string; | ||
| subCategory: string; | ||
| mainLocation: string; | ||
| subLocation: string; | ||
| totalCount: number; | ||
| introduce: string; | ||
| imageUrl: string; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Based on the gathered evidence, I can now provide the final response:
타입 정의가 실제 사용 사례와 일치하지 않습니다
코드베이스 분석 결과:
subCategory와subLocation은 폼에서null값을 허용하고 있습니다 (setValue('subCategory', null)사용 확인)- 생성 페이지에서도 초기값으로
null을 사용합니다 - 하지만
EditCrewResponseTypes에서는 이 필드들이 필수 문자열로 정의되어 있습니다
수정 제안:
EditCrewResponseTypes의 타입을 다음과 같이 수정해야 합니다:
subCategory: string | null;
subLocation: string | null;🔗 Analysis chain
타입 일관성 검토가 필요합니다
CreateCrewFormTypes와 비교했을 때 몇 가지 불일치가 있습니다:
subCategory와subLocation이string | null대신string으로 정의되어 있습니다imageUrl이File | 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
| 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
editCrew 함수의 구현을 개선해야 합니다.
다음과 같은 중요한 개선사항들이 필요합니다:
- 응답 처리가 누락되어 있습니다
- 에러 처리가 미흡합니다
- 반환 타입이 명시되어 있지 않습니다
다음과 같이 수정하는 것을 제안드립니다:
-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)
| 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, | ||
| }; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이미지 업로드 실패에 대한 에러 처리가 필요합니다.
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)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
| 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'] }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
쿼리 무효화 시 올바른 사용법 적용
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.
| queryClient.invalidateQueries({ queryKey: ['crewLists', 'crewDetail'] }); | |
| queryClient.invalidateQueries(['crewLists']); | |
| queryClient.invalidateQueries(['crewDetail']); |
| mutationFn: (data: EditCrewRequestTypes) => editCrew(id, data), | ||
| onSuccess: () => { | ||
| queryClient.invalidateQueries({ queryKey: ['crewDetail'] }); | ||
| toast.success('크루 정보가 수정되었습니다.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
| 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; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TypeScript 타입 캐스팅 관련 오류 수정 필요
editedData.imageUrl가 File 타입일 때, 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.
minkyung5x5
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수고많으셨습니다!!! 짱짱
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.jsonLength of output: 810
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 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 대신 isLoading과 error를 사용하는 방식으로 변경된 것이 더 명확하고 현대적인 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 배열에 "전체" 옵션이 추가되어 다른 지역들과 동일한 구조를 갖게 되었습니다.
| if (editedData.imageUrl instanceof File && newImageUrl !== data.imageUrl) { | ||
| const imgResponse = await getImageUrl(editedData.imageUrl, 'CREW'); | ||
| newImageUrl = imgResponse?.imageUrl as string; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이미지 업로드 응답에 대한 오류 처리가 필요합니다.
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.
| 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} /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 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>} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 문이 기능별로 잘 구분되어 있습니다. 다만 가독성을 위해 다음과 같은 순서로 정리하는 것을 제안드립니다:
- React 관련 import
- 외부 라이브러리 import
- 내부 API/쿼리 import
- 컴포넌트 import
- 타입 import
- 에셋 import
39-44: 로딩 상태 UI 개선 필요현재 로딩 상태 UI가 너무 단순합니다. 사용자 경험 향상을 위해 다음 사항들을 고려해보세요:
- 로딩 중임을 알리는 텍스트 추가
- 배경 오버레이 추가
- 접근성 고려 (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 응답 타입과 일치하는지 확인이 필요합니다. 특히 새로 추가된
gatheringCaptain과participant필드의 타입이 백엔드 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이렇게 하면
introduce가undefined나null일 때도 안전하게 0을 표시할 수 있습니다.
Line range hint
35-42: 폼 유효성 검사 규칙의 중앙화가 필요합니다.현재 유효성 검사 규칙이 컴포넌트 내부에 산재되어 있습니다. 이는 규칙의 재사용과 일관성 유지를 어렵게 만들 수 있습니다.
다음과 같은 구조적 개선을 제안드립니다:
- 별도의 validation 스키마 파일을 생성하여 유효성 검사 규칙을 중앙 관리
- 스키마를 react-hook-form의 resolver로 사용
- 이를 통해 폼 유효성 검사의 재사용성과 유지보수성 향상
예시 구조:
// validations/crew-form.ts export const crewFormValidationSchema = { title: { required: '필수 입력사항입니다.', maxLength: { value: 20, message: '20자 이내로 입력해주세요.' } }, // ... 다른 필드들의 유효성 검사 규칙 };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 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 주석으로 표시되어 있습니다:
- modalData 연결
- 좋아요 토글 로직
- 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(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 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)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
handleEdit 함수의 타입 안전성 및 에러 처리 개선 필요
- 이전 리뷰 코멘트에서 지적된 타입 캐스팅 문제가 아직 해결되지 않았습니다.
- 이미지 업로드 실패에 대한 에러 처리가 필요합니다.
- 옵셔널 체이닝 연산자 사용 시 기본값 처리가 불완전합니다.
다음과 같이 수정을 제안합니다:
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.
| 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); | |
| // 에러 처리 로직 추가 (예: 토스트 메시지 표시) | |
| } | |
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
⛔ Files ignored due to path filters (1)
package-lock.jsonis 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" />; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
| return <CreateCrewForm data={initialValue} type="create" />; | |
| const formType = 'create' as const; | |
| return <CreateCrewForm data={initialValue} type={formType} />; |
| type: 'create' | 'edit'; | ||
| data: CreateCrewFormTypes | EditCrewResponseTypes; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
📒 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:
이미지 업로드 에러 처리 관련 이전 리뷰 코멘트가 여전히 유효합니다
데이터 유효성 검증이 필요합니다
제출 전 필수 필드에 대한 유효성 검증이 누락되어 있습니다. 특히 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.tsx와edit/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
src/app/(crew)/crew/create/page.tsx
Outdated
| let savedInfo; | ||
| if (typeof window !== 'undefined') { | ||
| savedInfo = JSON.parse(localStorage.getItem('createCrew') ?? ''); | ||
| } | ||
| const initialValue = { ...savedInfo }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
로컬 스토리지 접근 시 에러 처리가 필요합니다
로컬 스토리지 접근과 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.
| 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 }; |
| <CreateCrewForm | ||
| data={initialValue} | ||
| onSubmit={handleSubmit} | ||
| type="create" | ||
| isEdit={savedInfo} | ||
| /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
🛠️ Refactor suggestion
isEdit prop의 타입이 올바르게 정의되어 있습니다
CreateCrewForm 컴포넌트의 prop 타입을 확인한 결과, isEdit은 boolean 타입으로 정의되어 있습니다. 하지만 현재 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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 최적화가 필요합니다현재 구현에서 다음과 같은 잠재적인 문제가 있을 수 있습니다:
value가null일 때마다 빈 문자열로onChange를 호출하는 것은 불필요한 리렌더링을 발생시킬 수 있습니다- 부모 컴포넌트가 빈 문자열에 반응하여 상태를 업데이트하면 무한 루프가 발생할 수 있습니다
다음과 같이 수정하는 것을 제안합니다:
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?.length는introduce가undefined일 때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
📒 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: 스타일링 변경사항에 대한 검토가 필요합니다
비활성 상태의 배경색이 제거되었습니다. 이로 인해 다음 사항들을 확인해 주시기 바랍니다:
- 사용자가 드롭다운의 상태를 시각적으로 명확하게 인지할 수 있는지
- 디자인 시스템의 가이드라인과 일치하는지
- 접근성 측면에서 충분한 시각적 대비를 제공하는지
스타일링 변경이 디자인 시스템과 일치하는지 확인하기 위해 다음 스크립트를 실행하세요:
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 스타일은 다음과 같은 이유로 적절합니다:
- 디자인 시스템에서 일관되게 사용되는 색상입니다:
- 제목, 라벨, 드롭다운 옵션 등 중요한 텍스트 요소에 사용
- 충분한 대비를 제공하여 접근성 기준을 충족
- 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: 조건부 로직 개선 필요
isEdit와 type 속성이 중복되어 사용되고 있습니다. 이전 리뷰 코멘트에서 지적된 사항이 아직 해결되지 않았습니다.
| 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 || ''); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
| 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: 사용자에게 오류 메시지 표시 | |
| } | |
| } | |
| }; |
| if (typeof window !== 'undefined') { | ||
| // eslint-disable-next-line react-hooks/rules-of-hooks | ||
| useFormPersist('createCrew', { | ||
| watch, | ||
| setValue, | ||
| storage: window.localStorage, | ||
| }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
폼 데이터 지속성 구현 위치 개선 필요
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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기본값 설정 로직이 매 렌더링마다 실행될 수 있습니다. 의존성 배열에isEdit와subLocation을 추가하여 최적화하는 것이 좋습니다.- 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
📒 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: 이전 리뷰 의견이 여전히 유효합니다
파일 업로드 처리 시 발생할 수 있는 오류에 대한 처리가 필요합니다.
🔖 Issue Ticket
#128
✍️ Description
크루 수정하기 API 연결했습니다~
✅ Checklist
PR
Test
Summary by CodeRabbit
릴리스 노트
신규 기능
useCreateCrewQuery,useEditCrewQuery) 추가.CreateCrewForm컴포넌트에 생성 및 편집 모드 구분 기능 추가.DropDown컴포넌트의 스타일 및 상태 관리 로직 개선.SpeedInsights컴포넌트를 통해 성능 모니터링 기능 추가.버그 수정
문서화
EditCrewResponseTypes,EditCrewRequestTypes) 추가로 데이터 구조 명확화.리팩토링