Conversation
Walkthrough로그인 TextField에 비밀번호 가시성 토글, 유효성 상태 아이콘, 리셋 버튼 옵션이 추가되고, register 흐름용 TopBar 컴포넌트가 도입되었으며 관련 로그인/회원가입 스토리가 해당 기능으로 확장되었습니다. Changes
Sequence DiagramsequenceDiagram
participant User
participant TextField
participant ParentState as State
User->>TextField: 입력(value) 또는 아이콘 클릭
TextField->>ParentState: onChange(value)
ParentState-->>TextField: prop value 업데이트
alt password field
User->>TextField: 눈(토글) 클릭
TextField->>TextField: togglePasswordVisibility()
TextField-->>User: input type 변경("password" ↔ "text")
end
alt useValidationStatus && value 존재
TextField->>TextField: hasValidationError / hasValidationSuccess 계산
TextField-->>User: 검증 아이콘(활성/비활성) 표시
end
alt useResetButton && value 존재
User->>TextField: 리셋 버튼 클릭
TextField->>ParentState: onChange("")
ParentState-->>TextField: value = ""
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (7)
apps/web/src/views/login/login.stories.tsx (1)
111-120: 통합 에러 메시지의 UX 개선을 고려하세요.현재 구현은 하나의 에러 메시지만 표시하여 사용자가 어느 필드에 문제가 있는지 명확히 알기 어렵습니다. 예를 들어, 이메일과 비밀번호 모두 잘못된 경우 이메일 에러만 표시됩니다.
PR 논의사항에서 언급된 대로 에러 메시지 표시 방식 검토가 필요합니다. 다음 방식들을 고려해보세요:
- 각 필드에 인라인 에러 표시 (TextField의
errorprop 활용)- 여러 에러를 모두 나열
- 필드 포커스를 통한 에러 안내
apps/web/src/views/register/components/TopBar.tsx (2)
40-42: 배열 인덱스를 키로 사용할 때 주의하세요.현재
index를 키로 사용하고 있는데, 액션 배열의 순서가 변경되거나 항목이 추가/삭제될 경우 React의 재조정(reconciliation) 문제가 발생할 수 있습니다. 스토리북 용도로는 문제없지만, 실제 사용 시에는 각 액션에 고유한 식별자를 사용하는 것이 좋습니다.또한 Line 41, 59에서
.toString()은 템플릿 리터럴이 자동으로 문자열 변환을 하므로 불필요합니다.- <Fragment key={`top-bar-left-action-${index.toString()}`}>{action}</Fragment> + <Fragment key={`top-bar-left-action-${index}`}>{action}</Fragment>Also applies to: 58-60
11-64: 타이틀과 액션 간 오버플로우 처리를 고려하세요.
leftActions와rightActions가 절대 위치로 배치되어 있고, 타이틀은width: "100%"로 중앙 정렬되어 있습니다. 액션이 많거나 타이틀이 긴 경우 시각적 겹침이 발생할 수 있습니다.다음 개선을 고려해보세요:
- 타이틀에
overflow: hidden,text-overflow: ellipsis추가- 타이틀 영역에 좌우 패딩 추가하여 액션과의 최소 간격 확보
apps/web/src/views/register/register.stories.tsx (2)
153-153: 플레이스홀더 URL을 적절한 값으로 교체하세요.모든 링크가
"https://google.com"을 가리키고 있습니다. 스토리북 시연 목적이라면"#"또는 실제 라우트 경로를 사용하는 것이 좋습니다.- href="https://google.com" + href="#"Also applies to: 167-167, 181-181
139-188: 스토리 구성을 검토하세요.
아이디_찾기_비밀번호_찾기_회원가입과SNS_계정으로_로그인스토리는 회원가입 페이지보다는 공통 네비게이션/레이아웃 컴포넌트에 더 적합해 보입니다.이러한 UI 요소들을 별도의 컴포넌트로 추출하고 해당 컴포넌트의 스토리 파일에서 문서화하는 것을 고려해보세요. 이렇게 하면 재사용성이 높아지고 스토리 구조가 더 명확해집니다.
Also applies to: 190-226
apps/web/src/views/login/components/TextField.tsx (2)
126-138: 버튼 스타일을 명시적으로 초기화하세요.
button요소에 기본 브라우저 스타일이 적용될 수 있습니다. 일관된 렌더링을 위해 다음 스타일을 추가하세요:style={{ cursor: "pointer" }} + style={{ + cursor: "pointer", + border: "none", + background: "none", + padding: 0, + display: "flex", + alignItems: "center" + }}Also applies to: 151-153
140-140: 일관성을 위해currentValue사용을 고려하세요.Lines 140, 150에서
value를 직접 확인하고 있지만, 컴포넌트 내에서는currentValue = value || ""로 정규화된 값을 사용하고 있습니다. 일관성을 위해currentValue를 사용하거나,value가 빈 문자열일 수 있다면 명시적으로 체크하는 것이 좋습니다.- value && useValidationStatus && ( + currentValue && useValidationStatus && (- value && useResetButton && ( + currentValue && useResetButton && (Also applies to: 150-150
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (5)
apps/web/public/icons/components/arrow-left-black.pngis excluded by!**/*.pngapps/web/public/icons/components/check-active.pngis excluded by!**/*.pngapps/web/public/icons/components/check-inactive.pngis excluded by!**/*.pngapps/web/public/icons/components/eye.pngis excluded by!**/*.pngapps/web/public/icons/components/reset.pngis excluded by!**/*.png
📒 Files selected for processing (4)
apps/web/src/views/login/components/TextField.tsx(5 hunks)apps/web/src/views/login/login.stories.tsx(1 hunks)apps/web/src/views/register/components/TopBar.tsx(1 hunks)apps/web/src/views/register/register.stories.tsx(5 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
apps/web/src/views/login/login.stories.tsx (2)
apps/web/src/views/login/components/TextField.tsx (1)
TextField(23-175)apps/web/src/views/login/components/Button.tsx (1)
Button(9-33)
apps/web/src/views/register/register.stories.tsx (2)
apps/web/src/views/login/components/TextField.tsx (1)
TextField(23-175)apps/web/src/views/register/components/TopBar.tsx (1)
TopBar(11-64)
🔇 Additional comments (3)
apps/web/src/views/login/login.stories.tsx (1)
124-148: TextField 컴포넌트에 에러 메시지가 전달되지 않습니다.TextField는
errorprop을 통해 인라인 에러 메시지를 표시할 수 있지만, 현재 이 스토리에서는 전달하지 않고 있습니다. 이로 인해 필드별 에러 표시 기능을 테스트할 수 없습니다.통합 에러 메시지 방식을 유지하더라도, 필드별 에러 prop을 조건부로 전달하여 두 가지 패턴을 모두 시연할 수 있습니다.
apps/web/src/views/register/register.stories.tsx (1)
43-44: 필드 기능 향상이 잘 적용되었습니다.
useValidationStatus와useResetButton플래그를 추가하여 TextField의 새로운 기능을 스토리에서 시연하고 있습니다. 이는 컴포넌트의 다양한 사용 패턴을 문서화하는 좋은 방법입니다.Also applies to: 72-72, 87-87, 114-115
apps/web/src/views/login/components/TextField.tsx (1)
43-45: 변수명이 의도를 명확히 전달합니다.
hasValidationError와hasValidationSuccess는 서로 다른 조건을 확인합니다:
hasValidationError: 검증 실패 여부 (값이 있고 검증 함수가 false 반환)hasValidationSuccess: 검증 성공 여부 (값이 있고 검증 함수가 true 반환)빈 값일 때는 둘 다 false이므로 로직이 올바르게 동작합니다.
| {...[ | ||
| type === "password" && ( | ||
| <button | ||
| type="button" | ||
| onClick={togglePasswordVisibility} | ||
| aria-label={isPasswordVisible ? "비밀번호 숨기기" : "비밀번호 보기"} | ||
| style={{ cursor: "pointer" }} | ||
| > | ||
| <Image | ||
| src="/icons/components/eye.png" | ||
| alt={isPasswordVisible ? "비밀번호 숨기기" : "비밀번호 보기"} | ||
| width={20} | ||
| height={20} | ||
| /> | ||
| </button> | ||
| ), | ||
| value && useValidationStatus && ( | ||
| <Image | ||
| src={ | ||
| hasValidationSuccess ? "/icons/components/check-active.png" : "/icons/components/check-inactive.png" | ||
| } | ||
| alt="유효성 검사 통과" | ||
| width={20} | ||
| height={20} | ||
| /> | ||
| ), | ||
| value && useResetButton && ( | ||
| <button type="button" onClick={() => onChange?.("")} aria-label="초기화" style={{ cursor: "pointer" }}> | ||
| <Image src="/icons/components/reset.png" alt="초기화" width={20} height={20} /> | ||
| </button> | ||
| ), | ||
| ]} |
There was a problem hiding this comment.
JSX 배열 렌더링 문법 오류를 수정하세요.
Line 124의 {...[...]} 구문은 올바른 React 문법이 아닙니다. 스프레드 연산자는 JSX 요소 배열을 렌더링하는 데 사용할 수 없습니다.
또한 type === "password" && 표현식은 조건이 거짓일 때 false를 반환하며, 이는 React에서 렌더링되지 않지만 혼란을 줄 수 있습니다.
다음과 같이 수정하세요:
>
- {...[
+ {[
type === "password" && (
<button
type="button"
onClick={togglePasswordVisibility}
aria-label={isPasswordVisible ? "비밀번호 숨기기" : "비밀번호 보기"}
style={{ cursor: "pointer" }}
>
<Image
src="/icons/components/eye.png"
alt={isPasswordVisible ? "비밀번호 숨기기" : "비밀번호 보기"}
width={20}
height={20}
/>
</button>
),
value && useValidationStatus && (
<Image
src={
hasValidationSuccess ? "/icons/components/check-active.png" : "/icons/components/check-inactive.png"
}
alt="유효성 검사 통과"
width={20}
height={20}
/>
),
value && useResetButton && (
<button type="button" onClick={() => onChange?.("")} aria-label="초기화" style={{ cursor: "pointer" }}>
<Image src="/icons/components/reset.png" alt="초기화" width={20} height={20} />
</button>
),
- ]}
+ ].filter(Boolean)}
</div>filter(Boolean)을 추가하여 falsy 값을 제거하면 더 안전합니다.
📝 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.
| {...[ | |
| type === "password" && ( | |
| <button | |
| type="button" | |
| onClick={togglePasswordVisibility} | |
| aria-label={isPasswordVisible ? "비밀번호 숨기기" : "비밀번호 보기"} | |
| style={{ cursor: "pointer" }} | |
| > | |
| <Image | |
| src="/icons/components/eye.png" | |
| alt={isPasswordVisible ? "비밀번호 숨기기" : "비밀번호 보기"} | |
| width={20} | |
| height={20} | |
| /> | |
| </button> | |
| ), | |
| value && useValidationStatus && ( | |
| <Image | |
| src={ | |
| hasValidationSuccess ? "/icons/components/check-active.png" : "/icons/components/check-inactive.png" | |
| } | |
| alt="유효성 검사 통과" | |
| width={20} | |
| height={20} | |
| /> | |
| ), | |
| value && useResetButton && ( | |
| <button type="button" onClick={() => onChange?.("")} aria-label="초기화" style={{ cursor: "pointer" }}> | |
| <Image src="/icons/components/reset.png" alt="초기화" width={20} height={20} /> | |
| </button> | |
| ), | |
| ]} | |
| {[ | |
| type === "password" && ( | |
| <button | |
| type="button" | |
| onClick={togglePasswordVisibility} | |
| aria-label={isPasswordVisible ? "비밀번호 숨기기" : "비밀번호 보기"} | |
| style={{ cursor: "pointer" }} | |
| > | |
| <Image | |
| src="/icons/components/eye.png" | |
| alt={isPasswordVisible ? "비밀번호 숨기기" : "비밀번호 보기"} | |
| width={20} | |
| height={20} | |
| /> | |
| </button> | |
| ), | |
| value && useValidationStatus && ( | |
| <Image | |
| src={ | |
| hasValidationSuccess ? "/icons/components/check-active.png" : "/icons/components/check-inactive.png" | |
| } | |
| alt="유효성 검사 통과" | |
| width={20} | |
| height={20} | |
| /> | |
| ), | |
| value && useResetButton && ( | |
| <button type="button" onClick={() => onChange?.("")} aria-label="초기화" style={{ cursor: "pointer" }}> | |
| <Image src="/icons/components/reset.png" alt="초기화" width={20} height={20} /> | |
| </button> | |
| ), | |
| ].filter(Boolean)} |
🤖 Prompt for AI Agents
In apps/web/src/views/login/components/TextField.tsx around lines 124-155, the
JSX currently uses an invalid `{...[ ... ]}` spread of an array which is not
valid React syntax and can produce falsy values (false) in the output; replace
the spread-within-braces approach by returning the elements directly (e.g., wrap
them in a fragment or return the array expression itself) and filter out falsy
values with `.filter(Boolean)` before rendering so conditional `&&` expressions
don't leave false in the render tree; also ensure each array item has a stable
key when rendering an array of elements.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
apps/web/src/views/login/login.stories.tsx (2)
195-195: 스토리 이름의 오타 수정 필요"통합_에러_메세지"는 "통합_에러_메시지"로 수정해야 합니다 (메세지 → 메시지).
-export const 통합_에러_메세지: Story = { +export const 통합_에러_메시지: Story = {
207-207: 한국어 텍스트 띄어쓰기 오류"잘못된 비밀번호 입니다."는 "잘못된 비밀번호입니다."로 수정해야 합니다 (입니다 앞 공백 제거).
- if (password.length < 8 || password.length > 16) return "잘못된 비밀번호 입니다."; + if (password.length < 8 || password.length > 16) return "잘못된 비밀번호입니다.";
🧹 Nitpick comments (2)
apps/web/src/views/login/login.stories.tsx (2)
106-155: 인라인 스타일 중복 및 플레이스홀더 URL세 개의 Link 컴포넌트가 동일한 스타일 객체를 중복하여 사용하고 있으며, 모든 링크가 플레이스홀더 URL(
https://google.com)을 가리키고 있습니다.스타일 객체를 상수로 추출하여 중복을 제거하는 것을 권장합니다:
export const 아이디_찾기_비밀번호_찾기_회원가입: Story = { args: {}, render: () => { + const linkStyle = { + color: "#111", + fontSize: "12px", + fontStyle: "normal", + fontWeight: "500", + lineHeight: "140%", + letterSpacing: "-0.24px", + }; + return ( <div style={{ width: "360px", display: "flex", flexDirection: "row", gap: "10px", alignItems: "center" }}> <Link - style={{ - color: "#111", - fontSize: "12px", - fontStyle: "normal", - fontWeight: "500", - lineHeight: "140%", - letterSpacing: "-0.24px", - }} + style={linkStyle} href="https://google.com" > 아이디 찾기 </Link> <div style={{ width: "1px", height: "11px", backgroundColor: "#D9D9D9" }} /> <Link - style={{ - color: "#111", - fontSize: "12px", - fontStyle: "normal", - fontWeight: "500", - lineHeight: "140%", - letterSpacing: "-0.24px", - }} + style={linkStyle} href="https://google.com" > 비밀번호 찾기 </Link> <div style={{ width: "1px", height: "11px", backgroundColor: "#D9D9D9" }} /> <Link - style={{ - color: "#111", - fontSize: "12px", - fontStyle: "normal", - fontWeight: "500", - lineHeight: "140%", - letterSpacing: "-0.24px", - }} + style={linkStyle} href="https://google.com" > 회원가입 </Link> </div> ); }, };참고: Storybook 스토리에서는 플레이스홀더 URL이 일반적이지만, 실제 구현 시 올바른 경로로 교체해야 합니다.
157-193: SNS 로그인 구분선 UI 구현SNS 로그인 영역을 시각적으로 구분하는 UI가 구현되었습니다. 기능적으로 올바르게 동작합니다.
구분선 스타일이 중복되므로, 선택적으로 상수로 추출할 수 있습니다:
export const SNS_계정으로_로그인: Story = { args: {}, render: () => { + const dividerStyle = { + width: "100%", + height: "1px", + backgroundColor: "#D9D9D9", + }; + return ( <div style={{ width: "360px", display: "flex", flexDirection: "row", gap: "12px", alignItems: "center" }}> - <div - style={{ - width: "100%", - height: "1px", - backgroundColor: "#D9D9D9", - }} - /> + <div style={dividerStyle} /> <div style={{ color: "#A7A7A7", fontSize: "12px", fontStyle: "normal", fontWeight: "500", lineHeight: "140%", letterSpacing: "-0.24px", textAlign: "center", minWidth: "fit-content", }} > SNS 계정으로 로그인 </div> - <div - style={{ - width: "100%", - height: "1px", - backgroundColor: "#D9D9D9", - }} - /> + <div style={dividerStyle} /> </div> ); }, };
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/web/src/views/login/login.stories.tsx(2 hunks)apps/web/src/views/register/register.stories.tsx(5 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
apps/web/src/views/register/register.stories.tsx (2)
apps/web/src/views/login/components/TextField.tsx (1)
TextField(23-175)apps/web/src/views/register/components/TopBar.tsx (1)
TopBar(11-64)
apps/web/src/views/login/login.stories.tsx (2)
apps/web/src/views/login/components/TextField.tsx (1)
TextField(23-175)apps/web/src/views/login/components/Button.tsx (1)
Button(9-33)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Deploy Storybook to Chromatic
🔇 Additional comments (6)
apps/web/src/views/register/register.stories.tsx (5)
2-2: 필요한 import 추가 확인새로운 탑바 스토리에서 사용하는
Image와TopBarimport가 적절하게 추가되었습니다.Also applies to: 6-6
42-43: TextField 기능 향상 적용 완료
useValidationStatus와useResetButton속성이 추가되어 유효성 검사 상태 표시 및 리셋 기능이 활성화되었습니다.
55-55: 비밀번호 확인 기능 구현 검토비밀번호 확인 필드가 추가되고 유효성 검사 상태 표시가 활성화되었습니다. 검증 로직은 비밀번호와 비밀번호 확인 값을 정확히 비교합니다.
참고: 비밀번호 확인을 먼저 입력한 후 비밀번호를 변경하면 불일치 에러가 표시되는데, 이는 의도된 동작입니다.
Also applies to: 71-71, 77-78, 86-86
113-113: 닉네임 필드에 유효성 검사 상태 표시 추가닉네임 TextField에
useValidationStatus속성이 추가되어 다른 입력 필드와 일관된 UX를 제공합니다.
120-136: TopBar 스토리 검증 완료 - 아이콘 경로 확인됨아이콘 파일이 올바른 위치에 존재합니다:
./apps/web/public/icons/components/arrow-left-black.pngNext.js의 public 폴더 자산 참조 방식에 따라
"/icons/components/arrow-left-black.png"경로가 올바르게 지정되었습니다. 스토리에서 TopBar 컴포넌트가 적절하게 구현되었으며, 모든 파일 참조가 유효합니다.apps/web/src/views/login/login.stories.tsx (1)
2-2: Link 컴포넌트 import 추가네비게이션 링크를 위한 Next.js Link 컴포넌트가 적절하게 import되었습니다.
widse
left a comment
There was a problem hiding this comment.
@seungdeok
깔끔한 작업 감사합니다!! 참고해서 추가 작업 할게용 ㅎㅎ..!!! :)
| <button type="button" onClick={() => onChange?.("")} aria-label="초기화" style={{ cursor: "pointer" }}> | ||
| <Image src="/icons/components/reset.png" alt="초기화" width={20} height={20} /> | ||
| </button> |
📝 PR 유형
📝 PR 설명
로그인 및 회원가입 페이지의 UI 컴포넌트와 관련 기능들을 구현했습니다.
관련된 이슈 넘버
✅ 작업 목록
MR하기 전에 확인해주세요
📚 논의사항
📚 ETC
Summary by CodeRabbit
새로운 기능
사용자 경험 개선