Conversation
Walkthrough로그인 페이지 UI를 구현하는 기능 추가로, LoginPage 컴포넌트, 폼 검증 및 오류 상태 처리, 입력 필드 오류 표시, 로딩 상태 관리, 전용 레이아웃 컴포넌트(AuthPageView), 오류 애니메이션 및 관련 스타일을 도입합니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant LoginPage as LoginPage
participant LoginForm as LoginForm
participant Utils as Validators
participant UI as Input/Button/Label
User->>LoginPage: 페이지 방문
LoginPage->>LoginForm: 마운트
User->>UI: 사용자명 입력
LoginForm->>Utils: trim & 검증
Utils->>LoginForm: 오류 초기화
LoginForm->>UI: hasError = false
User->>UI: 비밀번호 입력
LoginForm->>Utils: trim & 검증
Utils->>LoginForm: 오류 초기화
LoginForm->>UI: hasError = false
User->>UI: 제출 버튼 클릭
LoginForm->>Utils: isValidSubmit() 호출
alt 검증 실패
Utils-->>LoginForm: false
LoginForm->>LoginForm: error = true
LoginForm->>UI: LoginFormErrorLabel 표시
UI->>UI: 언더라인 + shake 애니메이션
else 검증 성공
Utils-->>LoginForm: true
LoginForm->>LoginForm: error = false, loading = true
LoginForm->>UI: 버튼 disabled, 로딩 표시
LoginForm->>LoginForm: 3초 후 loading = false
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related issues
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (9)
src/styles/base/layouts.css (1)
22-24: 레이아웃 토큰 분리 정의는 괜찮지만, 하나의 @theme 블록으로 정리하는 것도 고려해볼 만합니다.
--top-header-height,--bottom-navigation-height토큰 추가로 레이아웃 일관성 확보에는 도움이 됩니다. 다만 spacing 토큰과 같은 레벨의 전역 레이아웃 토큰이라면, 동일한@theme블록 안에 함께 두면 추후 토큰 관리/검색이 조금 더 수월할 수 있습니다. 현재 상태도 동작에는 문제 없습니다.src/features/auth/components/form/login-form.module.css (1)
7-23: 고정된margin-top: 14.75rem는 작은 화면에서 레이아웃 깨질 수 있습니다.현재 구조상 버튼 영역을 아래로 내리기 위해 큰
margin-top을 사용하고 있는데, 모바일 기기나 짧은 뷰포트에서 버튼이 화면 밖으로 밀릴 수 있습니다.가능하다면 다음과 같은 방향을 검토해보면 좋겠습니다.
- 부모 컨테이너에서
display: flex; flex-direction: column; justify-content: space-between;등을 사용해 자연스럽게 위/아래를 분리하거나- 버튼 컨테이너에
margin-top: auto;를 주는 방식으로, 뷰포트 높이에 따라 유동적으로 배치되도록 조정디자인 시안이 고정 오프셋을 요구하지 않는다면, 유연한 레이아웃으로 바꾸는 것을 추천합니다.
src/features/auth/components/input/password-input.module.css (1)
61-68: 에러 상태 언더라인/애니메이션 처리 방향 좋습니다.
containerError에서 언더라인 높이와 색을 빨간색으로 고정하고var(--animation-error-shake-once)를 적용해, 에러 시 시각적 피드백이 명확해졌습니다.작은 제안 하나만 드리면, 높이 변화 애니메이션도 사용하고 싶다면
transition에var(--transition-height-fast)를 함께 두어 포커스/에러 간 전환 느낌을 맞춰주는 것도 고려해볼 수 있습니다.src/app/(auth)/login/page.tsx (1)
1-12: LoginPage 구조는 단순명료하며, LoginForm를 감싸는 역할에 잘 맞습니다.두 가지 정도만 확인/제안 드립니다.
LoginForm Client Component 여부 확인 필요
LoginForm에서useState등 React 훅을 사용하고 있어, Next App Router 기준으로는 해당 파일 상단에"use client"선언이 있어야 합니다. 스니펫 상단이 잘리지 않았다면, 한 번 더 확인 부탁드립니다. 이 페이지 컴포넌트 자체는 Server Component여도, 자식으로 Client Component를 두는 패턴은 문제 없습니다.메인 카피를 시맨틱 heading으로 감싸는 것 고려
"먹고 싶은 학식, 바로 주문해요!"가 로그인 페이지의 대표 문구라면,<div>대신<h1>등 heading 태그로 감싸 두면 접근성/SEO 측면에서 이점이 있습니다. 예시:- <div className={styles.label}>먹고 싶은 학식, 바로 주문해요!</div> + <h1 className={styles.label}>먹고 싶은 학식, 바로 주문해요!</h1>(이미 상위 레이아웃에 다른
h1이 있다면 그 구조를 우선 고려해 주세요.)src/shared/components/layout/page-frame/view/auth-page-view.module.css (1)
1-13: 인증 페이지 전용 레이아웃이 추가되었습니다.
page-view.module.css와 매우 유사한 구조이지만 safe-area 변수를 사용하는 점이 다릅니다. 두 레이아웃의 중복을 줄이기 위해 공통 베이스 스타일과 변형(variant)을 고려할 수 있지만, 인증 페이지와 일반 페이지의 의미적 분리를 위해 현재 구조가 적절할 수 있습니다.만약 통합을 고려한다면 다음과 같은 접근을 생각해볼 수 있습니다:
/* 공통 베이스 */ .containerBase { background-color: var(--color-white); display: flex; flex-direction: column; justify-content: flex-start; align-items: stretch; max-width: var(--container-3xl); min-height: 100vh; padding-inline: var(--spacing-gutter); width: 100%; } /* 변형: 인증 페이지 */ .containerAuth { padding-top: var(--safe-top); padding-bottom: var(--safe-bottom); } /* 변형: 일반 페이지 */ .containerDefault { padding-top: var(--top-header-height); padding-bottom: var(--bottom-navigation-height); }src/features/auth/components/input/UsernameInput.tsx (1)
20-22: className 조건부 로직이 명확합니다.에러 상태에 따라 적절한 클래스를 적용하고 있습니다.
PasswordInput.tsx에서도 동일한 패턴이 사용되고 있으므로, 선택적으로 공통 유틸리티 함수로 추출하여 중복을 줄일 수 있습니다.선택적 리팩토링 예시:
// src/features/auth/utils/input/getInputContainerClassName.ts export function getInputContainerClassName( styles: Record<string, string>, hasError: boolean ): string { return hasError ? `${styles.container} ${styles.containerError}` : styles.container; }사용:
const containerClassName = getInputContainerClassName(styles, hasError);Also applies to: 25-25
src/features/auth/components/input/PasswordInput.tsx (1)
25-27: 에러 상태 className 로직이 UsernameInput과 동일합니다.두 입력 컴포넌트에서 동일한 패턴을 사용하고 있어 일관성이 유지됩니다. UsernameInput 리뷰에서 언급한 것처럼, 선택적으로 공통 유틸리티 함수로 추출하여 중복을 줄일 수 있습니다.
Also applies to: 37-37
src/shared/components/layout/page-frame/view/AuthPageView.tsx (1)
1-1: 선택 사항: React 19에서는 React import가 불필요합니다.React 19에서는 새로운 JSX transform이 적용되어 JSX 사용 시 React를 명시적으로 import할 필요가 없습니다. 이 import는 제거해도 됩니다.
다음 diff를 적용하여 불필요한 import를 제거하세요:
-import React from "react"; import styles from "./auth-page-view.module.css";src/features/auth/components/form/LoginForm.tsx (1)
44-47: TODO 주석: API 연동을 추적해야 합니다.로그인 API 연동이 필요하다는 TODO 주석이 있습니다. 현재는 3초 딜레이로 시뮬레이션되고 있습니다.
이 TODO 항목을 추적하기 위한 별도 이슈를 생성하시겠습니까? 또는 API 연동 코드 작성을 도와드릴 수 있습니다.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
src/assets/icons/error-circle-filled.svgis excluded by!**/*.svg
📒 Files selected for processing (20)
src/app/(auth)/layout.tsx(2 hunks)src/app/(auth)/login/page.module.css(1 hunks)src/app/(auth)/login/page.tsx(1 hunks)src/app/globals.css(1 hunks)src/assets/icons/index.ts(1 hunks)src/features/auth/components/form/LoginForm.tsx(1 hunks)src/features/auth/components/form/login-form.module.css(1 hunks)src/features/auth/components/input/PasswordInput.tsx(2 hunks)src/features/auth/components/input/UsernameInput.tsx(1 hunks)src/features/auth/components/input/password-input.module.css(1 hunks)src/features/auth/components/input/username-input.module.css(1 hunks)src/features/auth/components/label/LoginFormErrorLabel.tsx(1 hunks)src/features/auth/components/label/login-form-error-label.module.css(1 hunks)src/features/auth/utils/form/loginFormUtils.ts(1 hunks)src/shared/components/layout/page-frame/view/AuthPageView.tsx(1 hunks)src/shared/components/layout/page-frame/view/auth-page-view.module.css(1 hunks)src/shared/components/layout/page-frame/view/page-view.module.css(1 hunks)src/shared/components/navigation/bottom-navigation.module.css(1 hunks)src/styles/base/animations.css(1 hunks)src/styles/base/layouts.css(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/app/(auth)/login/page.tsx (1)
src/features/auth/components/form/LoginForm.tsx (1)
LoginForm(11-80)
src/app/(auth)/layout.tsx (1)
src/shared/components/layout/page-frame/view/AuthPageView.tsx (1)
AuthPageView(4-14)
src/features/auth/components/form/LoginForm.tsx (5)
src/features/auth/utils/form/loginFormUtils.ts (2)
isValidSubmit(1-8)isDisabled(10-12)src/features/auth/components/input/UsernameInput.tsx (1)
UsernameInput(13-46)src/features/auth/components/input/PasswordInput.tsx (1)
PasswordInput(14-62)src/features/auth/components/label/LoginFormErrorLabel.tsx (1)
LoginFormErrorLabel(8-22)src/features/auth/components/button/LoginButton.tsx (1)
LoginButton(10-25)
🔇 Additional comments (19)
src/shared/components/navigation/bottom-navigation.module.css (1)
7-12: 바텀 네비 높이 및 안전 영역 패딩 토큰화 방향 좋습니다.
var(--bottom-navigation-height)와var(--safe-bottom)을 사용해 높이/패딩을 정의한 것은 디자인 토큰과 safe-area 전략과 잘 맞습니다. 레이아웃 변경 시에도 한 곳에서 조정 가능해 유지보수성이 좋아질 것 같습니다.src/assets/icons/index.ts (1)
1-1: 새 에러 아이콘 export 추가는 패턴과 일관적입니다.기존 아이콘들과 동일한 방식으로
ErrorCircleFilled를 export 하고 있어 사용성/일관성 측면에서 괜찮습니다.error-circle-filled.svg파일 경로와 파일명만 한 번 더 확인해 두면 충분할 것 같습니다.src/app/(auth)/layout.tsx (1)
2-13: Auth 전용 뷰 컴포넌트로 분리한 구조가 명확합니다.
PageContainer아래에AuthPageView를 두어 인증 영역만 별도의 레이아웃/스타일로 관리하는 구조가 깔끔합니다. Auth 관련 다른 페이지에서도 동일 뷰를 재사용하기 좋을 것 같습니다.src/app/globals.css (1)
6-6: 애니메이션 전역 import 위치가 적절합니다.
colors → layouts → typography → motion뒤에animations.css를 가져오는 순서라, 토큰/키프레임 정의가 전역에서 안정적으로 사용 가능해 보입니다. 로그인 폼 에러 애니메이션과 잘 연동될 것 같습니다.src/app/(auth)/login/page.module.css (3)
1-5: 컨테이너 레이아웃이 적절합니다.플렉스 컬럼 레이아웃과 중앙 정렬이 로그인 페이지에 적합합니다.
7-15: 로고 플레이스홀더 구현 확인이 필요합니다.현재 회색 배경의 플레이스홀더로 구현되어 있습니다. 실제 로고 이미지 또는 브랜드 컴포넌트로 교체할 계획인지 확인해 주세요.
17-26: 레이블 스타일링이 디자인 시스템을 잘 따르고 있습니다.모든 타이포그래피 속성이 CSS 변수를 적절히 사용하고 있습니다.
src/styles/base/animations.css (2)
3-5: 재사용 가능한 애니메이션 변수 정의가 우수합니다.CSS 커스텀 프로퍼티를 활용하여 에러 애니메이션을 전역적으로 재사용할 수 있도록 잘 구성되어 있습니다.
7-23: 에러 쉐이크 애니메이션이 적절하게 구현되었습니다.진폭이 점차 감소하는 수평 흔들림 효과가 에러 피드백에 적합하며, 0.25초의 짧은 지속시간으로 사용자 경험을 방해하지 않습니다.
src/features/auth/components/input/username-input.module.css (1)
59-66: 에러 상태 스타일링이 명확하고 효과적입니다.빨간색 언더라인과 쉐이크 애니메이션을 결합하여 사용자에게 명확한 에러 피드백을 제공합니다. 포커스 상태와 동일한 높이(0.125rem)를 사용하여 일관성을 유지하는 것도 좋습니다.
src/features/auth/components/input/UsernameInput.tsx (1)
10-10: 에러 상태 prop 추가가 적절합니다.선택적
hasErrorprop에 기본값false를 제공하여 기존 코드와의 호환성을 유지하고 있습니다.Also applies to: 17-17
src/features/auth/components/label/login-form-error-label.module.css (3)
1-6: 에러 레이블 컨테이너 레이아웃이 적절합니다.플렉스 레이아웃과 일관된 간격 계산으로 깔끔하게 구성되어 있습니다.
8-10:visibility: hidden사용이 우수한 선택입니다.
display: none대신visibility: hidden을 사용하여 에러 메시지가 나타나거나 사라질 때 레이아웃 이동을 방지합니다. 이는 사용자 경험을 개선하는 좋은 패턴입니다.
12-18: 에러 레이블 타이포그래피가 적절합니다.작은 폰트 크기와 빨간색을 사용하여 에러 메시지를 명확히 전달하면서도 UI를 압도하지 않습니다.
src/features/auth/components/input/PasswordInput.tsx (1)
11-11: 에러 상태 prop 추가가 UsernameInput과 일관됩니다.동일한 패턴으로
hasErrorprop을 구현하여 두 입력 컴포넌트 간 일관성을 유지하고 있습니다.Also applies to: 18-18
src/shared/components/layout/page-frame/view/page-view.module.css (1)
5-10: CSS 변수 검증 완료 - 코드 승인
--top-header-height와--bottom-navigation-height변수가src/styles/base/layouts.css의 23-24번 줄에서 정확하게 정의되어 있습니다. 헤더 및 네비게이션 높이가 일관되게 적용되고 있으며, 레이아웃 변경이 적절하게 구현되었습니다.src/features/auth/components/label/LoginFormErrorLabel.tsx (1)
8-22: LGTM!에러 라벨 컴포넌트가 잘 구현되어 있습니다. visible prop을 통한 조건부 렌더링이 적절하게 처리되었습니다.
src/features/auth/components/form/LoginForm.tsx (2)
18-33: LGTM!입력 필드 변경 핸들러가 잘 구현되어 있습니다. 사용자가 입력할 때 에러 상태를 적절히 초기화하는 로직이 좋습니다.
50-77: LGTM!폼 구조와 컴포넌트 통합이 잘 되어 있습니다. disabled 및 hasError props가 적절하게 전달되고 있습니다.
✨ 변경 사항
✅ 테스트
Summary by CodeRabbit
릴리스 노트
새로운 기능
스타일
✏️ Tip: You can customize this high-level summary in your review settings.