Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning
|
| Cohort / File(s) | Summary |
|---|---|
API: 비밀번호 변경frontend/src/apis/auth/changePassword.ts |
PUT API_BASE_URL/auth/user/로 { password } 전송하는 changePassword 헬퍼 추가. 실패 응답에서 메시지 파싱 후 Error throw. |
InputField: 성공 상태 지원frontend/src/components/common/InputField/InputField.styles.ts, frontend/src/components/common/InputField/InputField.tsx |
isSuccess?: boolean prop 추가 및 경계선/포커스 색상 로직에 성공 상태(#28a745) 반영. 프롭 전달 업데이트. |
Button: disabled 지원frontend/src/components/common/Button/Button.tsx |
disabled?: boolean prop 추가 및 :disabled 스타일(회색 배경, not-allowed 커서, 불투명도) 적용. |
Admin 사이드바frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx |
탭 라벨을 "비밀번호 수정"으로 변경하고 준비중 가드 제거(이전 경고 제거)하여 네비게이션 활성화. |
AccountEditTab 스타일frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.styles.ts |
SuccessMessage, ErrorMessage, GuidanceBox, GuidanceText 등 가이드/메시지용 styled 컴포넌트 추가. |
AccountEditTab 기능frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx |
UI를 비밀번호 변경 흐름으로 전환. 로컬 상태(newPassword, confirmPassword, successMessage, isLoading), PASSWORD_REGEX 기반 검증, 일치 검사, changePassword 호출 및 로딩/성공/오류 처리 추가. 입력에 isError/isSuccess/helperText 전달. |
Sequence Diagram(s)
sequenceDiagram
autonumber
actor Admin as 관리자
participant UI as AccountEditTab(UI)
participant Val as Validation
participant API as changePassword()
participant S as Server
Admin->>UI: 새 비밀번호/확인 입력
UI->>Val: 실시간 정책·일치 검증
Val-->>UI: 검증 상태(유효/오류)
Admin->>UI: 비밀번호 변경 클릭
UI->>Val: 재검증(필수/정책/일치)
alt 검증 실패
UI-->>Admin: 오류 표시
else 검증 통과
UI->>API: PUT /auth/user/ { password }
API->>S: 요청 전달
alt 성공
S-->>API: 200 OK
API-->>UI: resolve
UI-->>Admin: 성공 메시지 표시, 입력 초기화
else 실패
S-->>API: 에러(JSON message)
API-->>UI: Error throw(message)
UI-->>Admin: 오류 알림
end
end
Estimated code review effort
🎯 3 (Moderate) | ⏱️ ~25 minutes
Assessment against linked issues
| Objective | Addressed | Explanation |
|---|---|---|
| 관리자 계정 비밀번호 변경 구현 MOA-219 | ✅ | |
| 관리자 계정 비밀번호 초기화 기능 MOA-219 | ❌ | 초기화(리셋) 관련 API 호출 및 UI가 없음. |
Assessment against linked issues: Out-of-scope changes
| Code Change | Explanation |
|---|---|
사이드바 네비게이션 가드 제거 및 라벨 변경 (frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx) |
이슈는 비밀번호 변경/초기화 구현이 목적이며, 사이드바 네비게이션 동작 변경은 명시된 목표에 포함되지 않음. |
Possibly related issues
- [feature] MOA-219 관리자 계정 비밀번호 변경 및 초기화한다 - FE #725: 관리자 비밀번호 변경 기능과 직접적으로 연관(같은 기능 목표).
Possibly related PRs
- [feature] 사이드바 내용을 계층 구조로 변경한다 #711: 사이드바 탭 라벨/클릭 처리와 중복 변경 가능성.
- [feature & refactor] 객관식 항목 1개 허용 · 필수 입력 UX 개선 · Application 구조 리팩토링 #558: InputField 스타일/프롭 관련 변경과 연관성 있음.
- [feature] 관리자 계정의 비밀번호 변경 및 초기화한다 #722: 백엔드의 사용자 비밀번호 업데이트/초기화 변경과 기능적 연계 가능성.
Suggested labels
✨ Feature, 💻 FE, 📬 API
Suggested reviewers
- lepitaaar
- oesnuj
✨ Finishing Touches
- 📝 Generate Docstrings
🧪 Generate unit tests
- Create PR with unit tests
- Post copyable unit tests in a comment
- Commit unit tests in branch
feature/#725-password-fe-MOA-219
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 @coderabbitai help to get the list of available commands and usage tips.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (14)
frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx (2)
45-47: 라벨 변경은 적절합니다. 경로 의미 일치 여부만 확인하세요.라벨이 ‘비밀번호 수정’으로 바뀌었지만 경로는 여전히
/admin/account-edit입니다. URL 의미와 실제 탭 콘텐츠가 일치하는지(예:/admin/password-edit) 확인해 주세요. 기존 딥링크/북마크 영향도 함께 검토하면 좋겠습니다.
62-69: 주석 제거 및 라벨 의존 가드 → 경로 기반 가드로 전환 권장주석 코드는 삭제해 코드베이스를 깔끔히 유지하고, 가드 로직은 라벨이 아닌 경로에 의존하도록 바꾸면 카피 변경에 안전합니다.
적용 예시:
- // if (item.label === '아이디/비밀번호 수정') { - // alert('아이디/비밀번호 수정 기능은 아직 준비 중이에요. ☺️'); - // return; - // } - if (item.label === '회원탈퇴') { + if (item.path === '/admin/user-delete') { alert('회원탈퇴 기능은 아직 준비 중이에요. ☺️'); return; }frontend/src/components/common/InputField/InputField.styles.ts (2)
26-33: 중첩 삼항 연산자 제거로 가독성 개선(기본 border 색상)가이드라인상 중첩/복잡한 삼항은 피하는 것이 좋습니다. 함수 추출로 명확하게 표현해 주세요.
- border: 1px solid - ${({ hasError, isSuccess }) => - hasError ? 'red' : isSuccess ? '#28a745' : '#c5c5c5'}; + border: 1px solid ${({ hasError, isSuccess }) => getBorderColor({ hasError, isSuccess })};파일 상단(스타일 정의 위)에 유틸 추가:
const getBorderColor = ({ hasError, isSuccess }: { hasError?: boolean; isSuccess?: boolean }) => { if (hasError) return 'red'; if (isSuccess) return '#28a745'; return '#c5c5c5'; };
49-56: focus 상태도 함수로 추출해 일관성/가독성 확보- border-color: ${({ hasError, isSuccess, readOnly }) => - readOnly - ? '#c5c5c5' - : hasError - ? 'red' - : isSuccess - ? '#28a745' - : '#007bff'}; + border-color: ${({ hasError, isSuccess, readOnly }) => + readOnly ? '#c5c5c5' : getFocusBorderColor({ hasError, isSuccess })};동일 파일에 아래 유틸 추가:
const getFocusBorderColor = ({ hasError, isSuccess }: { hasError?: boolean; isSuccess?: boolean }) => { if (hasError) return 'red'; if (isSuccess) return '#28a745'; return '#007bff'; };추가로, 컬러 값(HEX)은 테마 토큰을 쓰면 유지보수가 쉬워집니다.
frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.styles.ts (1)
16-22: 스타일 컴포넌트 추가는 적절합니다. 색상 상수는 테마 토큰으로 치환 권장성공/에러/가이던스 UI가 직관적입니다. 다만
#28a745,#dc3545,#f8f9fa등 하드코딩 색상은 테마(예:theme.colors.success,theme.colors.danger)로 치환하면 일관성과 다크모드 대응이 용이합니다. 여백/폰트 사이즈도 디자인 토큰으로 통일하면 더 좋습니다.Also applies to: 24-30, 32-38, 40-50
frontend/src/apis/auth/changePassword.ts (1)
11-13: 헤더에 Accept 추가로 서버 의도 명시서버가 JSON을 반환한다면 Accept를 명시해 상호작용을 명확히 해주세요.
headers: { 'Content-Type': 'application/json', + 'Accept': 'application/json', },frontend/src/components/common/InputField/InputField.tsx (2)
20-21: isSuccess 도입은 👍. 접근성(ARIA) 속성 추가로 오류/헬퍼 연결 권장시각적 피드백 외에 스크린리더를 위한
aria-invalid,aria-describedby를 설정하면 접근성이 개선됩니다.<Styled.Input type={type === 'password' && !isPasswordVisible ? 'password' : 'text'} value={value} onChange={handleChange} placeholder={placeholder} maxLength={maxLength} disabled={disabled} hasError={isError} isSuccess={isSuccess} readOnly={readOnly} + aria-invalid={isError ? true : undefined} + aria-describedby={isError && helperText ? `${label ?? 'input'}-helper` : undefined} /> @@ - {isError && helperText && ( - <Styled.HelperText>{helperText}</Styled.HelperText> - )} + {isError && helperText && ( + <Styled.HelperText id={`${label ?? 'input'}-helper`}>{helperText}</Styled.HelperText> + )}Also applies to: 38-39, 76-77
9-10: Input 타입 제약 완화로 재사용성 향상다른 타입(email, number 등)도 사용할 수 있게 표준 타입으로 확장하는 것을 권장합니다.
-import { useState } from 'react'; +import { useState, type HTMLInputTypeAttribute } from 'react'; @@ - type?: 'text' | 'password'; + type?: HTMLInputTypeAttribute;frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx (6)
61-61: Form 시맨틱으로 Enter 제출/접근성 개선Enter 키로 제출되도록 form 래핑과 submit 타입을 권장합니다.
- <div> + <form onSubmit={(e) => { e.preventDefault(); handleChangePassword(); }}> @@ - <Button width={'100%'} animated onClick={handleChangePassword}> + <Button width={'100%'} animated type="submit" onClick={handleChangePassword}> 비밀번호 변경하기 </Button> - </div> + </form>Also applies to: 106-108
75-86: 비밀번호 입력 필드에 name/autoComplete 지정브라우저/패스워드 매니저 호환성과 보안 UX 향상을 위해 autoComplete와 name을 명시하세요.
<InputField placeholder='새 비밀번호' type='password' value={newPassword} onChange={(e) => setNewPassword(e.target.value)} onClear={() => setNewPassword('')} + name="new-password" + autoComplete="new-password" maxLength={PASSWORD_MAX_LENGTH} isError={isPasswordValid === false} isSuccess={isPasswordValid === true} helperText={isPasswordValid === false ? `영문, 숫자, 특수문자 포함 ${PASSWORD_MIN_LENGTH}~${PASSWORD_MAX_LENGTH}자` : ''} /> @@ <InputField placeholder='새 비밀번호 재입력' type='password' value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)} onClear={() => setConfirmPassword('')} + name="new-password-confirm" + autoComplete="new-password" maxLength={PASSWORD_MAX_LENGTH} isError={isPasswordMatching === false} isSuccess={isPasswordMatching === true} helperText={isPasswordMatching === false ? '비밀번호가 일치하지 않습니다.' : ''} />Also applies to: 89-99
51-57: alert 대신 인라인 에러 메시지 또는 토스트 사용 권장서버 오류를 alert로 표시하기보다 페이지 내 에러 블럭(이미 존재하는 Styled.ErrorMessage) 또는 글로벌 토스트로 일관 UX를 주는 것이 좋습니다.
- } catch (err) { + } catch (err) { - if (err instanceof Error) { - alert(err.message); - } else { - alert('알 수 없는 오류가 발생했습니다.'); - } + if (err instanceof Error) { + setFormError(err.message); + } else { + setFormError('알 수 없는 오류가 발생했습니다.'); + } }추가 상태/표시:
- const [successMessage, setSuccessMessage] = useState(''); + const [successMessage, setSuccessMessage] = useState(''); + const [formError, setFormError] = useState(''); @@ - {successMessage && <Styled.SuccessMessage>{successMessage}</Styled.SuccessMessage>} + {successMessage && <Styled.SuccessMessage>{successMessage}</Styled.SuccessMessage>} + {formError && <Styled.ErrorMessage>{formError}</Styled.ErrorMessage>}Also applies to: 102-104
18-27: tri-state(Boolean|null) 명명 개선 제안
isPasswordValid,isPasswordMatching은 이름상 boolean을 기대하게 합니다.passwordValid/passwordsMatch+showValidation분리 혹은validity같은 명명으로 혼선을 줄일 수 있습니다.
65-65: 줄바꿈<br />남용 대신 스타일로 간격 관리여백은 스타일(예: margin, Stack/Spacer 컴포넌트)로 관리하면 DOM 의미가 명확해지고 유지보수가 수월합니다.
Also applies to: 88-88, 101-101, 103-104
46-51: 언마운트 후 setState 경고 방지 가드네트워크 지연 시 언마운트된 후 setState가 호출될 수 있습니다. isMounted ref 가드를 추가하는 것을 추천합니다.
예시:
const mountedRef = React.useRef(true); React.useEffect(() => () => { mountedRef.current = false; }, []); try { setIsSubmitting(true); await changePassword({ password: newPassword }); if (!mountedRef.current) return; setSuccessMessage('비밀번호가 성공적으로 변경되었습니다.'); setNewPassword(''); setConfirmPassword(''); } finally { if (mountedRef.current) setIsSubmitting(false); }원하시면 위 가드를 포함한 패치까지 바로 만들어드릴게요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (6)
frontend/src/apis/auth/changePassword.ts(1 hunks)frontend/src/components/common/InputField/InputField.styles.ts(3 hunks)frontend/src/components/common/InputField/InputField.tsx(3 hunks)frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx(2 hunks)frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.styles.ts(1 hunks)frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
frontend/**/*.{ts,tsx}
📄 CodeRabbit inference engine (frontend/.cursorrules)
frontend/**/*.{ts,tsx}: Replace magic numbers with named constants for clarity.
Replace complex or nested ternary operators with if/else statements or IIFEs for readability.
Assign complex boolean conditions to named variables.
Use consistent return types for similar functions and hooks.
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle).
Use unique, descriptive names for custom wrappers and functions to avoid ambiguity.
Define constants near related logic or ensure names link them clearly.
Files:
frontend/src/apis/auth/changePassword.tsfrontend/src/components/common/InputField/InputField.styles.tsfrontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.styles.tsfrontend/src/components/common/InputField/InputField.tsxfrontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsxfrontend/src/pages/AdminPage/components/SideBar/SideBar.tsx
frontend/**/*.tsx
📄 CodeRabbit inference engine (frontend/.cursorrules)
frontend/**/*.tsx: Abstract complex logic/interactions into dedicated components or higher-order components (HOCs).
Separate significantly different conditional UI/logic into distinct components.
Colocate simple, localized logic or use inline definitions to reduce context switching.
Choose field-level or form-level cohesion based on form requirements.
Break down broad state management into smaller, focused hooks or contexts.
Use component composition instead of props drilling.
Files:
frontend/src/components/common/InputField/InputField.tsxfrontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsxfrontend/src/pages/AdminPage/components/SideBar/SideBar.tsx
🧠 Learnings (1)
📚 Learning: 2025-03-19T05:18:07.818Z
Learnt from: seongwon030
PR: Moadong/moadong#195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
Applied to files:
frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx
🧬 Code graph analysis (2)
frontend/src/apis/auth/changePassword.ts (1)
frontend/src/apis/auth/secureFetch.ts (1)
secureFetch(3-41)
frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx (1)
frontend/src/apis/auth/changePassword.ts (1)
changePassword(8-21)
🔇 Additional comments (3)
frontend/src/apis/auth/changePassword.ts (1)
8-15: 비밀번호 변경 API 계약 확정 필요
changePassword는 현재PUT ${API_BASE_URL}/auth/user/에{ password }만 전송 중 → 백엔드에서currentPassword,passwordConfirm등 추가 필드를 요구하거나,PATCH메서드·다른 경로(/auth/user/password등)를 사용하는지 확인API_BASE_URL뒤 슬래시 중복(//auth/user/) 여부 점검frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx (2)
17-28: 실시간 유효성/일치 검증 로직은 명확합니다입력 공백 시 null 처리로 시각적 피드백 지연하는 UX 결정 좋아요. 의도대로 동작할 것으로 보입니다.
46-51: 비밀번호 변경 후 세션 처리 확인 필요백엔드가 비밀번호 변경 시 기존 토큰 무효화/재로그인을 요구하는지 확인 바랍니다. 필요 시 성공 후 강제 재인증(로그아웃→로그인) 또는 토큰 재발급 흐름을 추가해야 합니다.
| if (!response.ok) { | ||
| const errorData = await response.json(); | ||
| throw new Error(errorData.message || '비밀번호 변경에 실패했습니다.'); | ||
| } | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
JSON 이외 응답에서 response.json() 예외 발생 가능 — 에러 처리 보강 필요
500/502 등에서 HTML/text를 돌려줄 때 파싱 예외로 원인 메시지를 잃을 수 있습니다. 컨텐츠 타입에 따라 안전하게 처리해 주세요.
- if (!response.ok) {
- const errorData = await response.json();
- throw new Error(errorData.message || '비밀번호 변경에 실패했습니다.');
- }
+ if (!response.ok) {
+ const contentType = response.headers.get('content-type') || '';
+ let message = '비밀번호 변경에 실패했습니다.';
+ try {
+ if (contentType.includes('application/json')) {
+ const errorData = await response.json();
+ message = errorData?.message || message;
+ } else {
+ const text = await response.text();
+ if (text) message = text;
+ }
+ } catch {
+ // 파싱 실패 시 기본 메시지 유지
+ }
+ throw new Error(message);
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (!response.ok) { | |
| const errorData = await response.json(); | |
| throw new Error(errorData.message || '비밀번호 변경에 실패했습니다.'); | |
| } | |
| }; | |
| if (!response.ok) { | |
| const contentType = response.headers.get('content-type') || ''; | |
| let message = '비밀번호 변경에 실패했습니다.'; | |
| try { | |
| if (contentType.includes('application/json')) { | |
| const errorData = await response.json(); | |
| message = errorData?.message || message; | |
| } else { | |
| const text = await response.text(); | |
| if (text) message = text; | |
| } | |
| } catch { | |
| // 파싱 실패 시 기본 메시지 유지 | |
| } | |
| throw new Error(message); | |
| } | |
| }; |
🤖 Prompt for AI Agents
In frontend/src/apis/auth/changePassword.ts around lines 17 to 21, the code
always calls response.json() on non-OK responses which throws when the server
returns non-JSON (HTML/text); change it to first inspect
response.headers.get('content-type') (or attempt json parse in a try/catch), and
if the content-type includes 'application/json' parse and use errorData.message,
otherwise call response.text() and use that text (or fall back to
response.statusText) when constructing the thrown Error; ensure parsing is
guarded so parsing failures don’t mask the original error.
| // 비밀번호 유효성 검사를 위한 정규식 | ||
| const PASSWORD_REGEX = /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^])(?!.*\s).{8,20}$/; | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
비밀번호 정책 정합성: 허용 문자 제한 및 매직 넘버 상수화 필요
현재 정규식은 .{8,20}로 임의의 문자(이모지/한글 등)도 허용됩니다. 정책이 “영문+숫자+특수문자(!@#$%^)”라면 허용 문자 클래스를 명시하고, 8/20 같은 매직 넘버는 상수로 선언해 가이드/알림/maxlength와 일관되게 재사용하는 편이 안전합니다.
-// 비밀번호 유효성 검사를 위한 정규식
-const PASSWORD_REGEX = /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^])(?!.*\s).{8,20}$/;
+// 비밀번호 정책: 영문/숫자/특수문자(!@#$%^) 각 1자 이상, 허용 문자만 사용, 길이 8~20
+const PASSWORD_MIN_LENGTH = 8;
+const PASSWORD_MAX_LENGTH = 20;
+const PASSWORD_REGEX =
+ /^(?=.*[A-Za-z])(?=.*\d)(?=.*[!@#$%^])[A-Za-z\d!@#$%^]{8,20}$/;
@@
- alert('비밀번호가 정책에 맞지 않습니다. (영문, 숫자, 특수문자 포함 8~20자)');
+ alert(`비밀번호가 정책에 맞지 않습니다. (영문, 숫자, 특수문자 포함 ${PASSWORD_MIN_LENGTH}~${PASSWORD_MAX_LENGTH}자)`);
@@
- 비밀번호는 영문, 숫자, 특수문자(!@#$%^)를 포함하여 8자 이상 20자 이하로 입력해야 합니다.
+ {`비밀번호는 영문, 숫자, 특수문자(!@#$%^)를 포함하여 ${PASSWORD_MIN_LENGTH}자 이상 ${PASSWORD_MAX_LENGTH}자 이하로 입력해야 합니다.`}
@@
- maxLength={20}
+ maxLength={PASSWORD_MAX_LENGTH}
@@
- helperText={isPasswordValid === false ? '영문, 숫자, 특수문자 포함 8~20자' : ''}
+ helperText={isPasswordValid === false ? `영문, 숫자, 특수문자 포함 ${PASSWORD_MIN_LENGTH}~${PASSWORD_MAX_LENGTH}자` : ''}
@@
- maxLength={20}
+ maxLength={PASSWORD_MAX_LENGTH}Also applies to: 38-38, 66-73, 82-82, 85-85, 95-95
🤖 Prompt for AI Agents
In frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx around
lines 9-11 (and also at lines 38, 66-73, 82, 85, 95), the password regex
currently uses .{8,20} which permits any character (including emoji/ non-Latin);
replace this with an explicit allowed-character class (e.g. A-Za-z0-9 and the
allowed special chars) and remove the dot wildcard, and extract the min/max
lengths into named constants (e.g. PASSWORD_MIN_LENGTH, PASSWORD_MAX_LENGTH) so
they’re reused for the regex, maxlength attributes, and validation messages;
update all other occurrences to reference these constants and ensure special
chars in the class are properly escaped and the regex logic still enforces at
least one letter, one digit, and one special character.
| alert('비밀번호가 정책에 맞지 않습니다. (영문, 숫자, 특수문자 포함 8~20자)'); | ||
| return; | ||
| } | ||
| if (isPasswordMatching !== true) { |
There was a problem hiding this comment.
return null까지 해당 조건문으로 걸러준것 좋습니다
frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/src/components/common/Button/Button.tsx (1)
3-10: 기본 type='button' 지정 + 표준 버튼 속성 지원(Rest props 전달) 제안폼 내 사용 시 의도치 않은 submit을 막기 위해 기본 type을 'button'으로 두고, aria-* / data-* 등 기본 속성을 모두 받을 수 있도록 React 표준 속성을 확장하는 편이 안전합니다. onClick 타입도 React.MouseEventHandler로 좁히면 좋습니다.
-export interface ButtonProps { - width?: string; - children: React.ReactNode; - type?: string; - onClick?: () => void; - animated?: boolean; - disabled?: boolean; -} +export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> { + width?: string; + animated?: boolean; +} -const Button = ({ - width, - children, - onClick, - type, - animated = false, - disabled = false -}: ButtonProps) => ( - <StyledButton width={width} onClick={onClick} animated={animated} type={type} disabled={disabled}> +const Button = ({ + width, + children, + animated = false, + disabled = false, + type = 'button', + ...rest +}: ButtonProps) => ( + <StyledButton width={width} animated={animated} type={type} disabled={disabled} {...rest}> {children} </StyledButton> );Also applies to: 51-60
♻️ Duplicate comments (2)
frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx (2)
99-106: 조건 미충족 시 버튼 비활성화로 UX/중복 경고 방지입력/정책/일치 조건이 충족될 때만 클릭 가능하도록 disabled를 확장하세요.
+const isSubmitDisabled = + isLoading || + !newPassword || + !confirmPassword || + isPasswordInvalid || + isPasswordMismatch; @@ - <Button + <Button width={'100%'} animated - onClick={handleChangePassword} - disabled={isLoading} + type="button" + onClick={handleChangePassword} + disabled={isSubmitDisabled} > - {isLoading ? '변경 중...' : '비밀번호 변경하기'} + {isLoading ? '변경 중...' : '비밀번호 변경하기'} </Button>Also applies to: 17-20
9-10: 비밀번호 정책 명확화 + 매직 넘버 상수화(가이드/maxlength/헬퍼텍스트 일관)
.와일드카드로 이모지/한글 등도 허용됩니다. 허용 문자 클래스를 명시하고 8/20은 상수로 재사용해 주세요.-const PASSWORD_REGEX = /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^])(?!.*\s).{8,20}$/; +const PASSWORD_MIN_LENGTH = 8; +const PASSWORD_MAX_LENGTH = 20; +const PASSWORD_REGEX = new RegExp( + `^(?=.*[A-Za-z])(?=.*\\d)(?=.*[!@#$%^])[A-Za-z\\d!@#$%^]{${PASSWORD_MIN_LENGTH},${PASSWORD_MAX_LENGTH}}$` +); @@ - <Styled.GuidanceText> - 비밀번호는 영문, 숫자, 특수문자(!@#$%^)를 포함하여 8자 이상 20자 이하로 입력해야 합니다. - </Styled.GuidanceText> + <Styled.GuidanceText> + {`비밀번호는 영문, 숫자, 특수문자(!@#$%^)를 포함하여 ${PASSWORD_MIN_LENGTH}자 이상 ${PASSWORD_MAX_LENGTH}자 이하로 입력해야 합니다.`} + </Styled.GuidanceText> @@ - maxLength={20} + maxLength={PASSWORD_MAX_LENGTH} @@ - helperText={isPasswordValid ? '영문, 숫자, 특수문자 포함 8~20자' : ''} + helperText={isPasswordInvalid ? `영문, 숫자, 특수문자 포함 ${PASSWORD_MIN_LENGTH}~${PASSWORD_MAX_LENGTH}자` : ''} @@ - maxLength={20} + maxLength={PASSWORD_MAX_LENGTH}Also applies to: 61-67, 70-80, 83-93, 76-76, 89-89
🧹 Nitpick comments (6)
frontend/src/components/common/Button/Button.tsx (2)
30-37: disabled 상태에서 hover/active 애니메이션 완전 차단현재 :disabled 스타일은 있으나 hover/active 애니메이션이 일부 브라우저에서 시각적으로 겹칠 수 있습니다. pointer-events도 함께 막아주면 UX가 안정적입니다.
&:hover { background-color: #333333; ${({ animated }) => - animated && + animated && css` animation: ${pulse} 0.4s ease-in-out; `} } &:active { transform: ${({ animated }) => (animated ? 'scale(0.95)' : 'none')}; } &:disabled { background-color: #cccccc; /* 비활성화된 느낌의 회색 */ color: #666666; cursor: not-allowed; /* 클릭할 수 없음을 나타내는 커서 */ opacity: 0.7; + pointer-events: none; } + + &:disabled:hover { + animation: none; + background-color: #cccccc; + } + + &:disabled:active { + transform: none; + }Also applies to: 39-41, 43-48
43-48: 디자인 토큰(Theme) 연결 권장색상(#cccccc, #666666)은 테마 변수로 치환하면 디자인 일관성과 다크모드 대응이 쉬워집니다.
frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx (4)
17-20: 불리언 네이밍 반전(가독성): Invalid/Mismatch로 명시isPasswordValid, isPasswordMatching은 실제로 “유효하지 않음/불일치”를 나타냅니다. 아래처럼 바꾸면 의도 파악이 빨라집니다.
-const isPasswordValid = newPassword.length > 0 && !PASSWORD_REGEX.test(newPassword); -const isPasswordMatching = confirmPassword.length > 0 && newPassword !== confirmPassword; +const isPasswordInvalid = !!newPassword && !PASSWORD_REGEX.test(newPassword); +const isPasswordMismatch = !!confirmPassword && newPassword !== confirmPassword; @@ - isError={isPasswordValid} - isSuccess={newPassword.length > 0 && !isPasswordValid} + isError={isPasswordInvalid} + isSuccess={!!newPassword && !isPasswordInvalid} @@ - isError={isPasswordMatching} - isSuccess={confirmPassword.length > 0 && !isPasswordMatching} + isError={isPasswordMismatch} + isSuccess={!!confirmPassword && !isPasswordMismatch}Also applies to: 77-79, 90-92
26-37: alert 남용 대신 인라인 에러 메시지/토스트 권장현재 모든 검증 실패를 alert로 막습니다. 필드 하단(helperText) 또는 버튼 아래 ErrorMessage로 안내하면 흐름이 덜 끊깁니다. 필요 시 Toast 컴포넌트를 사용해 비동기 에러만 토스트로 노출하세요.
원하시면 인라인 에러 상태 관리(예: formError state)와 표시 컴포넌트 패턴을 제안드리겠습니다.
70-76: 접근성 개선: autoComplete + 성공 메시지 라이브 리전InputField가 네이티브 prop을 전달한다면 자동완성 힌트와 라이브 리전을 추가하세요.
<InputField placeholder='새 비밀번호' type='password' value={newPassword} - onChange={(e) => setNewPassword(e.target.value)} + onChange={(e) => setNewPassword(e.target.value)} onClear={() => setNewPassword('')} + autoComplete="new-password" maxLength={PASSWORD_MAX_LENGTH} @@ <InputField placeholder='새 비밀번호 재입력' type='password' value={confirmPassword} - onChange={(e) => setConfirmPassword(e.target.value)} + onChange={(e) => setConfirmPassword(e.target.value)} onClear={() => setConfirmPassword('')} + autoComplete="new-password" maxLength={PASSWORD_MAX_LENGTH} @@ -{successMessage && <Styled.SuccessMessage>{successMessage}</Styled.SuccessMessage>} +{successMessage && ( + <Styled.SuccessMessage role="status" aria-live="polite"> + {successMessage} + </Styled.SuccessMessage> +)}InputField가 autoComplete를 전달하지 않는다면 컴포넌트 쪽에 해당 prop 전달 로직이 있는지 확인 부탁드립니다.
Also applies to: 83-89, 96-98
74-76: 성공 메시지 유지 조건 보완(편집 시 즉시 숨김)비밀번호를 다시 타이핑하면 success 메시지를 지우는 편이 자연스럽습니다.
-onChange={(e) => setNewPassword(e.target.value)} +onChange={(e) => { setNewPassword(e.target.value); if (successMessage) setSuccessMessage(''); }} @@ -onChange={(e) => setConfirmPassword(e.target.value)} +onChange={(e) => { setConfirmPassword(e.target.value); if (successMessage) setSuccessMessage(''); }}Also applies to: 86-88
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
frontend/src/components/common/Button/Button.tsx(3 hunks)frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
frontend/**/*.{ts,tsx}
📄 CodeRabbit inference engine (frontend/.cursorrules)
frontend/**/*.{ts,tsx}: Replace magic numbers with named constants for clarity.
Replace complex or nested ternary operators with if/else statements or IIFEs for readability.
Assign complex boolean conditions to named variables.
Use consistent return types for similar functions and hooks.
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle).
Use unique, descriptive names for custom wrappers and functions to avoid ambiguity.
Define constants near related logic or ensure names link them clearly.
Files:
frontend/src/components/common/Button/Button.tsxfrontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx
frontend/**/*.tsx
📄 CodeRabbit inference engine (frontend/.cursorrules)
frontend/**/*.tsx: Abstract complex logic/interactions into dedicated components or higher-order components (HOCs).
Separate significantly different conditional UI/logic into distinct components.
Colocate simple, localized logic or use inline definitions to reduce context switching.
Choose field-level or form-level cohesion based on form requirements.
Break down broad state management into smaller, focused hooks or contexts.
Use component composition instead of props drilling.
Files:
frontend/src/components/common/Button/Button.tsxfrontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx
🧬 Code graph analysis (1)
frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx (1)
frontend/src/apis/auth/changePassword.ts (1)
changePassword(8-21)
🔇 Additional comments (2)
frontend/src/components/common/Button/Button.tsx (1)
9-9: disabled 지원 추가는 좋습니다시각/상태적 피드백이 명확해졌고, 상위 탭의 비밀번호 변경 흐름과 잘 맞습니다.
Also applies to: 43-48, 57-60
frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx (1)
39-55: 로딩 상태 처리와 성공 후 초기화 플로우는 깔끔합니다중복 클릭 가드, finally에서 로딩 해제, 성공 시 입력값 리셋까지 흐름이 명확합니다. 버튼 disabled와 결합되면 UX가 더 좋아집니다.
Also applies to: 99-106
| disabled = false | ||
| }: ButtonProps) => ( | ||
| <StyledButton width={width} onClick={onClick} animated={animated} type={type}> | ||
| <StyledButton width={width} onClick={onClick} animated={animated} type={type} disabled={disabled}> |
| alert('아이디/비밀번호 수정 기능은 아직 준비 중이에요. ☺️'); | ||
| return; | ||
| } | ||
| // if (item.label === '아이디/비밀번호 수정') { |
#️⃣연관된 이슈
#725
📝작업 내용
사이드탭 "비밀번호 수정"으로 변경
비밀번호 수정 페이지
비밀번호 검증 및 재입력과 동일한지 검증
중점적으로 리뷰받고 싶은 부분(선택)
논의하고 싶은 부분(선택)
🫡 참고사항
Summary by CodeRabbit