Skip to content

[feature] 비밀번호 변경 페이지 추가#726

Merged
Zepelown merged 2 commits intodevelop-fefrom
feature/#725-password-fe-MOA-219
Sep 9, 2025
Merged

[feature] 비밀번호 변경 페이지 추가#726
Zepelown merged 2 commits intodevelop-fefrom
feature/#725-password-fe-MOA-219

Conversation

@Zepelown
Copy link
Member

@Zepelown Zepelown commented Sep 6, 2025

#️⃣연관된 이슈

#725

📝작업 내용

사이드탭 "비밀번호 수정"으로 변경

image

비밀번호 수정 페이지

image

비밀번호 검증 및 재입력과 동일한지 검증

image - 비밀번호가 형식에 안맞으면 빨간색으로 나옴 image - 재입력과 동일하지 않아도 빨간색으로 나옴

중점적으로 리뷰받고 싶은 부분(선택)

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

ex) 메서드 XXX의 이름을 더 잘 짓고 싶은데 혹시 좋은 명칭이 있을까요?

논의하고 싶은 부분(선택)

논의하고 싶은 부분이 있다면 작성해주세요.

🫡 참고사항

Summary by CodeRabbit

  • New Features
    • 관리자 계정 관리에 비밀번호 변경 기능 추가: 실시간 유효성 검사(정책·일치), 변경 성공 메시지, 실패 시 오류 안내 및 비동기 처리
    • 비밀번호 변경용 API 엔드포인트 호출 헬퍼 추가
  • UI
    • 사이드바 라벨을 ‘아이디/비밀번호 수정’에서 ‘비밀번호 수정’으로 변경
    • 계정 관리 탭을 비밀번호 변경 중심으로 재구성
    • 입력 필드에 성공 상태 표시 추가 및 포커스/테두리 색상 로직 개선
  • Style
    • 성공/오류 메시지 스타일과 안내 박스/텍스트 스타일 추가
  • New Features
    • 버튼에 disabled 상태 지원 추가 (비활성화 시 시각적 피드백 및 비인터랙티브)

@vercel
Copy link

vercel bot commented Sep 6, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
moadong Ready Ready Preview Comment Sep 6, 2025 9:35am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 6, 2025

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Validation error: Invalid regex pattern for base branch. Received: "**" at "reviews.auto_review.base_branches[0]"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

관리자 계정 비밀번호 변경 UI·로직 및 API 헬퍼 추가. InputField에 성공 상태(isSuccess) 스타일/프롭 도입, 사이드바 라벨/가드 수정, AccountEditTab에 실시간 유효성 검사·비밀번호 변경 흐름(요청/성공/오류) 추가.

Changes

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
Loading

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

Possibly related PRs

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

📥 Commits

Reviewing files that changed from the base of the PR and between 0ed2fa7 and 652fa4d.

📒 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.ts
  • frontend/src/components/common/InputField/InputField.styles.ts
  • frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.styles.ts
  • frontend/src/components/common/InputField/InputField.tsx
  • frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx
  • frontend/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.tsx
  • frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx
  • frontend/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: 비밀번호 변경 후 세션 처리 확인 필요

백엔드가 비밀번호 변경 시 기존 토큰 무효화/재로그인을 요구하는지 확인 바랍니다. 필요 시 성공 후 강제 재인증(로그아웃→로그인) 또는 토큰 재발급 흐름을 추가해야 합니다.

Comment on lines +17 to +21
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || '비밀번호 변경에 실패했습니다.');
}
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Suggested change
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.

Comment on lines 9 to 11
// 비밀번호 유효성 검사를 위한 정규식
const PASSWORD_REGEX = /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^])(?!.*\s).{8,20}$/;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Copy link
Member

@seongwon030 seongwon030 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다 ㅎㅎ

alert('비밀번호가 정책에 맞지 않습니다. (영문, 숫자, 특수문자 포함 8~20자)');
return;
}
if (isPasswordMatching !== true) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return null까지 해당 조건문으로 걸러준것 좋습니다

Copy link
Collaborator

@suhyun113 suhyun113 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셨습니다ㅏ~

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

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.

📥 Commits

Reviewing files that changed from the base of the PR and between 652fa4d and 7023463.

📒 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.tsx
  • frontend/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.tsx
  • frontend/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

Copy link
Contributor

@lepitaaar lepitaaar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셨습니다~~

disabled = false
}: ButtonProps) => (
<StyledButton width={width} onClick={onClick} animated={animated} type={type}>
<StyledButton width={width} onClick={onClick} animated={animated} type={type} disabled={disabled}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

disabled 추가좋습니다

alert('아이디/비밀번호 수정 기능은 아직 준비 중이에요. ☺️');
return;
}
// if (item.label === '아이디/비밀번호 수정') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

삭제 해주셔도될꺼같습니다~

@Zepelown Zepelown merged commit 96ef84c into develop-fe Sep 9, 2025
5 checks passed
@Zepelown Zepelown deleted the feature/#725-password-fe-MOA-219 branch October 12, 2025 12:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

Comments