-
Notifications
You must be signed in to change notification settings - Fork 0
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
[feat] 명함 생성 도메인 구현 #73
Conversation
카드 생성용
📝 WalkthroughWalkthrough이번 PR은 여러 컴포넌트와 유틸리티의 업데이트 및 신규 기능 추가를 포함합니다.
Changes
Sequence Diagram(s)sequenceDiagram
participant U as 사용자
participant F as MultiStepFormView
participant C as Form Controller
participant D as createCareerFormData (utils)
participant Q as useCreateCard 훅
participant API as API 서버
U->>F: 폼 작성 후 Next 버튼 클릭
F->>C: 폼 유효성 검사 요청
C-->>F: 유효성 검사 결과 반환
F->>D: 폼 데이터 변환 요청
D-->>F: 변환된 FormData 반환
F->>Q: 카드 생성 API 호출
Q->>API: POST 요청 전송
API-->>Q: 응답 전송
Q-->>F: 처리 결과 반환
sequenceDiagram
participant U as 사용자
participant T as TagBox
participant Z as Zustand 스토어
U->>T: 태그 클릭
T->>Z: handleTagClick 호출
Z-->>Z: tagArray 및 tagCount 업데이트
Z-->>T: 업데이트된 상태 반환
T-->>U: UI 갱신 반영
Possibly related PRs
Suggested labels
Suggested reviewers
Tip ⚡🧪 Multi-step agentic review comment chat (experimental)
✨ Finishing Touches
🪧 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
Documentation and Community
|
기존의 wrappedInput에 hook-form value, onChange를 전달 받을수 있게 한다.
3단계의 태그 내용을 가져와서 validation을 처리하기 위해 전역상태를 사용하였습니다.
register API의 드롭다운 content를 연동하기 위해 전역상태를 사용하여 payload 처리
이미지 File 타입, zod validation 수정
EditableCardField 컴포넌트를 만들어 응집도를 높이고, 관심사 분리를 진행하였습니다.
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: 7
🔭 Outside diff range comments (1)
src/features/multi-step-form/ui/careerForm/secondStep.tsx (1)
37-38
:⚠️ Potential issue
setTagCount
의 사용에 문제가 있습니다
setTagCount
는 실제로incrementTagCount
함수이기 때문에 0으로 설정하는 목적으로 사용하면 안 됩니다. 상태 초기화에는resetTagArray
함수를 사용하는 것이 더 적절합니다.다음과 같이 수정하는 것이 좋겠습니다:
- setTagCount(0); - setTagArray([]); + useCardFormStore.getState().resetTagArray();
🧹 Nitpick comments (42)
src/features/new-card/config/index.tsx (1)
13-14
: 라우트 경로 명명 규칙 개선직군 관련 라우트 키를 소문자(
design
,dev
)에서 대문자(DESIGNER
,DEVELOPER
)로 변경한 것은 상수 네이밍 컨벤션에 더 적합합니다. 이 변경으로 상수임을 명확히 식별할 수 있게 되었습니다. 다만,CAREER_SELECT
의listText
객체 내 키(line 7-8)도 함께 대문자로 변경하는 것이 일관성을 유지하는 데 도움이 될 것입니다.
CAREER_SELECT
의listText
내부 키도 같은 네이밍 컨벤션을 따르도록 수정하는 것이 좋겠습니다:export const CAREER_SELECT = { title: '직군을 선택해 주세요', description: '직군에 맞는 템플릿으로 내 명함을 만들 수 있어요.', listText: { - design: '디자인 직군', - dev: '개발 직군', + DESIGNER: '디자인 직군', + DEVELOPER: '개발 직군', }, };src/features/multi-step-form/components/Cardview.tsx (1)
6-12
: 타입 정의 개선 제안
onCloseClick
속성은 옵셔널하지만 실제로는index !== 0
일 때만 사용됩니다. 타입 정의를 더 명확하게 개선할 수 있습니다.조건부 타입을 사용하여 더 엄격한 타입 체크를 구현할 수 있습니다:
type CardViewProps = { index: number; title: string; link: string; - onCloseClick?: () => void; + onCloseClick: index extends 0 ? undefined : () => void; onClick: () => void; };또는 간단하게:
type CardViewProps = { index: number; title: string; link: string; onCloseClick?: () => void; onClick: () => void; }; + + // 컴포넌트 내부에서 실행 전 확인 + const handleCloseClick = (e: React.MouseEvent) => { + e.stopPropagation(); + if (onCloseClick) onCloseClick(); + }src/features/new-card/ui/newCreateCardView.tsx (2)
17-20
: 디자이너 카드 생성 핸들러 개선라우팅 로직이 개선되었고 상태 관리가 추가되었습니다. 하지만 코드 가독성을 더 높이기 위해 라우팅과 상태 관리 로직을 더 명확하게 분리하는 것이 좋을 수 있습니다.
const handleCreateDesignCard = () => { - router.push(ROUTE_PATH.DESIGNER); - setJob('DESIGNER'); + const jobType = 'DESIGNER'; + setJob(jobType); + router.push(ROUTE_PATH.DESIGNER); };
22-25
: 개발자 카드 생성 핸들러 개선디자이너 핸들러와 마찬가지로, 이 함수도 가독성을 높이기 위해 리팩토링을 고려해 볼 수 있습니다.
const handleCreateDevCard = () => { - router.push(ROUTE_PATH.DEVELOPER); - setJob('DEVELOPER'); + const jobType = 'DEVELOPER'; + setJob(jobType); + router.push(ROUTE_PATH.DEVELOPER); };src/shared/hooks/useOnClickOutside.ts (2)
10-17
: 이벤트 핸들러 구현의 안전성 확보이벤트 핸들러가 ref 유효성과 내부 클릭 여부를 안전하게 확인합니다. 다만, 타입 단언(as Node)을 사용하는 대신 더 안전한 타입 가드 패턴을 고려할 수 있습니다.
const handleEvent = (event: MouseEvent | TouchEvent) => { - if (!ref.current || ref.current.contains(event.target as Node)) { + const target = event.target; + if (!ref.current || !target || ref.current.contains(target as Node)) { return; } callback(event); };
19-27
: 이벤트 리스너 관리의 최적화이벤트 리스너 등록 및 정리 로직이 적절히 구현되어 있습니다. 다만, 성능 최적화를 위해 passive 옵션을 추가하는 것을 고려해 볼 수 있습니다.
// mousedown과 touchstart 이벤트 등록 -document.addEventListener('mousedown', handleEvent); -document.addEventListener('touchstart', handleEvent); +document.addEventListener('mousedown', handleEvent, { passive: true }); +document.addEventListener('touchstart', handleEvent, { passive: true }); // cleanup: 언마운트 시 이벤트 제거 return () => { - document.removeEventListener('mousedown', handleEvent); - document.removeEventListener('touchstart', handleEvent); + document.removeEventListener('mousedown', handleEvent, { passive: true }); + document.removeEventListener('touchstart', handleEvent, { passive: true }); };src/features/multi-step-form/config/index.ts (1)
22-22
: 상수 값의 의미 명확화가 필요합니다.
MAXIMUM_ADD
상수가 추가되었지만, 이 값이 정확히 무엇을 제한하는지 명확하지 않습니다. 주석이나 더 구체적인 이름을 통해 이 상수의 목적을 명확히 하는 것이 좋겠습니다.- export const MAXIMUM_ADD = 10; + // 한 명함에 추가할 수 있는 최대 태그 개수 + export const MAXIMUM_TAG_ADD = 10;src/shared/ui/tag/tag.tsx (2)
11-11
: 새로운 TagSize 옵션이 추가되었습니다.'create-input' 사이즈 옵션이 추가되었지만, JSDoc 문서는 업데이트되지 않았습니다. 다른 개발자들이 이 새로운 옵션을 인지할 수 있도록 문서를 갱신해주세요.
JSDoc(26줄)을 다음과 같이 업데이트해 주세요:
- @param {"sm" | "md" | "lg"} [props.size="md"] - 태그의 크기를 설정합니다. + @param {"sm" | "md" | "lg" | "create-input"} [props.size="md"] - 태그의 크기를 설정합니다.
65-65
: Typography 변형 로직 업데이트가 필요합니다.Typography 컴포넌트의 variant 선택 로직에 새로 추가된 'create-input' 사이즈 케이스가 처리되지 않았습니다. 'create-input'의 텍스트 스타일도 고려해야 합니다.
- <Typography variant={size === 'lg' ? 'body-5' : size === 'md' ? 'body-5' : 'caption-1'}>{message}</Typography> + <Typography variant={size === 'lg' ? 'body-5' : size === 'md' ? 'body-5' : size === 'create-input' ? 'caption-1' : 'caption-1'}>{message}</Typography>src/features/multi-step-form/ui/careerForm/tagFormStep/ui/MotionTagBox.tsx (1)
14-27
: 애니메이션 variant 주석 처리가 좋습니다.애니메이션 variant에 대한 명확한 주석이 추가되어 코드 이해도를 높입니다. 다만,
selected
와tap
상태에 대한 주석도 추가하면 더 완벽할 것 같습니다.다음과 같이 나머지 상태에 대한 주석도 추가하는 것이 좋겠습니다:
// unselected: 태그가 부모 중앙에서 초기 오프셋(tag.initialPosition)만큼 떨어져 있다. + // selected: 태그가 부모 중앙에서 최종 오프셋(tag.endPosition)으로 이동한다. + // tap: 태그를 누를 때 살짝 축소되는 효과를 준다.src/features/multi-step-form/hooks/queries/useScrap.ts (2)
21-32
: postScapLink 함수 구현이 잘 되었습니다.API 요청 구현이 적절하게 되어 있으며, 헤더와 쿼리 파라미터 설정도 잘 되어 있습니다. 다만, 함수 이름에 오타가 있습니다.
- const postScapLink = async ({ payload, type }: { payload: ScrapPayload; type: ScrapType }) => { + const postScrapLink = async ({ payload, type }: { payload: ScrapPayload; type: ScrapType }) => {
34-40
: useScrap 훅의 에러 처리가 미흡합니다.
onSuccess
와onError
핸들러가 빈 함수로 구현되어 있습니다. 사용자에게 성공/실패 피드백을 제공하거나 에러 로깅을 추가하는 것이 좋겠습니다.return useMutation<ApiResponseType<ScrapResponse>, unknown, { payload: ScrapPayload; type: ScrapType }>({ mutationFn: postScapLink, - onSuccess: () => {}, - onError: () => {}, + onSuccess: (data) => { + console.log('Scrap link posted successfully:', data); + // 성공 메시지 표시 또는 추가 작업 수행 + }, + onError: (error) => { + console.error('Failed to post scrap link:', error); + // 에러 메시지 표시 또는 실패 처리 + }, });src/shared/ui/textArea/index.tsx (2)
8-31
: 텍스트 영역의 스타일 변형이 효과적으로 구성되어 있습니다.텍스트 에리어의 다양한 크기 변형과 에러 상태 스타일링이 잘 정의되어 있습니다. 단, 기본 크기가 'xs'로 설정되어 있어 높이가 매우 작을 수 있습니다(12px). 실제 사용 시 사용자 경험을 고려하여 기본값을 더 큰 사이즈로 변경하는 것이 좋을 수 있습니다.
defaultVariants: { - size: 'xs', + size: 'md', },
39-69
: 텍스트 영역 컴포넌트의 구현이 완성도 높습니다.forwardRef를 사용하여 DOM 접근성을 제공하고, 문자 수 카운팅 및 에러 메시지 표시 기능을 통합한 점이 좋습니다. 하지만
currentTextLength
계산 시 null 값에 대한 안전한 처리가 되어 있지만, 보다 명확하게 할 수 있습니다.- const currentTextLength = value?.toString().length ?? 0; + const currentTextLength = value ? value.toString().length : 0;또한
totalNumber
prop에 대한 유효성 검사를 추가하면 좋을 것 같습니다.src/shared/ui/Input/input.tsx (1)
26-29
: InputBodyProps 타입 수정 및 export 처리가 적절합니다.타입을 외부로 내보내고 error 속성을 추가한 것은 좋은 변경입니다. 다만, Textarea 컴포넌트처럼 errorMsg 속성도 추가하면 UX 관점에서 더 완성도 있을 것 같습니다.
export type InputBodyProps = React.ComponentProps<'input'> & { variant?: 'default' | 'withBtn'; // variant 타입 추가 error?: boolean; + errorMsg?: string; };
참고로, 주석은 가능하면 영어로 통일하는 것이 국제적인 코드베이스 관리에 도움이 됩니다.
export type InputBodyProps = React.ComponentProps<'input'> & { - variant?: 'default' | 'withBtn'; // variant 타입 추가 + variant?: 'default' | 'withBtn'; // Added variant type error?: boolean; };src/shared/store/cardFormState.ts (2)
36-49
: 태그 클릭 핸들러 구현이 효과적입니다.태그 토글 기능이 간결하게 구현되어 있습니다. 다만,
tagCount
는tagArray.length
로 도출될 수 있어 상태 중복이 있습니다. 추후 확장성을 고려한다면tagCount
를 파생 값으로 계산하는 방법을 고려해볼 수 있습니다.또한, 코드 내 한글 주석은 코드 베이스의 일관성을 위해 영어로 통일하는 것이 좋습니다.
- // 태그 클릭 핸들러 + // Tag click handler handleTagClick: (tagMessage: TagValue) => {
50-56
: resetTagArray 메소드에 대한 주석이 한글로 되어 있습니다.코드 일관성을 위해 영어 주석으로 변경하는 것이 좋습니다.
- // 태그 카운트와 태그 배열 초기화 + // Reset tag count and tag array resetTagArray: () =>src/features/multi-step-form/ui/careerForm/firstStep.tsx (1)
90-90
: 오타 수정 필요"괸심"을 "관심"으로 수정해야 합니다.
- placeholder="어떤 분야에 괸심이 있나요?" + placeholder="어떤 분야에 관심이 있나요?"src/features/multi-step-form/components/EditableCardField.tsx (2)
101-101
: 주석 중복 제거 필요
// // 스크래핑 함수
주석에서 이중 슬래시를 제거해야 합니다.- // // 스크래핑 함수 + // 스크래핑 함수
148-157
: 키 이벤트 처리 로직 개선 필요
handleKeyDown
함수에서 Enter 키가 눌렸을 때 편집 모드를 활성화하는 로직은 입력 필드에서 일반적으로 기대되는 동작(폼 제출)과 충돌할 수 있습니다. 현재 편집 모드 상태를 먼저 확인하는 것이 좋습니다.const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { // Enter key /** * 현재는 Enter키만 지원하지만 추후 다른 키도 지원할 수 있도록 수정 가능 */ if (e.key === 'Enter') { e.preventDefault(); + if (!editingStates[index]) { enableEditing(); + } } };src/features/new-card/hooks/queries/useRegisterQuery.ts (2)
26-32
: API 호출 함수가 간결합니다.
_getRegister
함수가 간결하게 구현되어 있습니다. 단, 에러 처리 로직이 보이지 않습니다.에러 처리를 위한 코드를 추가하면 좋을 것 같습니다:
const _getRegister = async (params: RegisterQueryParams) => { - const data = await client.get<ApiResponseType<RegisterResponse>>(`${CLIENT_SIDE_URL}/api/card/register`, { - params, - }); - - return data; + try { + const data = await client.get<ApiResponseType<RegisterResponse>>(`${CLIENT_SIDE_URL}/api/card/register`, { + params, + }); + + return data; + } catch (error) { + console.error('Failed to fetch register data:', error); + throw error; + } };
34-49
: useQuery 구성이 잘 되어 있습니다.React Query의
useQuery
훅을 사용한 구현이 좋습니다. 특히:
enabled
옵션을 통해 필요한 경우에만 쿼리가 실행되도록 했습니다.select
함수를 통해 응답 데이터를 필요한 형태로 변환해주어 컴포넌트에서 바로 사용할 수 있게 했습니다.다만, 쿼리 실패 시 재시도 로직이나 에러 핸들링은 추가하는 것이 좋을 것 같습니다.
export const useRegisterQuery = (params: RegisterQueryParams) => { return useQuery({ queryKey: [CARD_REGISTER, params], queryFn: () => _getRegister(params), enabled: !!params.job, + retry: 2, + retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000), + onError: (error) => { + console.error('Register query failed:', error); + // 필요시 에러 알림 표시 + }, select: (data) => data.data.careers.map(({ detailJobEn, id, detailJobKr }) => { return { id: id, label: detailJobEn, keywords: detailJobKr, value: detailJobEn, }; }), }); };src/features/multi-step-form/components/AvartarImg.tsx (1)
23-56
: 파일 업로드 처리가 효율적입니다.
Controller
컴포넌트를 사용하여 파일 입력을 폼과 통합한 접근 방식이 매우 효과적입니다. 특히:
- 파일 객체를 폼 상태에 저장하면서 미리보기용 Data URL을 별도로 관리하는 전략이 깔끔합니다.
- 주석을 통해 코드의 의도를 명확히 설명했습니다.
하지만 파일 크기 제한이나 이미지 타입 검증 등의 유효성 검사가 추가되면 좋을 것 같습니다.
onChange={(e) => { const file = e.target.files?.[0]; if (file) { + // 파일 크기 검증 (예: 5MB 제한) + if (file.size > 5 * 1024 * 1024) { + alert('파일 크기는 5MB 이하여야 합니다.'); + return; + } + + // 이미지 타입 검증 + if (!file.type.startsWith('image/')) { + alert('이미지 파일만 업로드 가능합니다.'); + return; + } + // 폼에는 File 객체를 onChange로 저장 onChange(file); // 미리보기용 Data URL 생성 const reader = new FileReader(); reader.onloadend = () => { const result = reader.result as string; setAvatarSrc(result); }; + reader.onerror = () => { + alert('파일을 읽는 중 오류가 발생했습니다.'); + }; reader.readAsDataURL(file); } }}src/shared/ui/tagInput.tsx (1)
120-150
: UI 구조 및 스타일링이 적절함태그 입력 UI가 명확하게 구성되어 있으며, 조건부 스타일링을 사용하여 에러 상태 표시 등을 처리하는 방식이 적절합니다. 태그와 입력 필드의 배치도 사용자가 이해하기 쉽게 되어 있습니다.
하지만 124번 줄의 주석 처리된
ref={containerRef}
는 필요하지 않다면 제거하는 것이 좋을 것 같습니다.- <div - // ref={containerRef} - className={cn( ...src/shared/ui/dropDown/searchDropdown.tsx (1)
92-152
: 커스텀 스타일 구현이 상세함Select 컴포넌트의 여러 부분(control, option, menu 등)에 대한 스타일이 상세하게 구현되어 있습니다. 특히 커스텀 색상과 여백을 적용하여 디자인 요구사항을 충족시키는 방식이 좋습니다.
다만, 134-137 라인에서 테두리 색상을 배경색과 동일하게 설정하여 테두리를 숨기는 방식은 혼란을 줄 수 있습니다. 필요하지 않다면
border: 'none'
으로 설정하는 것이 더 명확할 수 있습니다.border: '1px solid', -borderTopColor: '#202030', -borderRightColor: '#202030', -borderBottomColor: '#202030', -borderLeftColor: '#202030', +border: 'none',src/shared/ui/tag/motionTag.tsx (3)
11-19
: TagProps에 정의된onClose
사용 여부 확인
TagProps
인터페이스에onClose
가 있지만, 아래 구현부에서 실제로 사용하지 않고 있습니다. 만약 닫기 기능이 필요 없다면 프로퍼티에서 제거하거나, 추후 기능 구현을 고려해 보세요.
38-43
: 타이포그래피 매핑 적절성 확인
sm
일 때caption-1
, 그 외md
,lg
가body-5
로 동일하게 매핑되어 있습니다. 디자인 가이드를 만족하는지, 추가로 미세조정이 필요한지 확인을 권장합니다.
45-69
: 애니메이션 및 이벤트 처리
프레이머 모션의variants
를 적절히 설정해 태그에 동적 효과를 주는 것은 좋아 보입니다. 다만,onClose
에 대한 로직이 누락되어 있어 클릭 이외의 닫기 기능이 필요한 경우 추가 구현이 필요합니다.src/features/multi-step-form/ui/careerForm/thridStep.tsx (7)
1-20
: react-hook-form과 Zustand 연계 점검
useFormContext
,useFieldArray
,useShallow
를 동시에 사용하면서 폼 상태와 전역 상태를 명확히 구분해야 합니다. 현재로서는 충돌 우려는 없어 보이나, 폼 검사 로직이 다수 확장될 경우 복잡도가 높아질 수 있으므로 구조를 주기적으로 점검하시기 바랍니다.
22-50
:useFieldArray
로 배열 필드 관리
content
,sns
,project
에 각각useFieldArray
를 적용하여 동적 폼 필드를 구성하는 것은 효율적입니다. 다만, 유사한 로직이 반복되므로 추후 리팩터링으로 공통화할 여지가 있어 보입니다.
52-74
: 별도의 편집 상태 훅(useEditingStates
) 활용
필드마다 편집 여부를 별도로 관리하는 구조는 유연하지만, 상태가 늘어날 경우 복잡도가 커질 수 있습니다. 공통 로직을 합치거나 모듈화하는 방안을 검토해 보세요.
161-175
: 취미 필드
포맷과 에러 메시지 처리는 잘 되어 있으나, SNS 등과 마찬가지로 여러 취미를 추가할 수 있는 요구사항이 있을 경우useFieldArray
로 확장하는 방안도 고려해 보세요.
177-191
: 최근 소식 필드
UX 측면에서 사용자에게 힌트를 더 주거나, 여백과 레이블 안내 등을 강화하면 입력 편의가 더 좋아질 수 있습니다.
193-230
: 작성한 글(content
) 배열 필드
handleFieldAppend
와handleFieldRemove
를 통해 배치, SNS 부분과 유사한 로직이 반복됩니다. 공통화 및 재사용 가능성을 염두에 두면 코드 유지보수에 도움이 됩니다.
231-273
: 프로젝트(project
) 배열 필드
배열 필드 로직이 일관된 패턴으로 유지되어 있습니다. 다만, 필드 생성을 위한 초기값이 여러 곳에서 설정되므로, 공통 상수나 factory 함수를 정의해 중복을 없앨 수 있겠습니다.src/features/multi-step-form/ui/careerForm/tagFormStep/ui/TagBox.tsx (2)
12-13
: 컴포넌트에서 외부 스토어 함수 직접 사용
Props로 전달하던 로직을 제거하고, 곧바로 Zustand 스토어의 함수를 사용하는 구조는 간결해졌습니다. 대신, 복잡한 로직이 스토어 쪽에 집중될 수 있으니 유지보수 시 잘 관리하세요.
17-50
: 태그 위치 계산 로직
getTagPositions
내부에서 태그 선택 순서별로 위치를 지정하고 있으며, 중복되는 조건문이 많습니다. 이 로직을 객체 매핑 또는 배열 인덱스 활용으로 줄일 수 있으면 관리가 더욱 편리해질 것입니다.src/features/multi-step-form/utils/index.tsx (2)
14-33
: 도메인 패턴 매핑에 대한 확장 고려
현재 주요 플랫폼들만 대응하지만,youtu.be
와 같은 짧은 도메인이나 기타 TLD를 처리해야 할 수도 있습니다. 향후 새로운 플랫폼 도메인 추가 시, 해당 배열에만 간단히 패턴을 추가하면 확장할 수 있어 유연성이 좋습니다.
89-90
: 옵셔널 체이닝 사용 제안
content && content.every(...)
대신content?.every(...)
형태로 옵셔널 체이닝을 적용하면 코드 가독성과 안전성이 조금 더 향상됩니다.-if (content && content.every((contentItem) => contentItem.link !== '')) { +if (content?.every((contentItem) => contentItem.link !== '')) { formData.append('content', JSON.stringify(content)); }🧰 Tools
🪛 Biome (1.9.4)
[error] 89-90: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
src/features/multi-step-form/ui/careerForm/index.tsx (1)
94-95
: 테스트용 콘솔 코드
현재 주석 처리된console.log(data)
는 실제 배포 시 제거하거나 로깅을 활용한 별도의 디버깅 방식으로 대체할 수 있습니다.src/features/multi-step-form/schema/index.ts (2)
30-38
: 중복 검증 규칙 간소화 필요
min(1)
과 뒤이어refine((value) => value.length > 1)
가 동시에 선언되어 있으나, 사실상 최소 길이가 2로 정의된 셈이라 중복 표현일 수 있습니다.min(2)
혹은 해당refine
만 사용하는 방식으로 검증 규칙을 일관성 있게 맞추는 것이 좋습니다.
67-67
: 오타 수정 제안
에러 메시지에서'최소 하나의 지역'
처럼 두 칸 공백이 발생하고 있습니다. 작은 부분이지만 UI/UX 면에서 혼란이 없도록 메시지를 정제하면 좋겠습니다.
📜 Review details
Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (39)
package.json
(2 hunks)src/app/layout.tsx
(1 hunks)src/features/multi-step-form/components/AvartarImg.tsx
(1 hunks)src/features/multi-step-form/components/Cardview.tsx
(1 hunks)src/features/multi-step-form/components/EditableCardField.tsx
(1 hunks)src/features/multi-step-form/config/index.ts
(2 hunks)src/features/multi-step-form/hooks/queries/useCreateCard.ts
(1 hunks)src/features/multi-step-form/hooks/queries/useScrap.ts
(1 hunks)src/features/multi-step-form/hooks/useEditingStates.ts
(1 hunks)src/features/multi-step-form/schema/index.ts
(1 hunks)src/features/multi-step-form/ui/careerForm/firstStep.tsx
(2 hunks)src/features/multi-step-form/ui/careerForm/fourthStep.tsx
(2 hunks)src/features/multi-step-form/ui/careerForm/index.tsx
(3 hunks)src/features/multi-step-form/ui/careerForm/secondStep.tsx
(2 hunks)src/features/multi-step-form/ui/careerForm/tagFormStep/config/config.ts
(2 hunks)src/features/multi-step-form/ui/careerForm/tagFormStep/ui/Ball.tsx
(1 hunks)src/features/multi-step-form/ui/careerForm/tagFormStep/ui/MotionTagBox.tsx
(1 hunks)src/features/multi-step-form/ui/careerForm/tagFormStep/ui/TagBox.tsx
(1 hunks)src/features/multi-step-form/ui/careerForm/thridStep.tsx
(1 hunks)src/features/multi-step-form/ui/index.tsx
(2 hunks)src/features/multi-step-form/utils/index.tsx
(1 hunks)src/features/new-card/config/index.tsx
(1 hunks)src/features/new-card/hooks/queries/useRegisterQuery.ts
(1 hunks)src/features/new-card/ui/newCreateCardView.tsx
(1 hunks)src/shared/hooks/useOnClickOutside.ts
(1 hunks)src/shared/spacing/spacing.ts
(1 hunks)src/shared/store/cardFormState.ts
(1 hunks)src/shared/types/index.ts
(1 hunks)src/shared/ui/Input/index.tsx
(3 hunks)src/shared/ui/Input/input.tsx
(2 hunks)src/shared/ui/Input/tagInput.tsx
(3 hunks)src/shared/ui/dropDown/index.tsx
(1 hunks)src/shared/ui/dropDown/searchDropdown.tsx
(1 hunks)src/shared/ui/label.tsx
(1 hunks)src/shared/ui/tag/motionTag.tsx
(1 hunks)src/shared/ui/tag/tag.tsx
(3 hunks)src/shared/ui/tagInput.tsx
(1 hunks)src/shared/ui/textArea/index.tsx
(1 hunks)tailwind.config.ts
(1 hunks)
🧰 Additional context used
🧬 Code Definitions (14)
src/features/multi-step-form/hooks/queries/useCreateCard.ts (1)
src/shared/types/index.ts (1) (1)
ApiResponseType
(1:6)
src/shared/ui/Input/index.tsx (1)
src/shared/ui/Input/input.tsx (1) (1)
InputBody
(48:48)
src/features/multi-step-form/hooks/queries/useScrap.ts (1)
src/shared/types/index.ts (1) (1)
ApiResponseType
(1:6)
src/features/multi-step-form/config/index.ts (1)
src/features/multi-step-form/schema/index.ts (1) (1)
CareerFormData
(123:123)
src/features/multi-step-form/ui/careerForm/tagFormStep/ui/MotionTagBox.tsx (1)
src/features/multi-step-form/ui/careerForm/tagFormStep/config/config.ts (2) (2)
TagConfigItem
(6:19)tagConfig
(21:107)
src/shared/ui/Input/input.tsx (1)
src/shared/lib/utils.ts (1) (1)
cn
(4:6)
src/features/multi-step-form/ui/careerForm/fourthStep.tsx (1)
src/features/multi-step-form/ui/careerForm/tagFormStep/config/config.ts (1) (1)
tagConfig
(21:107)
src/features/multi-step-form/ui/careerForm/secondStep.tsx (1)
src/shared/store/cardFormState.ts (1) (1)
useCardFormStore
(17:56)
src/app/layout.tsx (2)
src/shared/lib/font.ts (1) (1)
pretendard
(3:53)src/shared/providers/index.tsx (1) (1)
Providers
(8:15)
src/features/new-card/hooks/queries/useRegisterQuery.ts (1)
src/shared/types/index.ts (1) (1)
ApiResponseType
(1:6)
src/shared/ui/Input/tagInput.tsx (1)
src/shared/ui/Input/input.tsx (2) (2)
InputBodyProps
(26:29)inputVariants
(48:48)
src/features/multi-step-form/ui/careerForm/tagFormStep/ui/TagBox.tsx (2)
src/shared/store/cardFormState.ts (1) (1)
useCardFormStore
(17:56)src/features/multi-step-form/ui/careerForm/tagFormStep/config/config.ts (2) (2)
TagConfigItem
(6:19)tagConfig
(21:107)
src/features/multi-step-form/ui/careerForm/firstStep.tsx (3)
src/features/multi-step-form/schema/index.ts (1) (1)
CareerFormData
(123:123)src/shared/store/cardFormState.ts (1) (1)
useCardFormStore
(17:56)src/shared/ui/dropDown/searchDropdown.tsx (1) (1)
SearchOptions
(13:18)
src/features/multi-step-form/ui/careerForm/index.tsx (2)
src/features/multi-step-form/schema/index.ts (2) (2)
CareerFormData
(123:123)cardCreateSchema
(3:121)src/features/multi-step-form/config/index.ts (1) (1)
TOTAL_STEPS
(20:20)
🪛 Biome (1.9.4)
src/features/multi-step-form/utils/index.tsx
[error] 89-90: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
[error] 94-95: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (110)
src/shared/spacing/spacing.ts (1)
17-17
: 일관된 스페이싱 시스템 확장 👍새로운 패딩 변형
ms
를 추가하여 12px 값을 지원하도록 했습니다. 이는 xs(4px)와 sm(8px) 사이의 중간 값으로, 더 세밀한 레이아웃 제어가 가능해졌습니다. 이 변경은 tailwind.config.ts의 spacing 설정과도 일치하여 일관성을 유지했습니다.tailwind.config.ts (1)
132-132
: 중간 크기의 스페이싱 값 추가 👍spacing 객체에 'ms' 값(12px)을 추가한 것은 좋은 결정입니다. 이는 sm(8px)과 md(16px) 사이의 세밀한 간격 제어가 필요한 UI 요소에 유용하게 사용될 수 있습니다. 이 변경은 spacingStyles의 padding 변형 추가와 일치하여 디자인 시스템의 일관성을 유지합니다.
src/shared/types/index.ts (1)
1-6
: 표준화된 API 응답 타입 정의 👍
ApiResponseType<T>
제네릭 타입을 정의하여 API 응답 구조를 표준화한 것은 좋은 접근입니다. 이를 통해 응답 처리가 일관되고 타입 안전성이 향상됩니다. 상태, 메시지, 타임스탬프 및 실제 데이터를 포함하는 구조는 명확하고 이해하기 쉽습니다.src/features/multi-step-form/ui/careerForm/tagFormStep/ui/Ball.tsx (1)
25-38
: 사용자 상호작용 처리 개선
pointer-events-none
클래스를 추가하여 파도 효과를 가진 div 요소들이 마우스 이벤트를 캡처하지 않도록 변경한 것은 좋은 개선입니다. 이렇게 하면 사용자가 태그 구슬과 상호작용할 때 배경 요소가 간섭하지 않게 됩니다.src/shared/ui/label.tsx (1)
1-19
: 재사용 가능한 레이블 컴포넌트 구현 완료Radix UI를 활용한 레이블 컴포넌트가 잘 구현되어 있습니다.
forwardRef
를 사용하여 레퍼런스를 전달하고,class-variance-authority
를 통해 스타일 변형을 관리하는 패턴이 적절합니다.package.json (4)
19-19
: 새로운 드롭다운 메뉴 기능 추가에 필요한 종속성 추가Radix UI 드롭다운 메뉴 라이브러리가 적절히 추가되었습니다. 이는 명함 생성 인터페이스에서 드롭다운 기능을 구현하는데 사용될 것으로 보입니다.
28-29
: 한글 처리 및 애니메이션 기능을 위한 라이브러리 추가명함 생성 도메인에 필요한 두 가지 중요한 라이브러리가 추가되었습니다:
es-hangul
: 한글 텍스트 처리 라이브러리로, 이름이나 직함 등의 한글 데이터를 다루는데 유용합니다.framer-motion
: UI 애니메이션 라이브러리로, 사용자 경험을 향상시키는 부드러운 전환과 애니메이션을 구현할 수 있습니다.
37-37
: 검색 가능한 드롭다운 구현을 위한 라이브러리 추가
react-select
라이브러리는 고급 선택 컴포넌트를 제공하여 명함 생성 시 직업, 회사명 등을 검색하고 선택할 수 있는 기능을 구현하는데 도움이 될 것입니다.
43-44
: 상태 관리를 위한 Zustand 라이브러리 추가Zustand 상태 관리 라이브러리가 적절히 추가되었습니다. 이는 명함 생성 과정에서 여러 단계의 폼 상태를 관리하는데 유용할 것입니다. React Context API보다 더 간결하고 성능이 좋은 대안입니다.
src/app/layout.tsx (1)
26-32
: React Fragment로 레이아웃 컴포넌트 구조 개선레이아웃 컴포넌트의 JSX 반환 구조를 React Fragment(
<> ... <>
)로 감싸는 것은 좋은 패턴입니다. 이는 실제 DOM에 추가 노드를 생성하지 않으면서 여러 요소를 그룹화할 수 있게 해줍니다. 향후 레이아웃 확장 시 유연성이 향상됩니다.src/features/new-card/ui/newCreateCardView.tsx (2)
7-7
: Zustand 스토어를 활용한 상태 관리 개선Zustand 상태 관리 라이브러리를 활용하여 명함 생성 상태를 관리하는 방식은 적절합니다.
useCardFormStore
와setJob
함수를 통해 사용자가 선택한 직업 유형을 중앙 집중식으로 관리할 수 있게 되었습니다.Also applies to: 14-14
29-36
: UI 구조 및 사용자 경험 개선헤더와 리스트의 레이아웃이 잘 구성되어 있습니다. 사용자에게 명확한 안내와 선택 옵션을 제공하는 방식이 적절합니다.
src/shared/hooks/useOnClickOutside.ts (1)
5-29
: useOnClickOutside 훅 구현외부 클릭을 감지하는 훅이 잘 구현되어 있습니다. 이 훅은 드롭다운, 모달 등의 UI 컴포넌트에서 외부 클릭 시 닫히는 기능을 구현하는 데 유용합니다. 코드에 적절한 주석이 포함되어 있고, 이벤트 리스너의 등록 및 정리가 올바르게 처리되었습니다.
src/features/multi-step-form/config/index.ts (1)
24-32
: LGTM! Record 타입 활용이 적절합니다.
FIELD_TAG_MAPPING
객체는 Record 타입과 Pick 유틸리티를 활용하여 타입 안정성을 확보했습니다. 이것은 폼 데이터와 태그 값 간의 매핑을 명확하게 정의하여 유지보수성을 높입니다.src/shared/ui/tag/tag.tsx (2)
48-48
: 'create-input' 스타일 정의가 추가되었습니다.새로운 사이즈 옵션에 대한 스타일 정의가 적절히 추가되었습니다. 다양한 사이즈 옵션을 제공함으로써 컴포넌트의 유연성이 향상되었습니다.
70-70
: 버튼 여백 조정이 적용되었습니다.버튼의 왼쪽 여백이
ml-1
에서ml-[2px]
로 변경되었습니다. 이 미세한 조정으로 UI의 세부 사항이 개선되었습니다.src/features/multi-step-form/ui/careerForm/tagFormStep/ui/MotionTagBox.tsx (1)
1-56
: MotionTagBox 컴포넌트 구현이 잘 되었습니다.Framer Motion 라이브러리를 활용하여 태그 애니메이션을 효과적으로 구현했습니다. Zustand 상태 관리와의 통합도 적절하게 처리되었습니다. 각 태그의 애니메이션 상태(선택/미선택)에 따른 변형이 명확하게 정의되어 있고, 인터랙션도 잘 구현되었습니다.
src/features/multi-step-form/hooks/queries/useScrap.ts (1)
8-19
: 타입 정의가 명확합니다.
ScrapType
,ScrapPayload
,ScrapResponse
타입이 명확하게 정의되어 있어 API와의 인터페이스가 잘 구조화되어 있습니다.src/shared/ui/Input/input.tsx (3)
16-18
: 에러 상태를 위한 스타일 변형 추가가 적절합니다.에러 표시를 위한 border 스타일이 명확하게 정의되어 있습니다.
32-37
: 에러 속성이 inputVariants에 올바르게 적용되었습니다.error 속성을 추출하고 className에 적용하는 방식이 적절합니다.
48-48
: InputBody와 함께 inputVariants를 내보내는 것이 적절합니다.이 변경으로 다른 컴포넌트에서 스타일 변형을 직접 활용할 수 있어 재사용성이 향상됩니다.
src/features/multi-step-form/ui/index.tsx (2)
28-39
: handleStepBack의 의존성 배열이[handleBack]
으로 변경되었습니다.이전에는
[currentStep]
에 의존했지만 이제[handleBack]
에만 의존합니다. 이 함수도currentStep
을 참조하지만 의존성에서 제외되었습니다. 함수형 업데이트를 사용하므로 상태 업데이트에는 문제가 없을 수 있지만, 이 변경 사항으로 handleBack이 변경될 때만 handleStepBack이 재생성됩니다.
19-26
:✅ Verification successful
handleNextStep의 의존성 배열 변경에 대한 검토가 필요합니다.
기존에는
[currentStep]
에 의존했지만 이제 빈 배열[]
로 변경되었습니다. 이 함수는currentStep
을 참조하지만 의존성에 포함되지 않아 클로저 문제가 발생할 가능성이 있습니다.setCurrentStep
은 함수형 업데이트를 사용하므로 안전하지만, 이러한 변경의 의도가 명확해야 합니다.
🌐 Web query:
React useCallback 함수형 업데이트와 의존성 배열 관계
💡 Result:
React에서
useCallback
은 함수를 메모이제이션하여 불필요한 재생성과 리렌더링을 방지하는 Hook입니다. 함수형 업데이트는setState
에 함수를 전달해 최신 상태를 안전하게 참조하는 기법이며, 의존성 배열은useCallback
이 함수 재생성 시점을 결정하는 핵심 요소입니다. 두 개념의 관계는 다음과 같이 요약됩니다:
useCallback과 의존성 배열
메모이제이션된 함수 관리
useCallback(() => { ... }, [deps])
는deps
배열의 값이 변경될 때만 함수를 재생성합니다.
- 빈 배열
[]
: 컴포넌트 마운트 시점의 값에 의존[1][6].- 의존성 포함
[state]
: 해당state
변경 시마다 함수 재생성[4][5].렌더링 최적화
자식 컴포넌트에 함수를 전달할 때, 의존성 배열을 통해 함수 참조 안정성을 유지하여 불필요한 리렌더링 방지[8][9].
함수형 업데이트와의 관계
함수형 업데이트(
setState(prev => ...)
)는useCallback
의 의존성 배열을 간소화하는 데 활용됩니다:
- 최신 상태 접근
함수형 업데이트는setState
에 전달된 콜백이 항상 최신 상태를 인자로 받기 때문에,useCallback
의 의존성 배열에서 해당 상태를 제거할 수 있습니다[1][7].// 의존성 배열 없이 최신 data 참조 const updateData = useCallback((newItem) => { setData((prevData) => [...prevData, newItem]); }, []);- 의존성 배열 축소
상태 변경 로직을 함수형 업데이트로 처리하면 해당 상태를 의존성 배열에 포함시킬 필요가 사라집니다. 이로 인해 함수 재생성 빈도 감소[6][10].
주의 사항
상황 함수형 업데이트 미사용 함수형 업데이트 사용 상태 참조 의존성 배열에 상태 추가 필수 의존성 배열에서 상태 제외 가능 최신 값 초기 상태 고정 항상 최신 상태 참조 리렌더링 상태 변경 시 함수 재생성 동일 함수 참조 유지 예시:
// ❌ 상태를 의존성 배열에 추가해야 최신 값 참조 const handleSubmit = useCallback(() => { post('/api', { data }); }, [data]); // data 변경 시마다 재생성 // ✅ 함수형 업데이트로 의존성 제거 const handleSubmit = useCallback(() => { setData((prevData) => [...prevData, newData]); // prevData는 항상 최신 }, []); // 재생성 없음
결론
- 함수형 업데이트는
useCallback
과 결합해 의존성 배열을 최소화하고 최신 상태를 안전하게 참조합니다.- 의존성 배열은 함수가 외부 상태에 의존할 때만 필수로 포함시키고, 그렇지 않으면 함수형 업데이트로 대체하여 성능을 최적화합니다[4][7][10].
Citations:
- 1: https://sunho-doing.tistory.com/entry/Reactjs-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%B5%9C%EC%A0%81%ED%99%94-useCallback-%ED%95%A8%EC%88%98%ED%98%95-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8
- 2: https://velog.io/@kjewt/React-useCallback%EC%9D%B4%EB%9E%80
- 3: https://blacksoy0516.tistory.com/71
- 4: https://mooneedev.netlify.app/frontend/%EC%96%B8%EC%A0%9C%EC%8D%A8%EC%95%BC%ED%95%A0%EA%B9%8C%20usecallback/
- 5: https://dolphinsarah.tistory.com/31
- 6: https://velog.io/@fltxld3/React-useCallback%EB%A1%9C-Rendering-%EC%B5%9C%EC%A0%81%ED%99%943-feat.%EC%9D%BC%EA%B8%B0%EC%9E%A5
- 7: https://stella0905.tistory.com/91
- 8: https://kindjjee.tistory.com/150
- 9: https://thring.tistory.com/77
- 10: https://sarah1918.tistory.com/16
- 11: https://k-developer.gitbook.io/dev/react/react-hook/usecallback
- 12: https://duektmf34.tistory.com/200
- 13: https://ko.react.dev/reference/react/useCallback
- 14: https://velog.io/@kyeun95/React-useCallback%EC%9D%B4%EB%9E%80
- 15: https://dongkeun2.tistory.com/entry/React-React-Hooks-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0
- 16: https://velog.io/@kimjumpsun_code/React-useCallback-%ED%95%A8%EC%88%98%ED%98%95-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8
- 17: https://honken.tistory.com/156
- 18: https://kyechan99.github.io/post/lib/react-render
- 19: https://blog.naver.com/so_no7/223258376081
- 20: https://blog.naver.com/futureds/221757489685?viewType=pc
핵심: 현재 코드에서는 함수형 업데이트를 활용해 빈 의존성 배열 사용이 안전함이 확인되었습니다.
handleNextStep은 setCurrentStep에 함수형 업데이트 패턴을 사용하기 때문에, 이전 상태(currentStep)를 명시적으로 의존성 배열에 포함하지 않아도 항상 최신 상태를 참조합니다. 다만, 이 변경 의도가 명확하도록 추가 주석을 달거나, 관련 문서에 변경 사유를 간략히 기재하면 가독성과 유지보수에 도움이 될 것 같습니다.
src/shared/store/cardFormState.ts (1)
17-29
: 기본 상태 및 job 설정 함수가 적절히 구현되어 있습니다.기본값 설정과
setJob
함수의 구현이 좋습니다.typeof job === 'function'
체크로 함수형 업데이트를 지원하는 점이 유연합니다.src/features/multi-step-form/hooks/useEditingStates.ts (1)
1-54
: 잘 구현된 커스텀 훅입니다!
useEditingStates
훅은 필드 배열의 편집 상태를 관리하는 기능을 잘 구현했습니다. 상태 초기화, 필드 배열 변경에 대한 반응, 그리고 다양한 상태 관리 함수들이 체계적으로 구현되어 있습니다. JSDoc을 통한 문서화도 잘 되어 있어 사용법이 명확합니다.src/features/multi-step-form/ui/careerForm/secondStep.tsx (4)
3-6
: Zustand 스토어 통합 개선이 좋습니다!Zustand를 사용한 상태 관리로의 전환은 코드 구조를 개선했습니다.
12-14
: 타입 이름 변경 및 반환 타입 개선
SecondStepPropsType
에서SecondStepProps
로의 이름 변경과handleNextStep
의 반환 타입 변경이 더 명확합니다.
17-18
: 상태 관리 방식 개선Zustand 스토어를 활용한 상태 관리가 잘 구현되었습니다.
27-27
: TagBox 컴포넌트 사용 방식 개선TagBox 컴포넌트에 props를 전달하지 않고 Zustand 스토어를 직접 사용하도록 변경한 것은 prop drilling을 줄이는 좋은 접근법입니다.
src/features/multi-step-form/ui/careerForm/firstStep.tsx (6)
5-12
: 적절한 컴포넌트 및 쿼리 가져오기필요한 컴포넌트와 훅을 잘 가져왔습니다.
useRegisterQuery
,useCardFormStore
,SearchDropdown
,TagInput
,Textarea
는 모두 리팩토링된 form에 필요한 요소들입니다.
24-28
: 직군 데이터 가져오기 로직이 잘 구현되었습니다Zustand 스토어의 job 상태를 기반으로
useRegisterQuery
를 호출하여 career 옵션을 가져오는 구현이 잘 되어 있습니다.
41-55
: 필드 이름 및 컴포넌트 업데이트
name
필드를nickname
으로 변경하고 관련 오류 처리도 적절하게 업데이트했습니다.
56-82
: SearchDropdown 구현이 잘 되었습니다
detailJobId
필드에 대한SearchDropdown
사용이 효과적으로 구현되었습니다. 선택된 옵션의id
만 추출하여 전달하는 로직이 명확하고, 현재 필드 값에 해당하는 옵션을 찾는 방식도 효율적입니다.
83-96
: TagInput 컴포넌트 사용 개선
domain
필드를interestDomain
으로 변경하고WrappedTagInput
에서TagInput
으로 변경한 것은 좋은 개선입니다.
97-113
: Textarea 컴포넌트 사용 개선
description
필드를summary
로 변경하고WrappedInput
에서Textarea
로 변경한 것은 적절한 수정입니다. 문자 수 제한 기능을 추가한 것도 좋은 개선입니다.src/features/multi-step-form/components/EditableCardField.tsx (9)
4-11
: 필요한 컴포넌트와 유틸리티 가져오기필요한 컴포넌트와 유틸리티를 잘 가져왔습니다.
WrappedInput
,Label
, 그리고 필요한 상수와 훅들이 적절하게 임포트되었습니다.
13-29
: 타입 정의가 명확합니다
FieldData
와FieldProps
인터페이스가 명확하게 정의되어 있어 코드의 타입 안전성이 보장됩니다.
30-45
: Props 구조가 잘 정의되었습니다
EditableCardFieldProps
타입이 잘 정의되어 있어 컴포넌트의 사용법이 명확합니다.
46-67
: JSDoc 문서화가 잘 되어 있습니다컴포넌트의 목적과 각 props의 용도가 JSDoc을 통해 상세하게 문서화되어 있어 다른 개발자들이 이해하기 쉽습니다.
102-128
: 스크래핑 로직이 잘 구현되었습니다링크에 대한 스크래핑 로직과 오류 처리가 잘 구현되어 있습니다. 스크래핑에 실패했을 때도 적절한 대체 로직을 제공합니다.
129-146
: 포커스 상실 처리 로직이 적절합니다포커스를 잃었을 때 필드 업데이트와 편집 모드 비활성화 로직이 체계적으로 구현되어 있습니다.
159-191
: 컴포넌트 구조가 잘 설계되었습니다컴포넌트의 렌더링 로직이 조건에 따라 적절히 분기되어 있고, 필요한 props가 잘 전달되고 있습니다.
205-219
: EditableCardInputView 컴포넌트의 메모이제이션 적용이 좋습니다
memo
를 사용하여EditableCardInputView
컴포넌트를 메모이제이션한 것은 불필요한 렌더링을 방지하는 좋은 최적화 방법입니다.
232-234
: EditableCardEditView 컴포넌트 메모이제이션 적용이 좋습니다
EditableCardEditView
컴포넌트에도memo
를 적용하여 성능을 최적화한 것이 좋습니다.src/features/multi-step-form/ui/careerForm/fourthStep.tsx (2)
4-4
: 훌륭한 리팩토링!React Hook Form을 활용하여 폼 상태 관리를 구현한 점이 좋습니다.
useFormContext
훅을 사용해 상위 폼 컨텍스트에 접근하는 방식으로 컴포넌트 간 상태 공유를 효율적으로 처리했습니다.Also applies to: 12-13, 19-20
49-76
:✅ Verification successful
Controller 컴포넌트 활용이 적절합니다.
Controller
컴포넌트를 사용하여 태그 선택 UI와 폼 상태를 연결한 방식이 깔끔합니다. 특히tag.value.toUpperCase()
를 통해 백엔드 API와의 일관성을 유지하려는 노력이 보입니다.다만, 여기서 한 가지 검증이 필요합니다:
🏁 Script executed:
#!/bin/bash # CareerFormData 스키마에서 previewInfoType 필드가 대문자 값을 기대하는지 확인 echo "Checking CareerFormData schema for previewInfoType field..." cat src/features/multi-step-form/schema/index.ts | grep -A 10 "previewInfoType"Length of output: 372
검증 완료 – Controller 컴포넌트 활용 및 대문자 변환이 적절합니다.
- CareerFormData 스키마에서 previewInfoType 필드가 대문자 문자열(예: "PROJECT", "CONTENT", ...)을 기대하는 점과 코드에서
tag.value.toUpperCase()
를 통해 이를 충족하고 있는 점이 확인되었습니다.src/features/new-card/hooks/queries/useRegisterQuery.ts (2)
1-6
: 적절한 import 구조입니다.필요한 라이브러리와 타입들을 명확하게 가져오고 있습니다.
7-23
: 타입 정의가 명확합니다.API 응답과 요청 파라미터에 대한 타입 정의가 잘 되어 있습니다.
CardJobType
을 유니온 타입으로 정의하여 타입 안전성을 확보했습니다.src/features/multi-step-form/ui/careerForm/tagFormStep/config/config.ts (3)
4-4
: TagValue 타입 정의가 명확합니다.
TagValue
타입을 유니온 타입으로 정의하여 가능한 태그 값들을 명확하게 제한했습니다. 이는 타입 안전성을 높이는 좋은 접근 방식입니다.
9-10
: TagConfigItem 인터페이스 확장이 적절합니다.인터페이스에
value
속성과 위치 관련 속성(initialPosition
,endPosition
)을 추가한 것은 좋은 접근입니다. 특히 위치 속성에 대한 주석을 통해 의도를 명확히 했습니다.Also applies to: 16-18
26-27
: tagConfig 업데이트가 일관성 있게 적용되었습니다.모든 태그 항목에 새로운 속성(
value
,initialPosition
,endPosition
)이 일관되게 추가되었습니다. 특히value
값이TagValue
타입과 일치하여 타입 안전성을 유지했습니다.Also applies to: 39-40, 51-56, 63-68, 75-80, 87-92, 99-104
src/features/multi-step-form/components/AvartarImg.tsx (1)
1-5
: React Hook Form 통합이 깔끔합니다.
'use client'
지시문 추가 및 필요한 모듈을 임포트하고useFormContext
를 사용하여 폼 컨텍스트에 접근하는 방식이 깔끔합니다.Also applies to: 9-10, 12-13
src/shared/ui/Input/tagInput.tsx (7)
5-7
: 적절한 유틸리티 가져오기가 추가됨cn 유틸리티를 사용하여 클래스 이름을 조건부로 적용하는 것은 좋은 방법입니다. 이는 컴포넌트의 가독성과 유지보수성을 향상시킵니다.
19-26
: 태그 추가 로직이 개선됨trimmedValue 변수를 사용하여 입력 값을 정리하고, 이 값이 비어있지 않고 기존 태그에 없을 때만 추가하는 로직이 적절하게 구현되었습니다.
35-39
: 간격 클래스 제거 및 컨테이너 구조 개선
gap
클래스를 제거하고 구조를 간소화하여 레이아웃이 개선되었습니다.
41-46
: 태그 UI 구현 개선태그 위치와 스타일이 적절하게 구현되었습니다. 태그 삭제 기능도 잘 동작할 것으로 보입니다.
51-58
: TagInputBody 컴포넌트로 교체기존 InputBody에서 새로운 TagInputBody 컴포넌트로 변경된 것이 확인됩니다. 이 변경은 태그 입력에 특화된 기능을 제공할 수 있게 합니다.
64-77
: 새로운 TagInputBody 컴포넌트 구현TagInputBody 컴포넌트가 적절하게 구현되었습니다. forwardRef를 사용하여 ref를 전달하고, inputVariants를 활용하여 스타일을 적용하는 방식이 좋습니다.
80-80
: displayName 설정 추가TagInputBody 컴포넌트에 displayName을 설정한 것은 디버깅과 개발 도구에서의 식별을 위해 좋은 방법입니다.
src/shared/ui/Input/index.tsx (8)
11-14
: 추가된 Props 타입이 적절함error, errorMsg, closeBtn, closeBtnClick 등의 새로운 속성이 추가되어 컴포넌트의 기능이 확장되었습니다. 각 속성의 목적이 명확하게 정의되어 있습니다.
31-37
: 제어/비제어 상태 관리 개선컴포넌트가 제어 컴포넌트(controlled)와 비제어 컴포넌트(uncontrolled) 모두를 지원하도록 개선되었습니다. React Hook Form과의 통합을 위한 좋은 접근 방식입니다.
38-45
: handleChange 함수 추가입력값 변경을 처리하는 함수가 추가되었습니다. 제어/비제어 상태에 따라 적절히 처리하는 로직이 잘 구현되어 있습니다.
47-54
: handleClearInput 함수 개선입력 필드 초기화 기능이 개선되었습니다. 제어/비제어 상태에 따라 적절히 처리하고, closeBtnClick 콜백을 호출하는 로직이 잘 구현되어 있습니다.
71-80
: 컴포넌트 구조 개선InputBody에 error 속성을 전달하고, 값과 이벤트 핸들러를 적절하게 설정하는 등 컴포넌트 구조가 개선되었습니다.
81-90
: 닫기 버튼 추가 기능closeBtn 속성이 true일 때 닫기 버튼을 렌더링하는 기능이 추가되었습니다. 사용자 경험 향상을 위한 좋은 기능입니다.
92-92
: 오류 메시지 표시 기능 추가errorMsg가 있을 때 오류 메시지를 표시하는 기능이 추가되었습니다. 사용자에게 피드백을 제공하는 중요한 기능입니다.
100-100
: 기본 내보내기 설정컴포넌트를 기본 내보내기로 설정하여 다른 컴포넌트에서 쉽게 가져와 사용할 수 있도록 했습니다.
src/shared/ui/tagInput.tsx (9)
3-10
: 적절한 의존성 가져오기필요한 훅과 컴포넌트를 적절히 가져왔습니다. 특히 useOnClickOutside 훅을 사용하여 외부 클릭을 처리하는 것은 좋은 UX를 제공합니다.
12-18
: TagInputProps 타입 정의가 명확함컴포넌트의 속성을 위한 타입 정의가 명확하게 되어 있습니다. 외부 제어를 위한 value와 onChange 속성을 포함하여 컴포넌트의 유연성을 높였습니다.
20-40
: 자세한 컴포넌트 문서화JSDoc 형식으로 컴포넌트와 속성에 대한
상세한 설명과 사용 예시를 제공한 것은 매우 좋은 방법입니다. 이는 다른 개발자가 컴포넌트를 이해하고 사용하는 데 큰 도움이 됩니다.
42-60
: ref 전달 처리가 정교함forwardRef를 사용하여 외부에서 전달받은 ref를 내부 ref에 연결하는 로직이 잘 구현되어 있습니다. 다양한 ref 유형(함수, 객체)을 모두 처리할 수 있습니다.
66-72
: 한글 입력 처리를 위한 컴포지션 이벤트 관리컴포지션 이벤트(composition event)를 처리하여 한글과 같은 조합형 문자 입력을 적절히 지원하는 것은 매우 중요한 기능입니다. 이는 한국어 사용자 경험을 크게 향상시킵니다.
78-90
: 키보드 이벤트 처리가 종합적임다양한 키(공백, 쉼표, 탭, 백스페이스, 엔터)의 이벤트를 처리하여 태그 추가 및 삭제 기능을 구현한 것은 사용자 경험을 향상시키는 좋은 접근 방식입니다.
92-111
: 태그 관리 기능이 잘 구현됨태그 추가 및 삭제 기능이 제어/비제어 상태를 모두 고려하여 잘 구현되어 있습니다. 빈 태그 추가 방지와 중복 태그 검사도 적절히 처리됩니다.
113-118
: 외부 클릭 처리가 적절함입력 필드 외부를 클릭했을 때, 입력값이 있으면 태그로 추가하는 기능이 구현되어 있습니다. 이는 사용자 편의성을 향상시킵니다.
151-157
: displayName 설정 및 내보내기컴포넌트에 displayName을 설정하고 기본 내보내기로 지정한 것은 좋은 방법입니다. 이는 디버깅과 다른 파일에서의 사용성을 향상시킵니다.
src/shared/ui/dropDown/searchDropdown.tsx (8)
3-11
: 필요한 의존성 가져오기필요한 라이브러리와 유틸리티 함수가 적절히 가져와졌습니다. 특히
es-hangul
패키지의disassemble
함수를 사용하여 한글 검색 기능을 개선한 것은 좋은 접근 방식입니다.
13-32
: 타입 정의가 명확함
SearchOptions
,SearchDropdownProps
,CombineSearchDropdownProps
타입이 명확하게 정의되어 있습니다. 특히 react-select와 react-hook-form의 속성을 결합한 방식이 적절합니다.
34-56
: 컴포넌트 구조가 간결함forwardRef를 사용하여 ref를 전달하고, 필요한 속성들을 Select 컴포넌트에 전달하는 구조가 간결하고 명확합니다. 오류 메시지 처리도 적절히 구현되어 있습니다.
61-78
: 한글 검색 기능 개선한글 자모를 분해하여 검색하는
customFilterOption
함수가 잘 구현되어 있습니다. 이는 한글 검색의 정확도를 크게 향상시키는 중요한 기능입니다. 레이블과 키워드 모두에서 검색하는 방식도 적절합니다.
80-90
: 일관된 폰트 스타일 설정재사용 가능한
fontStyle
객체를 만들어 스타일을 일관되게 적용한 것은 좋은 방법입니다. 이는 코드 중복을 줄이고 유지보수성을 향상시킵니다.
153-196
: 스크롤바 및 UI 요소 스타일링option, menuList, placeholder 등의 UI 요소에 대한 스타일링이 잘 구현되어 있습니다. 특히 스크롤바를 숨기는 CSS를 적용하여 깔끔한 UI를 제공하는 것은 좋은 접근 방식입니다.
197-209
: 선택된 값 스타일링singleValue에 대한 스타일링이 잘 구현되어 있습니다. 텍스트 넘침 처리와 폰트 설정이 적절합니다.
211-211
: 컴포넌트 내보내기컴포넌트를 기본 내보내기로 설정하여 다른 파일에서 쉽게 가져와 사용할 수 있도록 했습니다.
src/shared/ui/tag/motionTag.tsx (3)
1-2
: 클라이언트 전용 설정 확인
이 컴포넌트가 브라우저 환경에서만 동작하도록'use client'
지시어를 사용한 것은 적절해 보입니다.
3-8
: 필요한 라이브러리와 유틸 함수 임포트
React,class-variance-authority
,framer-motion
,cn
유틸 등 필요한 의존성을 잘 가져오고 있습니다. 현재로서는 임포트 구조에 문제될 부분이 없어 보입니다.
21-36
: cva 스타일 정의 시 중복 속성 주의
py-[10px]
와pb-[10px]
가 함께 정의되어 있는데, 의도대로인지를 확인해 주세요. 또한z-[2147483647]
라는 매우 높은 z-index는 다른 UI 요소를 가리지 않을지 주의가 필요합니다.src/features/multi-step-form/ui/careerForm/thridStep.tsx (5)
76-78
: 선택된 태그(selectedTags
) 활용
Zustand 스토어의tagArray
로 선택된 태그를 관리하는 로직이 명확합니다. 다른 스텝에서도 동일 로직이 재사용되는지 확인하여, 전역 상태를 일관성 있게 유지하세요.
81-104
: 소속 정보 필드 렌더링
selectedTags.includes(...)
조건으로 보여주기 때문에, 소속 정보가 필요한 경우에만 입력 필드가 드러납니다. 불필요한 필드를 감춘다는 점에서 UX가 향상될 것으로 보입니다.
106-143
: SNS 동적 입력 필드
SNS 필드에append
,remove
기능을 제공해 사용자 친화적으로 구성되었습니다. 오류 메시지를errors.sns?.[idx]?.link?.message
형태로 잘 연동하여, 에러 관리를 간결히 처리하고 있습니다.
145-159
: 활동 지역 필드
이 부분은 다른 필드와 구조가 유사하며, 에러 처리가 동일 패턴으로 적용되고 있습니다. 전체적인 일관성이 유지되어 좋아 보입니다.
277-277
: UI 구조 닫힘
컴포넌트가 JSX 구조 및useFormContext
리턴까지 정상적으로 마무리됩니다. 전체적으로 폼 기능에 누락된 부분은 없어 보입니다.src/features/multi-step-form/ui/careerForm/tagFormStep/ui/TagBox.tsx (1)
2-5
: Zustand와 shallow 사용을 통한 최적화
useShallow
를 사용해 필요한 상태(tagArray
,handleTagClick
)만 구독하는 점은 불필요한 리렌더링을 줄일 수 있어 적절해 보입니다.src/features/multi-step-form/utils/index.tsx (2)
8-12
: URL 파싱 로직 적절함
extractDomainUsingRegex
함수는 일반적인 HTTP/HTTPS URL에 대한 도메인 추출에 충분히 유효해 보입니다. 다만, 다른 프로토콜(예:ftp://
등)을 다뤄야 하는 경우에는 추가 처리가 필요할 수 있습니다.
35-50
: 플랫폼 식별 함수 구조 적절
getPlatformFromUrl
는 직관적인 구성으로 빠르게 플랫폼 식별을 수행합니다. 도메인 검사가 실패할 경우'LINK'
를 반환하는 처리가 명확히 되어 있어 가독성이 좋습니다.src/features/multi-step-form/ui/careerForm/index.tsx (4)
27-27
: 타입 정의 변경 확인
handleNextStep
이() => Promise<void>
에서() => void
로 교체되었습니다. 사용 범위에서 비동기 처리 여부를 다시 확인해 보시기 바랍니다.
30-64
: 초기값 설정 구조화
initialValues
에서 각 필드를 명확히 초기화하여, 폼 구성 및 타입 유효성을 유지하고 있습니다. 배열 필드(sns, content, project)도 기본 요소를 포함해 둠으로써 실제 사용 시 예외 처리가 줄어들 수 있습니다.
128-143
: 스텝 검증 후 제출 흐름
handleNextStep
에서 현재 스텝에 해당하는 필드를trigger()
로 검증한 다음, 유효성 통과 시 스텝 이동 또는 최종 제출을 처리하는 로직이 간결하게 잘 구성되었습니다.
148-155
: 버튼 상태 및 표시 텍스트
disabled={!isStepValid}
처리와'다음'
,'제출'
레이블 구분이 명확합니다. 2단계에서 버튼이 표시되지 않는 로직도 UI 요구사항에 부합하면 괜찮습니다.src/shared/ui/dropDown/index.tsx (11)
1-9
: 적절한 의존성 관리와 설정'use client' 지시어를 사용하여 클라이언트 컴포넌트로 정확히 지정했습니다. Radix UI와 필요한 아이콘, 유틸리티 함수들을 잘 가져왔습니다. Took-FE 프로젝트에서 명함 생성 도메인 구현을 위한 드롭다운 UI를 만들기 위한 기본 설정이 잘 되어있습니다.
10-20
: 기본 드롭다운 컴포넌트 정의 좋습니다Radix UI 기본 컴포넌트를 직접 재정의하는 방식으로 구조화되어 있어 유지보수가 용이합니다. 이는 향후 컴포넌트의 동작이나 스타일을 확장하기에 좋은 기반이 됩니다.
22-42
: DropdownMenuSubTrigger 구현 완성도 높음forwardRef를 사용하여 ref 전달을 지원하고, inset 속성을 통해 들여쓰기 옵션을 제공하는 접근 방식이 훌륭합니다. ChevronRight 아이콘으로 하위 메뉴 존재를 직관적으로 나타내고 있습니다.
43-57
: DropdownMenuSubContent 애니메이션 및 스타일링 우수상태 변화에 따른 애니메이션이 잘 구현되어 있으며, 다양한 위치(top, right, bottom, left)에 따른 반응형 스타일링이 포함되어 있습니다. 사용자 경험 측면에서 매우 좋은 구현입니다.
58-76
: DropdownMenuContent의 Portal 사용 적절Portal을 사용하여 드롭다운 메뉴가 다른 요소들 위에 렌더링되도록 구현한 점이 좋습니다. 스크롤 가능한 컨텐츠를 위한 overflow 설정과 최대 높이 제한도 사용성을 고려한 좋은 접근입니다.
96-118
: DropdownMenuCheckboxItem 적절한 구조체크박스 인디케이터가 절대 위치로 잘 배치되어 있으며, Check 아이콘 사용이 직관적입니다. checked 상태 처리도 적절하게 구현되어 있습니다.
119-140
: DropdownMenuRadioItem 디자인 일관성 유지RadioItem이 CheckboxItem과 일관된 구조를 유지하면서도 Circle 아이콘을 사용하여 라디오 버튼의 특성을 잘 반영하고 있습니다. 시각적 계층 구조가 잘 유지되고 있습니다.
141-154
: DropdownMenuLabel 적절한 구현라벨 컴포넌트가 단순하고 명확하게 구현되어 있으며, inset 옵션을 통한 유연성도 제공하고 있습니다. 폰트 스타일 속성(font-semibold)을 통해 시각적 계층 구조를 강화한 점이 좋습니다.
155-166
: DropdownMenuSeparator 간결한 구현구분선 컴포넌트가 간결하고 효과적으로 구현되어 있습니다. 음수 마진(-mx-1)을 사용하여 시각적으로 구분선이 더 넓게 보이도록 한 부분이 세심한 디자인 고려를 보여줍니다.
167-171
: DropdownMenuShortcut 키보드 접근성 고려키보드 단축키를 표시하기 위한 컴포넌트가 추가된 점이 접근성 측면에서 좋습니다. tracking-widest를 적용해 가독성을 높인 부분도 사용자 경험 향상에 도움이 됩니다.
172-188
: 컴포넌트 내보내기 완전성모든 드롭다운 관련 컴포넌트가 명확하게 내보내져 있어 사용자가 필요한 컴포넌트를 선택적으로 임포트할 수 있습니다. 명함 생성 도메인에서 필요한 다양한 드롭다운 기능을 유연하게 구성할 수 있을 것입니다.
import { useMutation } from '@tanstack/react-query'; | ||
|
||
import { client } from '@/shared/apis/client'; | ||
import { CLIENT_SIDE_URL } from '@/shared/constants'; | ||
import { ApiResponseType } from '@/shared/types'; | ||
|
||
const createCard = async (payload: FormData) => { | ||
const res = await client.post<ApiResponseType<null>>(`${CLIENT_SIDE_URL}/api/card`, payload); | ||
return res; | ||
}; | ||
|
||
// 카드 생성 API 호출 | ||
export const useCreateCard = () => { | ||
return useMutation({ | ||
mutationFn: createCard, | ||
onSuccess: (data) => { | ||
console.log('카드 생성 성공', data); | ||
}, | ||
onError: (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.
🛠️ Refactor suggestion
에러 처리 및 사용자 피드백 개선 필요
카드 생성 API 호출을 위한 훅 구현이 잘 되어 있습니다. 그러나 현재 에러 처리가 콘솔 로그에만 의존하고 있어 사용자에게 피드백을 제공하지 않습니다.
다음과 같이 사용자에게 알림을 제공하는 기능을 추가하는 것이 좋겠습니다:
- import { useMutation } from '@tanstack/react-query';
+ import { useMutation } from '@tanstack/react-query';
+ import { toast } from 'react-toastify'; // 또는 프로젝트에서 사용 중인 알림 라이브러리
import { client } from '@/shared/apis/client';
import { CLIENT_SIDE_URL } from '@/shared/constants';
import { ApiResponseType } from '@/shared/types';
const createCard = async (payload: FormData) => {
const res = await client.post<ApiResponseType<null>>(`${CLIENT_SIDE_URL}/api/card`, payload);
return res;
};
// 카드 생성 API 호출
export const useCreateCard = () => {
return useMutation({
mutationFn: createCard,
onSuccess: (data) => {
console.log('카드 생성 성공', data);
+ toast.success('카드가 성공적으로 생성되었습니다.');
},
onError: (error) => {
console.error('카드 생성 실패', error);
+ toast.error('카드 생성에 실패했습니다. 다시 시도해주세요.');
},
});
};
또한 반환값에 isLoading 상태를 활용하여 UI에 로딩 상태를 표시하는 것도 고려해보세요.
📝 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.
import { useMutation } from '@tanstack/react-query'; | |
import { client } from '@/shared/apis/client'; | |
import { CLIENT_SIDE_URL } from '@/shared/constants'; | |
import { ApiResponseType } from '@/shared/types'; | |
const createCard = async (payload: FormData) => { | |
const res = await client.post<ApiResponseType<null>>(`${CLIENT_SIDE_URL}/api/card`, payload); | |
return res; | |
}; | |
// 카드 생성 API 호출 | |
export const useCreateCard = () => { | |
return useMutation({ | |
mutationFn: createCard, | |
onSuccess: (data) => { | |
console.log('카드 생성 성공', data); | |
}, | |
onError: (error) => { | |
console.error('카드 생성 실패', error); | |
}, | |
}); | |
}; | |
import { useMutation } from '@tanstack/react-query'; | |
import { toast } from 'react-toastify'; // 또는 프로젝트에서 사용 중인 알림 라이브러리 | |
import { client } from '@/shared/apis/client'; | |
import { CLIENT_SIDE_URL } from '@/shared/constants'; | |
import { ApiResponseType } from '@/shared/types'; | |
const createCard = async (payload: FormData) => { | |
const res = await client.post<ApiResponseType<null>>(`${CLIENT_SIDE_URL}/api/card`, payload); | |
return res; | |
}; | |
// 카드 생성 API 호출 | |
export const useCreateCard = () => { | |
return useMutation({ | |
mutationFn: createCard, | |
onSuccess: (data) => { | |
console.log('카드 생성 성공', data); | |
toast.success('카드가 성공적으로 생성되었습니다.'); | |
}, | |
onError: (error) => { | |
console.error('카드 생성 실패', error); | |
toast.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.
현재 고려중인 사항입니다. 코드래빗 그대 ... 당신은 좋은 리뷰어입니다.
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.
@jangwonyoon 감사합니다! 좋은 리뷰어라고 해주셔서 기쁩니다. 고려 중이신 사항이라고 하니 천천히 검토해보시길 바랍니다. 추가적인 도움이 필요하시면 언제든지 말씀해주세요. 계속해서 좋은 코드 리뷰로 도움이 되도록 하겠습니다. 😊
interface CardFormState { | ||
tagArray: TagValue[]; | ||
setTagArray: Dispatch<React.SetStateAction<TagValue[]>>; | ||
tagCount: number; | ||
incrementTagCount: Dispatch<React.SetStateAction<number>>; | ||
handleTagClick: (tagMessage: TagValue) => void; | ||
job: CardJobType; | ||
setJob: Dispatch<React.SetStateAction<CardJobType>>; | ||
} |
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
CardFormState 인터페이스에 일부 메소드가 누락되어 있습니다.
인터페이스에는 resetTagArray
메소드가 선언되어 있지 않지만 구현부에는 존재합니다. 사용되는 모든 메소드를 인터페이스에 정의하는 것이 타입 안전성을 높입니다.
interface CardFormState {
tagArray: TagValue[];
setTagArray: Dispatch<React.SetStateAction<TagValue[]>>;
tagCount: number;
incrementTagCount: Dispatch<React.SetStateAction<number>>;
handleTagClick: (tagMessage: TagValue) => void;
job: CardJobType;
setJob: Dispatch<React.SetStateAction<CardJobType>>;
+ resetTagArray: () => void;
}
📝 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.
interface CardFormState { | |
tagArray: TagValue[]; | |
setTagArray: Dispatch<React.SetStateAction<TagValue[]>>; | |
tagCount: number; | |
incrementTagCount: Dispatch<React.SetStateAction<number>>; | |
handleTagClick: (tagMessage: TagValue) => void; | |
job: CardJobType; | |
setJob: Dispatch<React.SetStateAction<CardJobType>>; | |
} | |
interface CardFormState { | |
tagArray: TagValue[]; | |
setTagArray: Dispatch<React.SetStateAction<TagValue[]>>; | |
tagCount: number; | |
incrementTagCount: Dispatch<React.SetStateAction<number>>; | |
handleTagClick: (tagMessage: TagValue) => void; | |
job: CardJobType; | |
setJob: Dispatch<React.SetStateAction<CardJobType>>; | |
resetTagArray: () => void; | |
} |
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.
@jangwonyoon 네, 알겠습니다. 추후에 구현하실 때 도움이 필요하시면 말씀해 주세요.
🐰
incrementTagCount: () => { | ||
set((state) => ({ | ||
tagCount: state.tagCount + 1, | ||
})); | ||
}, |
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
incrementTagCount의 타입과 구현이 일치하지 않습니다.
incrementTagCount
는 Dispatch<React.SetStateAction<number>>
로 선언되어 있지만, 구현은 파라미터를 받지 않고 항상 1을 증가시킵니다. 타입과 구현을 일치시키거나, 함수의 용도에 맞게 타입을 변경하는 것이 좋습니다.
- incrementTagCount: Dispatch<React.SetStateAction<number>>;
+ incrementTagCount: () => void;
Committable suggestion skipped: line range outside the PR's diff.
{tagConfig.map((tag) => ( | ||
<div key={tag.id}> | ||
<Tag | ||
message={tag.message} // 화면에 표시할 텍스트는 label 사용 | ||
size="lg" | ||
className={cn( | ||
'transition-all duration-500 ease-in-out', | ||
!tagArray.includes(tag.value) && tag.animation, | ||
tag.className, | ||
tagArray.includes(tag.value) ? getTagPositions(tag.position, tag) : tag.position, | ||
)} | ||
onClick={() => handleTagClick(tag.value)} // 클릭 시 value 전달 | ||
/> | ||
</div> | ||
))} |
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
태그 렌더링 및 클릭 이벤트
tagConfig.map
으로 태그 목록을 순회하며, 클릭 시 handleTagClick(tag.value)
를 전달해주는 흐름이 명확합니다. 다만, 태그의 애니메이션 클래스(tag.animation
)와 getTagPositions
가 여러 군데서 조건을 처리하니, UI가 복잡해질 경우 구조화가 필요할 수 있습니다.
const DropdownMenuItem = React.forwardRef< | ||
React.ElementRef<typeof DropdownMenuPrimitive.Item>, | ||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & { | ||
inset?: boolean; | ||
} | ||
>(({ className, inset, ...props }, ref) => ( | ||
<DropdownMenuPrimitive.Item | ||
ref={ref} | ||
className={cn( | ||
'relative flex cursor-default select-none items-center gap-2 text-sm outline-none transition-colors focus:bg-gray-500 focus:text-zinc-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0', | ||
spacingStyles({ padding: 'md' }), | ||
inset && 'pl-8', | ||
className, | ||
)} | ||
{...props} | ||
/> | ||
)); | ||
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; | ||
|
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.
💡 Verification agent
🧩 Analysis chain
DropdownMenuItem 포커스 스타일 대비 확인 필요
전반적인 구현은 좋으나, focus 상태의 스타일에서 focus:bg-gray-500
와 focus:text-zinc-900
의 조합은 배경색이 어둡고 텍스트 색상도 어두워 대비가 낮을 수 있습니다. 접근성 측면에서 이 부분을 확인해보세요.
🌐 Web query:
색상 대비 WCAG 기준으로 gray-500과 zinc-900의 대비는 충분한가요?
💡 Result:
그레이-500과 징크-900의 색상 대비는 WCAG 2.1 기준을 충족하지 않습니다. 두 색상의 대비율은 약 3.35:1로, 일반 텍스트에 필요한 최소 4.5:1 미만이기 때문입니다[1][3][5].
주요 기준 요약
- 일반 텍스트 (최소 4.5:1): 미충족 (3.35:1)
- 큰 텍스트/굵은 텍스트 (최소 3:1): 충족 (3.35:1)[3][5]
- WCAG AAA 수준 (7:1): 미충족[3]
디자인 시스템별 가이드 참고
- Adobe Spectrum에서는 Gray-500을 비활성 텍스트/장식용으로만 사용하며, 4.5:1 대비 미달로 인해 주요 콘텐츠에 권장하지 않습니다[3][6].
- Material Design은 색상 계급 차이 50단계 이상에서 4.5:1 대비를 보장하는 시스템을 채택했습니다[3][6].
대비 개선 방안
- 텍스트 크기 증가: 18pt 이상 또는 14pt 굵은 글씨로 변경 시 3:1 기준 충족 가능[5][9].
- 색상 조정: Gray-600(#4B5563)으로 변경 시 징크-900과의 대비율이 4.6:1로 WCAG AA 충족[3].
도구 활용 팁
- **Coolors Contrast Checker**에서 실시간 대비 측정 가능[3][6].
- Chrome의 Lighthouse 접근성 감사 기능으로 페이지 전체 대비 문제 진단 가능[1].
"색상 대비는 정보 인지의 핵심 요소입니다. 연한 회색 계열은 저시력자에게 가독성 문제를 유발할 수 있습니다." – 웹 접근성 전문가[3][6]
Citations:
- 1: https://web.dev/articles/color-and-contrast-accessibility?hl=ko
- 2: https://tailscan.com/tailwind/backgrounds/bg-zinc-900
- 3: https://brunch.co.kr/@somgs34/18
- 4: https://brunch.co.kr/@heavenlych/2
- 5: https://webaim.org/articles/contrast/
- 6: https://yozm.wishket.com/magazine/detail/2921/
- 7: https://a11y.gitbook.io/wcag/1-perceivable/1.4-distinguishable
- 8: https://nuli.navercorp.com/data/blog/Understanding_WCAG_2_ko_by_NULI.pdf
- 9: https://www.books.weniv.co.kr/accessibility/chapter02/02-5
- 10: https://a11ykr.github.io/wcag22/
- 11: https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum
- 12: https://nuli.navercorp.com/data/blog/NULI_Web%20Content%20Accessibility%20Guidelines_2_1_KR.pdf
- 13: https://app.contrast-finder.org/?lang=ko
- 14: https://www.jaenung.net/tree/13008
- 15: http://www.kwacc.or.kr/Board/DataFile/791/Detail?page=1
- 16: http://www.kwacc.or.kr/WAI/wcag21/
- 17: https://blog.sonim1.com/253
- 18: https://accessibility.naver.com/acc/guide_04
DropdownMenuItem 포커스 스타일 대비 보완 필요
현재 focus:bg-gray-500
와 focus:text-zinc-900
조합은 WCAG 2.1 일반 텍스트 기준(최소 4.5:1)을 충족하지 않아(대비율 약 3.35:1) 접근성 측면에서 개선이 필요합니다.
- 해결 제안: 배경색을 예를 들어
gray-600
(#4B5563) 등 WCAG AA 기준(대비율 최소 4.5:1) 이상을 만족하는 색상으로 변경하거나, 텍스트 색상과의 명도 차이를 재검토해 보세요. - 대상 파일:
src/shared/ui/dropDown/index.tsx
(라인 77-95)
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
🧹 Nitpick comments (14)
src/features/multi-step-form/utils/index.tsx (1)
52-102
: FormData 생성 함수가 체계적으로 구현되었습니다.필수 필드와 선택적 필드를 구분하여 처리하는 방식이 명확합니다. 배열 필드의 경우 유효성 검사 후 JSON 문자열로 변환하는 것이 적절합니다.
정적 분석 도구의 제안에 따라 89-90줄과 94-95줄의 코드를 옵셔널 체이닝으로 개선할 수 있습니다:
- if (content && content.every((contentItem) => contentItem.link !== '')) { + if (content?.every((contentItem) => contentItem.link !== '')) { formData.append('content', JSON.stringify(content)); } - if (project && project.every((projectItem) => projectItem.link !== '')) { + if (project?.every((projectItem) => projectItem.link !== '')) { formData.append('project', JSON.stringify(project)); }이렇게 하면 코드가 더 간결해지고 null/undefined 체크가 한 번에 이루어집니다.
🧰 Tools
🪛 Biome (1.9.4)
[error] 89-90: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
[error] 94-95: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
src/features/multi-step-form/ui/careerForm/index.tsx (7)
31-64
: 초기값 설정 시 주의사항
initialValues
에 모든 필드를 빈 문자열로 두면, 파일 업로드(profileImage
)처럼 기본적으로null
이거나undefined
가 되어야 하는 경우와 혼동될 수 있습니다. 파일 입력이 기본적으로 없을 경우null
이나undefined
할당이 더 명시적일 수 있으니 고려해 보세요.- profileImage: '', + profileImage: undefined,
71-71
:mode: 'onBlur'
검증 모드 확인
onBlur
모드는 필드가 포커스를 잃을 때 즉시 검증이 이루어지므로, UX 상 빠른 피드백이 필요한지 확인해 보세요. 상황에 따라onSubmit
또는onChange
가 더 나은 경우도 있습니다.
81-81
: API 훅 네이밍 일관성
useCreateCard
가 반환하는mutate: createCardAPI
는 사용성에 문제는 없지만,mutateCreateCard
처럼 직관적인 네이밍도 고려해 보세요.
94-94
: 최종 제출 로직 연동
createCardAPI(createCareerFormData(data))
호출 시 반환값 에러 처리가 별도로 필요하다면,onError
콜백 등을 활용해 UI 상 응답 처리하는 것도 고려하세요.
100-124
: 배열 필드 검증 로직
validateArrayFields
가 배열 내link
가 빈 값인지 검사하는 로직이 명확합니다. 다만 다른 속성(title
,imageUrl
등)에 대한 검증도 필요하다면 별도 추가 검토가 필요합니다.
148-148
: 조건부 렌더링
{currentStep !== 2 && (...)}
는 2단계에서 버튼이 사라지는 의도라면 정확하지만, 사용자 입장에서 혼란이 없도록 UI/UX도 같이 고려해주세요.
150-154
: 다음/제출 버튼 비활성화 조건
disabled={!isStepValid}
로직은 직관적입니다. 필요 시, 단계별 상세 안내 메시지를 표시해 유저가 무엇을 수정해야 하는지 알 수 있도록 개선을 고려해 주세요.src/features/multi-step-form/components/EditableCardField.tsx (6)
13-44
: 타입 정의 명확성
FieldData
,FieldProps
,EditableCardFieldProps
등 세분화된 타입이 가독성에 유리합니다. 다만,type?: string;
과 같이 어떤 값이 들어올지 모호한 경우, enum 또는 구체적 리터럴 타입으로 엄격히 제한해 볼 수도 있습니다.
68-82
: 컴포넌트 구조 분리
EditableCardField
에서enableEditing
,disableEditing
함수 등을 구현해 UI 상태를 분리한 점이 좋습니다. 재사용성도 고려해 볼 수 있겠네요.
83-84
: 에러 처리 시 await 주의
mutateAsync: scapAPI
를 호출할 때 await로 감싸는 점은 좋으나, 에러 핸들링 부문에서 사용자 알림이나 추가 로깅이 필요할 수 있습니다.
92-99
: 편집 모드 토글
enableEditing
,disableEditing
함수를 통해 인덱스별 편집 상태를 관리하는 로직이 깔끔합니다. 다만, 편집 중에 다른 필드로 넘어가거나 삭제되는 경우의 예외가 있는지 확인해 주세요.
101-127
: 메타데이터 스크래핑 로직
스크래핑에 성공하면updateField
로 모든 데이터를 갱신하는 흐름이 명확합니다. 실패 시 기본 플랫폼 제목으로 세팅하는 예외 처리도 적절합니다. 다만, 스크래핑이 반복해서 발생하지 않도록 방어 로직이 필요한지 검토가 필요합니다.
148-157
: Enter 키 이벤트 처리
handleKeyDown
에서 Enter 누르면enableEditing
이 되도록 처리했는데, 일반적으로 Enter 시 편집을 종료하는 패턴이므로 UX 디자인 의도가 맞는지 확인이 필요합니다.
📜 Review details
Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
src/features/multi-step-form/components/Cardview.tsx
(1 hunks)src/features/multi-step-form/components/EditableCardField.tsx
(1 hunks)src/features/multi-step-form/config/index.ts
(2 hunks)src/features/multi-step-form/hooks/queries/useScrap.ts
(1 hunks)src/features/multi-step-form/schema/index.ts
(1 hunks)src/features/multi-step-form/ui/careerForm/firstStep.tsx
(2 hunks)src/features/multi-step-form/ui/careerForm/index.tsx
(3 hunks)src/features/multi-step-form/utils/index.tsx
(1 hunks)src/shared/ui/tagInput.tsx
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/features/multi-step-form/hooks/queries/useScrap.ts
- src/features/multi-step-form/components/Cardview.tsx
🧰 Additional context used
🧬 Code Definitions (5)
src/features/multi-step-form/config/index.ts (1)
src/features/multi-step-form/schema/index.ts (1) (1)
CareerFormData
(123:123)
src/features/multi-step-form/ui/careerForm/firstStep.tsx (3)
src/features/multi-step-form/schema/index.ts (1) (1)
CareerFormData
(123:123)src/shared/store/cardFormState.ts (1) (1)
useCardFormStore
(17:56)src/shared/ui/dropDown/searchDropdown.tsx (1) (1)
SearchOptions
(13:18)
src/features/multi-step-form/ui/careerForm/index.tsx (3)
src/features/multi-step-form/schema/index.ts (2) (2)
CareerFormData
(123:123)cardCreateSchema
(3:121)src/features/multi-step-form/utils/index.tsx (1) (1)
createCareerFormData
(52:102)src/features/multi-step-form/config/index.ts (1) (1)
TOTAL_STEPS
(20:20)
src/features/multi-step-form/utils/index.tsx (1)
src/features/multi-step-form/schema/index.ts (1) (1)
CareerFormData
(123:123)
src/features/multi-step-form/components/EditableCardField.tsx (3)
src/features/multi-step-form/hooks/queries/useScrap.ts (1) (1)
useScrap
(34:40)src/features/multi-step-form/utils/index.tsx (1) (1)
getPlatformFromUrl
(40:50)src/features/multi-step-form/config/index.ts (1) (1)
MAXIMUM_TAG_ADD
(22:22)
🪛 Biome (1.9.4)
src/features/multi-step-form/utils/index.tsx
[error] 89-90: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
[error] 94-95: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (32)
src/features/multi-step-form/config/index.ts (2)
22-22
: 태그 추가 제한을 명확하게 상수로 정의했네요!최대 태그 추가 개수를 상수로 정의하여 코드의 가독성과 유지보수성을 높였습니다. 이 값이 UI나 다른 로직에서 일관되게 사용될 수 있어 좋습니다.
24-32
: 폼 필드와 태그 값 간의 매핑이 잘 정의되었습니다.Record 타입과 Pick을 사용해 타입 안전성을 확보한 방식이 좋습니다. 각 폼 필드가 해당하는 태그 값에 명확하게 매핑되어 있어, 코드의 일관성과 유지보수성이 향상됩니다.
src/features/multi-step-form/ui/careerForm/firstStep.tsx (6)
5-8
: 관련 훅과 스토어 추가가 잘 이루어졌습니다.Job 상태 관리를 위한
useCardFormStore
와 직군 정보를 가져오기 위한useRegisterQuery
의 추가는 적절합니다. 컴포넌트 간 상태 공유와 API 통합에 좋은 접근법입니다.
24-28
: Zustand를 활용한 상태 관리가 잘 구현되었습니다.Job 상태를 추출하여 API 쿼리에 활용하는 방식이 깔끔합니다. 이러한 패턴은 컴포넌트 간 상태 공유를 효과적으로 만들어줍니다.
41-55
: 필드 이름 변경이 일관되게 적용되었습니다.
name
에서nickname
으로의 변경이 모든 관련 속성에 일관되게 적용되었습니다. 이름 변경은 더 명확한 의미를 제공하여 코드 가독성을 향상시킵니다.
56-82
: 직군 선택을 위한 SearchDropdown 구현이 효과적입니다.
detailJobId
필드에SearchDropdown
컴포넌트를 사용한 구현이 뛰어납니다. 특히:
- 선택된 옵션의 ID 추출 로직이 명확합니다.
- 에러 처리가 잘 구현되어 있습니다.
- 현재 값에 대한 옵션 찾기 로직이 간결합니다.
이러한 접근 방식은 사용자 경험을 향상시키고, 타입 안전성을 보장합니다.
83-96
: TagInput 컴포넌트 사용으로 입력 경험이 개선되었습니다.
WrappedTagInput
에서TagInput
으로의 전환은 좋은 변화입니다. 태그 입력에 특화된 컴포넌트를 사용함으로써 사용자 경험이 향상되었습니다.
97-113
: Textarea 사용으로 더 나은 요약 입력 경험을 제공합니다.
WrappedInput
에서Textarea
로의 변경은 한 줄 소개를 입력하는데 더 적합합니다. 특히 40자 제한을 설정한 것이 좋습니다. 에러 처리도 잘 구현되어 있습니다.src/shared/ui/tagInput.tsx (6)
12-18
: 태그 입력을 위한 Props 타입이 명확하게 정의되었습니다.TagInputProps 타입이 잘 설계되어 있습니다. 외부 제어를 위한 value와 onChange 속성이 포함되어 있어 컴포넌트의 유연성을 높입니다.
20-40
: 컴포넌트 문서화가 매우 상세합니다.JSDoc을 활용한 문서화가 잘 되어 있습니다. 사용 예시까지 포함하여 다른 개발자들이 쉽게 사용할 수 있도록 설명되어 있습니다.
50-72
: 한국어 입력을 위한 조합 이벤트 처리가 잘 구현되었습니다.Composition 이벤트(한글 입력 시 발생하는 이벤트)를 적절히 처리하여 한국어 입력 시 발생할 수 있는 문제를 방지했습니다. 이는 국제화 지원에 중요한 부분입니다.
78-90
: 키보드 인터랙션 처리가 탁월합니다.여러 키(스페이스, 쉼표, 탭, 백스페이스, 엔터)를 통한 태그 추가/제거 기능이 사용자 경험을 향상시킵니다. Composition 상태 확인을 통해 한글 입력 중에 의도하지 않은 동작을 방지하는 것도 좋은 접근법입니다.
113-118
: 외부 클릭 처리가 효과적으로 구현되었습니다.
useOnClickOutside
훅을 사용하여 외부 클릭 시 입력값을 태그로 추가하는 기능이 사용자 경험을 향상시킵니다. Composition 상태도 함께 확인하여 한글 입력 중에 의도하지 않은 태그 추가를 방지합니다.
120-149
: UI 구현이 접근성과 스타일링을 잘 고려했습니다.
- 라벨 연결이 적절합니다.
- 에러 메시지 표시 기능이 잘 구현되어 있습니다.
- 태그 표시와 입력 필드의 조합이 사용자 친화적입니다.
- Focus 관리를 위한 tabIndex 설정과 스타일링이 잘 되어 있습니다.
전반적으로 사용자 경험과 접근성을 모두 고려한 훌륭한 구현입니다.
src/features/multi-step-form/utils/index.tsx (3)
8-12
: URL에서 도메인을 추출하는 함수가 효과적으로 구현되었습니다.정규 표현식을 사용하여 URL에서 도메인을 추출하는 함수가 잘 구현되어 있습니다. 'www.' 접두사를 선택적으로 제거하고 도메인을 소문자로 변환하여 일관된 결과를 제공합니다.
15-33
: 플랫폼 패턴 테이블이 체계적으로 구성되었습니다.다양한 플랫폼을 카테고리별로 정리한 룩업 테이블이 잘 구성되어 있습니다. 이런 방식은 새로운 플랫폼을 쉽게 추가할 수 있어 확장성이 좋습니다.
40-50
: URL에서 플랫폼을 판별하는 함수가 명확하게 구현되었습니다.도메인을 추출하고 패턴을 확인하는 로직이 명확합니다. 플랫폼을 대문자로 반환하고, 매칭되지 않는 경우 'LINK'를 반환하는 것이 일관된 응답 형식을 제공합니다.
src/features/multi-step-form/ui/careerForm/index.tsx (7)
25-28
:handleNextStep
타입 정의 확인 필요
handleNextStep
가 비동기 로직(async/await
)을 사용하는 부분이 있으므로, 필요 시() => Promise<void>
형태로 업데이트하는 것이 명시적이고 안정적일 수 있습니다. 현 코드 흐름에는 큰 문제 없지만, 향후 확장성을 고려해 타입을 확인해 주세요.
74-79
: React Hook Form 디스트럭처링
handleSubmit
,trigger
,watch
,errors
사용이 명확합니다. 그러나trigger
사용 시 성능 저하가 있을 수 있으므로, 검증 필드 범위를 명확히 지정해서 불필요한 검증 호출이 일어나지 않도록 관리해 주세요.
83-83
: 전역 상태 사용 체크
useCardFormStore
를 통해 가져오는validationTagArray
가 동적으로 변할 가능성이 있다면, 폼 단계에 따라 적절히 재검증이 이뤄지는지 확인이 필요합니다.
85-90
: 단계별 필드 분리
stepValidationFields
구성이 명확합니다. 단, 2단계와 4단계에 필드가 비어 있는 로직이 맞다면, 추후 변경되었을 때 유지보수에 유의해야 합니다.
98-98
:watch
사용 주의
watch(stepValidationFields[currentStep])
는 특정 필드만 모니터링하므로, 다른 필드 변화가 필요한 경우watch()
인자나 구조를 유연하게 바꿀 수 있는지 검토해 주세요.
127-127
:handleNextStep
함수 비동기 처리
이미trigger
로 검증 후 로직을 분기하고 있으므로, 불필요한 추가 예외 처리는 없어도 됩니다. 다만 API 통신 후 에러가 발생하면 단계를 넘어가지 못하도록 보강할 수 있는지 확인해 주세요.
130-144
: 스텝 이동 로직
onNextStep()
과 마지막 단계에서handleSubmit(onSubmit)()
로 분기하는 구조가 명확합니다. 다만TOTAL_STEPS
변경 시 이 로직도 함께 수정되어야 하므로, 유지보수 시 주의 바랍니다.src/features/multi-step-form/schema/index.ts (1)
4-120
: 더욱 세분화된 검증 로직 적용
cardCreateSchema
에서 각 필드를 구체적으로 유효성 검증하는 것이 인상적입니다. 배열 필드(sns
,content
,project
)에서link
나 기타 속성에 대한 에러 메시지가 충분히 안내되고 있으니 유지보수 시 혼동이 없을 것 같습니다. 다만, 일부 문자열 길이 제한(예:organization
,news
)과profileImage
가 URL일 때, 실제로 외부 링크 이미지가 유효하지 않아도 통과될 위험이 있을 수 있으므로, 필요하다면 API 쪽에서 추가 검증이 이뤄지도록 확인해주세요.src/features/multi-step-form/components/EditableCardField.tsx (7)
1-2
: React Hook Form 의존성 타입 확인
FieldArrayWithId
,UseFieldArrayUpdate
등을 사용 시 버전에 따라 타입 명시가 달라질 수 있으니, 동일 버전 유지에 유념하세요.
7-10
:MAXIMUM_TAG_ADD
상수 사용
최대 개수 제한을 상수로 잘 분리했습니다. 다양한 곳에서 이 제한이 사용된다면, 유지보수 편의성이 높아집니다.
46-66
: JS Doc 주석 활용
주석으로 각 프로퍼티의 의미가 명확하게 설명되어 있습니다. 추후 유지보수 시 팀원들이 쉽게 이해할 수 있으므로, 매우 바람직한 문서화입니다.
88-90
: 플랫폼 추출 로직
parseLinkToTitle = getPlatformFromUrl(value)
호출이 명확합니다. 도메인에서 플랫폼을 인식할 때 오탐 가능성이 있는지 고려해 주세요.
129-146
: 블러 이벤트 시점 처리
handleBlur
에서scapingTargetArr
를 확인 후 스크래핑을 수행하는 구조가 직관적입니다. 그러나 링크가 변경된 뒤 즉시 블러가 발생하지 않는 경우도 있을 수 있으니, 사용자가 편집을 완전히 마쳤을 때만 스크래핑이 수행되도록 추가 고려해주세요.
159-192
: 조건부 렌더링(!error && value && !editingStates[index]
)
에러 없는 상태이면서 값이 존재하고, 편집 모드가 아닌 경우EditableCardEditView
로 전환하는 로직이 좋습니다. 다만, 에러가 있는데도 값이 있을 때 어떻게 표시되는지 UX 시나리오를 한 번 더 점검해 주시면 좋겠습니다.
194-239
: 컴포넌트 분리로 가독성 향상
EditableCardInputView
,EditableCardEditView
를memo
로 감싼 뒤 각각 분리하여 관리하는 구조가 훌륭합니다. 재렌더링 비용도 절감할 수 있으니 좋은 시도입니다.
📌 개요
관련 이슈 : close #58
명함 생성 도메인
기능
화면
리뷰어에게
React hook form 부분에 중복 코드가 많이 생겨, 추상화를 진행하였습니다.
컴포넌트 추상화 : EditableCardField
hook 추상화 : useEditingStates
API 실패 및 성공 시, 에러 처리에 대한 논의가 추후에 필요해 보입니다.
✅ 체크사항
Summary by CodeRabbit
신규 기능
UI 개선