[FE] SISC1-171 [FEAT] 이메일/비밀번호 찾기 모달, 이메일 결과/비밀번호 재설정 모달 구현#54
Conversation
Walkthrough로그인 루트가 기본 페이지로 바뀌고, 로그인/복구 흐름이 모달 기반으로 재구성되었습니다. VerificationModal, FindEmailResultModal, ResetPasswordModal이 추가되었고, 회원가입용 EmailVerificationModal 및 관련 스타일은 삭제되었습니다. 로그인 폼은 id 대신 email을 사용하도록 수정되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant LoginForm
participant VerificationModal
participant FindEmailResultModal
participant ResetPasswordModal
rect rgba(200,230,255,0.12)
note over User,LoginForm: 이메일 찾기 흐름
User->>LoginForm: "이메일 찾기" 클릭
LoginForm->>VerificationModal: 모달 열기 (전화번호 인증)
User->>VerificationModal: 번호 입력, 코드 전송/입력
VerificationModal-->>LoginForm: onSuccess({ email })
LoginForm->>FindEmailResultModal: 결과 모달 표시
User->>FindEmailResultModal: 닫기
FindEmailResultModal-->>LoginForm: onClose
end
rect rgba(200,255,200,0.12)
note over User,LoginForm: 비밀번호 재설정 흐름
User->>LoginForm: "비밀번호 찾기" 클릭
LoginForm->>VerificationModal: 모달 열기 (전화번호 인증)
User->>VerificationModal: 인증 완료
VerificationModal-->>LoginForm: onSuccess()
LoginForm->>ResetPasswordModal: 재설정 모달 표시
User->>ResetPasswordModal: 새 비밀번호 제출
ResetPasswordModal-->>LoginForm: onClose
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 13
🧹 Nitpick comments (8)
frontend/src/components/login/ResetPasswordModal.jsx (1)
8-21: 중복된 유효성 검사 로직 개선 권장비밀번호 유효성 검사가
isPasswordValid변수(Line 23)와handleSubmit함수 내부(Lines 10-17)에서 중복으로 수행되고 있습니다.isPasswordValid검사만으로도 충분하므로handleSubmit내의 중복 검사를 제거하는 것을 권장합니다.다음과 같이 리팩토링할 수 있습니다:
const handleSubmit = (e) => { e.preventDefault(); - if (password !== confirmPassword) { - alert('비밀번호가 일치하지 않습니다.'); - return; - } - if (password.length < 8) { - alert('비밀번호는 8자 이상이어야 합니다.'); - return; - } console.log('비밀번호 재설정 API 호출'); alert('비밀번호가 성공적으로 재설정되었습니다.'); onClose(); };참고: 버튼이
disabled={!isPasswordValid}로 이미 보호되어 있으므로, 폼 제출 시 추가 검사가 불필요합니다.frontend/src/components/login/FindEmailResultModal.jsx (1)
4-6: 사용되지 않는 함수 제거
handleSubmit함수가 정의되어 있지만 컴포넌트 어디에서도 사용되지 않습니다. 죽은 코드는 제거하는 것이 좋습니다.다음 diff를 적용하세요:
const FindEmailResultModal = ({ onClose, result }) => { - const handleSubmit = (e) => { - e.preventDefault(); - }; - return (frontend/src/components/VerificationModal.module.css (1)
73-76: input 요소의 display: flex 검토 필요
.newPassword클래스가 input 요소에 적용되는데display: flex가 설정되어 있습니다. input 요소는 인라인 교체 요소(inline replaced element)이므로 flex 컨테이너로 사용하는 것이 적절하지 않으며, 의도한 효과가 나타나지 않을 수 있습니다.
width: 50%만 필요한 경우 다음과 같이 수정하는 것을 권장합니다:.newPassword { - display: flex; width: 50%; }만약 flex 레이아웃이 필요하다면, input을 감싸는 컨테이너 div에 적용해야 합니다.
frontend/src/components/login/LoginForm.jsx (1)
87-101: 시맨틱 HTML 개선 권장
href속성 없이onClick만 있는 앵커 태그는 시맨틱하지 않으며 키보드 네비게이션에 문제가 있을 수 있습니다.button요소를 사용하거나href="#"및preventDefault를 추가하는 것을 권장합니다.다음과 같이 개선할 수 있습니다:
<div> <a className={styles.text} + href="#" - onClick={() => setModalStep('verifyPhoneForEmail')} + onClick={(e) => { + e.preventDefault(); + setModalStep('verifyPhoneForEmail'); + }} > 이메일 찾기 </a> <span className={styles.divider} aria-hidden="true"> | </span> <a className={styles.text} + href="#" - onClick={() => setModalStep('verifyPhoneForPassword')} + onClick={(e) => { + e.preventDefault(); + setModalStep('verifyPhoneForPassword'); + }} > 비밀번호 찾기 </a> </div>또는 더 나은 방법으로 button 요소를 사용:
<div> <button type="button" className={styles.text} onClick={() => setModalStep('verifyPhoneForEmail')} > 이메일 찾기 </button> <span className={styles.divider} aria-hidden="true"> | </span> <button type="button" className={styles.text} onClick={() => setModalStep('verifyPhoneForPassword')} > 비밀번호 찾기 </button> </div>frontend/src/components/VerificationModal.jsx (4)
4-4: PropTypes 또는 TypeScript로 prop 타입을 정의하세요.런타임 타입 체크를 위해 PropTypes를 추가하거나 TypeScript로 마이그레이션하는 것을 권장합니다. 특히
onSuccess콜백의 매개변수 형식을 명확히 해야 합니다.PropTypes를 추가하려면:
import { useState } from 'react'; import PropTypes from 'prop-types'; import styles from './VerificationModal.module.css'; const VerificationModal = ({ title, onClose, onSuccess }) => { // ... existing code }; VerificationModal.propTypes = { title: PropTypes.string.isRequired, onClose: PropTypes.func.isRequired, onSuccess: PropTypes.func.isRequired, }; export default VerificationModal;
76-95: 인증번호 만료 타이머를 표시하세요.사용자가 인증번호의 유효 시간을 알 수 있도록 카운트다운 타이머를 추가하는 것을 권장합니다.
타이머 상태 및 로직 추가:
const [timer, setTimer] = useState(180); // 3분 useEffect(() => { if (isCodeSent && timer > 0) { const interval = setInterval(() => { setTimer((prev) => prev - 1); }, 1000); return () => clearInterval(interval); } }, [isCodeSent, timer]); const formatTime = (seconds) => { const mins = Math.floor(seconds / 60); const secs = seconds % 60; return `${mins}:${secs.toString().padStart(2, '0')}`; };UI에 타이머 표시:
<label htmlFor="verification-code"> 인증번호 {isCodeSent && timer > 0 && `(${formatTime(timer)})`} </label>
98-104: 닫기 버튼에 확인 로직을 추가하세요.인증 진행 중에 모달을 닫으려 할 때 확인 메시지를 표시하여 실수로 닫는 것을 방지하세요.
+const handleClose = () => { + if (isCodeSent) { + if (window.confirm('인증을 취소하시겠습니까?')) { + onClose(); + } + } else { + onClose(); + } +}; + <button type="button" - onClick={onClose} + onClick={handleClose} className={`${styles.button} ${styles.closeButton}`} > 닫기 </button>
1-112: 전반적인 개선 사항 요약이 컴포넌트는 기본 기능은 작동하지만, 프로덕션 환경에서 사용하기 위해서는 다음 사항들을 개선해야 합니다:
- 보안 & 검증: 전화번호 및 인증번호 형식 검증 필수
- 에러 처리: Alert 대신 UI 기반 에러 메시지 표시
- 접근성: ARIA 속성 추가 및 키보드 네비게이션 지원
- UX: 로딩 상태, 타이머, 입력 필드 수정 가능 등
- 국제화: 하드코딩된 한국어 텍스트를 i18n 시스템으로 교체
PR 설명에 언급된 대로 API 연동이 필요하며, 위의 제안사항들을 함께 적용하면 더 견고한 컴포넌트가 될 것입니다.
위 개선 사항들 중 우선순위가 높은 항목부터 적용하시는 것을 권장합니다. 추가 도움이 필요하시면 말씀해 주세요!
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
frontend/src/components/LoginAndSignUpForm.module.css(1 hunks)frontend/src/components/VerificationModal.jsx(1 hunks)frontend/src/components/VerificationModal.module.css(1 hunks)frontend/src/components/login/FindEmailResultModal.jsx(1 hunks)frontend/src/components/login/LoginForm.jsx(1 hunks)frontend/src/components/login/ResetPasswordModal.jsx(1 hunks)frontend/src/components/login/SocialLoginButtons.jsx(1 hunks)frontend/src/components/login/SocialLoginButtons.module.css(1 hunks)frontend/src/components/signup/EmailVerificationModal.jsx(0 hunks)frontend/src/components/signup/EmailVerificationModal.module.css(0 hunks)frontend/src/components/signup/SignUpForm.jsx(4 hunks)
💤 Files with no reviewable changes (2)
- frontend/src/components/signup/EmailVerificationModal.module.css
- frontend/src/components/signup/EmailVerificationModal.jsx
🧰 Additional context used
🧬 Code graph analysis (4)
frontend/src/components/login/LoginForm.jsx (3)
frontend/src/components/login/ResetPasswordModal.jsx (1)
password(5-5)frontend/src/components/VerificationModal.jsx (1)
VerificationModal(4-110)frontend/src/components/login/FindEmailResultModal.jsx (1)
FindEmailResultModal(3-29)
frontend/src/components/login/ResetPasswordModal.jsx (1)
frontend/src/components/VerificationModal.jsx (1)
handleSubmit(24-42)
frontend/src/components/login/FindEmailResultModal.jsx (1)
frontend/src/components/VerificationModal.jsx (1)
handleSubmit(24-42)
frontend/src/components/VerificationModal.jsx (1)
frontend/src/components/signup/SignUpForm.jsx (1)
phoneNumber(10-10)
🔇 Additional comments (4)
frontend/src/components/login/ResetPasswordModal.jsx (1)
18-18: API 연동 필요현재 비밀번호 재설정 API 호출이 구현되지 않았습니다. PR 설명에 언급된 대로 향후 실제 API 연동이 필요합니다.
frontend/src/components/login/SocialLoginButtons.module.css (1)
34-37: LGTM!버튼 내 이미지 크기를 조정하는 스타일링이 적절합니다.
frontend/src/components/signup/SignUpForm.jsx (1)
5-5: LGTM!
EmailVerificationModal에서 공유VerificationModal컴포넌트로의 마이그레이션이 적절히 수행되었습니다. 상태 변수 및 핸들러 이름 변경도 일관성 있게 처리되었습니다.Also applies to: 12-12, 46-51, 78-78, 125-130
frontend/src/components/LoginAndSignUpForm.module.css (1)
82-106: LGTM!텍스트 컨테이너, 디바이더 스타일링 및 색상 업데이트가 새로운 모달 기반 복구 플로우를 잘 지원합니다. Flexbox 레이아웃과 스타일링이 적절하게 구현되었습니다.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
frontend/src/components/login/ResetPasswordModal.jsx (1)
23-23: 엄격한 동등 연산자(===) 사용 필요
==는 타입 강제 변환을 수행하여 예상치 못한 동작을 일으킬 수 있습니다. 비밀번호 비교 시 엄격한 동등 연산자(===)를 사용해야 합니다.다음 diff를 적용하여 수정하세요:
- const isPasswordValid = password == confirmPassword && password.length >= 8; + const isPasswordValid = password === confirmPassword && password.length >= 8;
🧹 Nitpick comments (2)
frontend/src/components/login/ResetPasswordModal.jsx (2)
14-16: 비밀번호 강도 요구사항 추가를 권장합니다.현재는 8자 이상의 길이만 검증하고 있습니다. 보안을 강화하기 위해 숫자, 특수문자, 대소문자 조합 등의 추가 요구사항을 고려해보세요.
예시:
const validatePassword = (password) => { const minLength = password.length >= 8; const hasNumber = /\d/.test(password); const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password); return minLength && hasNumber && hasSpecialChar; };
26-63: 접근성(Accessibility) 개선이 필요합니다.모달에 적절한 ARIA 속성이 누락되어 스크린 리더 사용자가 모달을 제대로 인식하지 못할 수 있습니다.
다음 속성들을 추가하세요:
- <div className={styles.overlay}> + <div className={styles.overlay} role="dialog" aria-modal="true" aria-labelledby="reset-password-title"> - <div className={styles.modal}> + <div className={styles.modal}> - <h1>비밀번호 재설정</h1> + <h1 id="reset-password-title">비밀번호 재설정</h1>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
frontend/src/components/VerificationModal.jsx(1 hunks)frontend/src/components/VerificationModal.module.css(1 hunks)frontend/src/components/login/ResetPasswordModal.jsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/components/VerificationModal.jsx
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/components/login/ResetPasswordModal.jsx (1)
frontend/src/components/VerificationModal.jsx (1)
handleSubmit(28-46)
🪛 Biome (2.1.2)
frontend/src/components/VerificationModal.module.css
[error] 98-98: Duplicate properties can lead to unexpected behavior and may override previous declarations unintentionally.
color is already defined here.
Remove or rename the duplicate property to ensure consistent styling.
(lint/suspicious/noDuplicateProperties)
🔇 Additional comments (3)
frontend/src/components/login/ResetPasswordModal.jsx (1)
18-20: API 연결 및 에러 처리 구현 필요현재는 placeholder console.log만 있고 실제 API 연결이 없습니다. PR 목표에서 언급된 대로 API 연결이 필요하며, try-catch를 사용한 에러 처리도 추가해야 합니다.
VerificationModal.jsx(27-45행)의 패턴을 참고하여 다음과 같이 구현할 수 있습니다:
try { // const result = await api.resetPassword(password); alert('비밀번호가 성공적으로 재설정되었습니다.'); onClose(); } catch (error) { console.error('비밀번호 재설정 실패:', error); alert('비밀번호 재설정에 실패했습니다. 다시 시도해주세요.'); }API 엔드포인트가 준비되었는지 확인이 필요합니다.
frontend/src/components/VerificationModal.module.css (2)
84-105: 버튼 스타일 정의 확인 완료
.button클래스에font-size: 16px(100행),font-weight: 500(102행)이 올바르게 정의되어 있습니다. 과거 코멘트에서 지적된 누락 사항이 해결된 것으로 보입니다.
73-76: width 속성이 50%로 제한되어 있습니다.
.newPassword클래스가 width를 50%로 제한하고 있어 ResetPasswordModal의 비밀번호 입력 필드가 의도한 것보다 좁게 표시될 수 있습니다. 디자인 요구사항과 일치하는지 확인이 필요합니다.피그마 디자인에서 비밀번호 입력 필드의 너비가 50%인지 확인해주세요. 만약 전체 너비를 사용해야 한다면:
.newPassword { display: flex; - width: 50%; + width: 100%; }
GamjaIsMine02
left a comment
There was a problem hiding this comment.
리뷰 검토하고 수정해서 PR 요청했습니다! 제가 아직 모르는게 너무 많네요
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (6)
frontend/src/components/VerificationModal.jsx (5)
15-15: alert 대신 인라인 메시지 UI를 사용하세요.
alert()는 UX를 저해합니다. 모달 내에 에러/성공 메시지를 표시하는 상태와 UI를 추가하는 것이 좋습니다.상태 추가:
const [phoneNumber, setPhoneNumber] = useState(''); const [verificationCode, setVerificationCode] = useState(''); const [isCodeSent, setIsCodeSent] = useState(false); const [sendButtonDisabled, setSendButtonDisabled] = useState(false); + const [message, setMessage] = useState({ type: '', text: '' });JSX에 메시지 표시 영역 추가:
<form onSubmit={handleSubmit}> {message.text && ( <div className={message.type === 'error' ? styles.errorMessage : styles.successMessage}> {message.text} </div> )} {/* ... 나머지 폼 */} </form>Also applies to: 23-23, 31-31, 40-40, 44-44
48-122: 접근성 향상을 위해 ARIA 속성을 추가하세요.스크린 리더 사용자를 위해 적절한 ARIA 속성을 추가해야 합니다.
- <div className={styles.overlay}> + <div className={styles.overlay} role="dialog" aria-modal="true" aria-labelledby="modal-title"> <div className={styles.modal}> <div className={styles.modalHeader}> - <h1>{title}</h1> + <h1 id="modal-title">{title}</h1> <button type="button" className={styles.closeButton} onClick={onClose} + aria-label="모달 닫기" > × </button> </div> <form onSubmit={handleSubmit}> <div className={styles.inputGroup}> <label htmlFor="phone-number">전화번호 인증</label> <div className={styles.verificationContainer}> <input type="tel" id="phone-number" value={phoneNumber} onChange={(e) => setPhoneNumber(e.target.value)} placeholder="'-' 없이 입력" className={styles.codeInput} + aria-required="true" + aria-invalid={phoneNumber && !/^01[0-9]{1}[0-9]{7,8}$/.test(phoneNumber)} />
19-25: API 응답에 따라 버튼 상태를 제어하세요.현재는 3초 타이머로 버튼을 무조건 재활성화하는데, 이는 API 성공/실패와 무관합니다. 실제 API 응답을 기다린 후 상태를 변경해야 합니다.
setSendButtonDisabled(true); console.log(`${phoneNumber}로 인증번호 전송 API 호출`); - // 실제 api 추가 + + try { + // const response = await api.sendVerificationCode(phoneNumber); + alert('인증번호가 전송되었습니다.'); + setIsCodeSent(true); + setTimeout(() => setSendButtonDisabled(false), 60000); // 재전송은 60초 후 + } catch (error) { + console.error('인증번호 전송 실패:', error); + alert('인증번호 전송에 실패했습니다.'); + } finally { + setSendButtonDisabled(false); + } - - alert('인증번호가 전송되었습니다.'); - setIsCodeSent(true); - setTimeout(() => setSendButtonDisabled(false), 3000);
36-41: 실제 API 연동 및 검증 로직을 구현하세요.현재 mock 데이터를 사용하고 있어 실패 케이스를 테스트할 수 없습니다. 또한 인증번호 형식 검증(6자리 숫자)이 없습니다.
+ // 인증번호 형식 검증 (6자리 숫자) + if (!/^\d{6}$/.test(verificationCode)) { + alert('인증번호는 6자리 숫자여야 합니다.'); + return; + } + console.log(`${phoneNumber}와 ${verificationCode}로 인증 확인 API 호출`); try { - // <<-- 실제 API 호출 로직: const result = await api.verifyCode(phoneNumber, code) -->> - - const mockResult = { email: 'user@example.com', message: '인증 성공' }; + const result = await api.verifyCode(phoneNumber, verificationCode); alert('인증에 성공했습니다!'); - onSuccess(mockResult); + onSuccess(result); } catch (error) {
100-106: 제출 버튼에 로딩 상태를 추가하세요.사용자의 중복 클릭을 방지하고 API 호출 중임을 시각적으로 표시해야 합니다.
상태 추가:
const [sendButtonDisabled, setSendButtonDisabled] = useState(false); + const [isVerifying, setIsVerifying] = useState(false);handleSubmit 함수 수정:
const handleSubmit = async (e) => { e.preventDefault(); + setIsVerifying(true); try { // API 호출 onSuccess(result); } catch (error) { console.error('인증 실패:', error); alert('인증번호가 올바르지 않습니다.'); + } finally { + setIsVerifying(false); } };버튼 업데이트:
<button type="submit" className={`${styles.button} ${styles.submitButton}`} - disabled={!isCodeSent} + disabled={!isCodeSent || isVerifying} > - 인증 확인 + {isVerifying ? '확인 중...' : '인증 확인'} </button>frontend/src/components/VerificationModal.module.css (1)
115-118: 중복된 color 속성을 제거하세요.Line 115와 118에서
color속성이 중복 정의되어 있습니다. Line 118의color: #fff;가 Line 115의color: white;를 덮어씁니다.Based on static analysis hints
다음 diff를 적용하여 중복을 제거하세요:
color: white; border: none; - color: #fff; font-family: Pretendard;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
frontend/src/App.jsx(1 hunks)frontend/src/components/VerificationModal.jsx(1 hunks)frontend/src/components/VerificationModal.module.css(1 hunks)frontend/src/pages/Login.css(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/App.jsx (1)
frontend/src/pages/Login.jsx (1)
Login(4-10)
🪛 Biome (2.1.2)
frontend/src/components/VerificationModal.module.css
[error] 118-118: Duplicate properties can lead to unexpected behavior and may override previous declarations unintentionally.
color is already defined here.
Remove or rename the duplicate property to ensure consistent styling.
(lint/suspicious/noDuplicateProperties)
🔇 Additional comments (1)
frontend/src/pages/Login.css (1)
1-109: CSS 구조가 잘 정의되어 있습니다.로그인 페이지의 레이아웃과 스타일이 체계적으로 구성되어 있습니다. 특히
.error-message클래스에서 고정 높이를 예약하여 레이아웃 이동을 방지한 점이 좋습니다.
DongEun02
left a comment
There was a problem hiding this comment.
코멘트 단 건 거의 없는데 예외 처리에 좀 더 신경 써주시면 될 것 같아요
GamjaIsMine02
left a comment
There was a problem hiding this comment.
추가로 수정했습니다!
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
frontend/src/components/login/FindEmailResultModal.jsx (2)
4-6: 사용되지 않는 함수를 제거하세요.
handleSubmit함수가 정의되어 있지만 JSX 어디에서도 사용되지 않습니다. 이 함수는 제거하는 것이 좋습니다.다음 diff를 적용하세요:
const FindEmailResultModal = ({ onClose, result }) => { - const handleSubmit = (e) => { - e.preventDefault(); - }; - return (
3-3: PropTypes 또는 TypeScript 타입 정의를 고려해보세요.prop 검증을 위해 PropTypes를 추가하거나 향후 TypeScript로 마이그레이션하는 것을 고려해보세요. 이는 런타임 오류를 방지하고 개발 경험을 개선할 수 있습니다.
PropTypes를 추가하는 예시:
import PropTypes from 'prop-types'; import styles from '../VerificationModal.module.css'; const FindEmailResultModal = ({ onClose, result }) => { // ... component code }; FindEmailResultModal.propTypes = { onClose: PropTypes.func.isRequired, result: PropTypes.string, }; export default FindEmailResultModal;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
frontend/src/components/login/FindEmailResultModal.jsx(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/components/login/FindEmailResultModal.jsx (1)
frontend/src/components/VerificationModal.jsx (1)
handleSubmit(28-46)
🔇 Additional comments (1)
frontend/src/components/login/FindEmailResultModal.jsx (1)
13-20: 조건부 렌더링이 잘 구현되었습니다.
result가 없을 때에 대한 예외 처리가 잘 되어 있습니다. 과거 리뷰에서 지적된 null/undefined 체크가 적절하게 반영되었습니다.
1) 작업한 이슈번호
SISC1-171 이메일/비밀번호 찾기 모달, 이메일 결과/비밀번호 재설정 모달
2) 변경 요약 (What & Why)
3) 스크린샷/동영상 (UI 변경 시)
4) 상세 변경사항 (전부 다)
5) 참고사항
사용자 정보가 없을 때의 처리 필요
함수 분리 & API 연결 필요
Summary by CodeRabbit
New Features
Refactor
Style