Repository navigation
Conversation
There was a problem hiding this comment.
코드 리뷰 결과
🔴 주요 문제점
1. 라우트 경로 네이밍 (App.js)
<Route path="/SettingForm" element={<SettingForm />} />- 문제: 라우트 경로가
/SettingForm으로 PascalCase 사용. React Router 관례상 소문자 케밥 케이스(/settings,/setting-form)를 사용해야 합니다. - 권고:
<Route path="/settings" element={<SettingForm />} />로 변경.
2. 컴포넌트명과 파일명 불일치
- 파일명:
SettingForm.js(단수 + Form) - 컴포넌트명:
Settings(복수) - 권고: 둘 중 하나로 통일. 실제로는 "폼"이 아닌 "설정 페이지" 역할이므로
SettingsPage.js/SettingsPage가 적절합니다.
3. CSS 파일 및 코드에 개행 없음
Settings.css와SettingForm.js모두\ No newline at end of file표시.- 권고: POSIX 표준 준수 및 Git diff 가독성을 위해 파일 끝 개행 추가.
4. Cookies 유틸 모듈의 안티패턴
import Cookies from "../utils/authStorage";
const token = Cookies.get("authToken");- 문제:
Cookies가 커스텀 모듈이지만 이름이js-cookie같은 표준 라이브러리와 혼동될 수 있음. - 권고:
authStorage로 import 이름을 통일하거나, 명확한 네이밍(AuthCookies) 사용.
5. 민감 정보 토큰의 쿠키 저장
Authorization: `Bearer ${token}`- 보안 우려:
authToken을 JS에서 읽을 수 있는 쿠키(비 HttpOnly)에 저장하고 있다면 XSS 취약점 발생 가능.HttpOnly+Secure+SameSite쿠키로 관리하고, 서버에서 세션 기반 인증을 권장.
6. API 호출 에러 처리 미흡
} catch (error) {
console.error("회원 정보 조회 실패:", error);
}- 문제: 사용자에게 에러 상태가 전혀 표시되지 않음. 네트워크 실패 시 화면이 빈 채로 남음.
- 권고:
errorstate를 추가해 사용자에게 재시도/알림 UI 제공.
const [error, setError] = useState(null);
// ...
catch (e) { setError("회원 정보를 불러오지 못했습니다."); }7. 빈 userName 렌더링 문제
<strong>{userName}</strong>- 문제:
kakaoUserName이 없거나 fetch 실패 시 빈 문자열이 그대로 노출. 레이아웃 붕괴 또는 의도치 않은 빈 UI. - 권고: 로딩 상태(
isLoading) 및 fallback 값 처리 (ex.{userName || "-"}또는 스켈레톤 UI).
8. 불필요한 <span> 마크업 (CSS)
<h3>회원 정보 <span>Member Information</span></h3>- CSS에도
.settings-section h3 span { font-weight: 700; }만 존재하여 스타일적 의미가 없음. - 권고: 영문 부제를 표시할 목적이라면 클래스로 분리해 스타일 지정, 그렇지 않으면 span 제거.
9. 외부 폰트 CDN 의존성
@font-face {
src: url("https://fastly.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Regular.woff")
}- 우려: 외부 CDN 장애 시 폰트 로드 실패. CSP 정책 위반 가능성.
@1.1같은 태그 참조는 재현성은 좋지만 버전 관리 정책 확인 필요. - 권고: 프로젝트 자체 호스팅 또는 공식 Pretendard CDN 사용 검토.
10. CSS white-space: nowrap + 반응형
.name-description {
font-size: 9px; /* 접근성 매우 낮음 */
white-space: nowrap;
}- 문제: 9px 폰트는 접근성 기준 미달.
nowrap으로 인해 좁은 화면에서 텍스트 잘림 가능. - 권고: 최소 11~12px로 조정,
nowrap제거 후 자연스러운 줄바꿈 허용.
11. 접근성(A11y) 개선 필요
- 닫기 버튼은
aria-label이 있어 좋으나,.settings-close가<button>임에도<span>두 개로 X 아이콘을 만든 것은 스크린 리더 관점에서 불필요.aria-hidden="true"를 span에 추가하는 것이 좋음. handleEditClick이console.log만 실행 — 사용자 입장에서 버튼이 동작하지 않는 것처럼 보임. 모달 미구현이라면disabled처리하거나aria-disabled명시 권장.
12. CSS 클래스 네이밍 혼재
settingsContainer(camelCase) vssettings-content(kebab-case) — 한 파일 내 네이밍 컨벤션이 혼재.- 권고: 프로젝트 컨벤션에 맞춰 통일 (일반적으로 kebab-case 권장).
13. 하드코딩된 매직 넘버 남용
padding-top: 105px,margin-bottom: 59px,top: 30px등 UI가 특정 뷰포트에 강하게 결합됨. Safe-area 대응이나 반응형 대응이 어려워짐.- 권고: CSS 변수(
--header-offset: 105px)화 또는clamp()사용.
14. navigate(-1)의 UX 위험
onClick={() => navigate(-1)}- 문제: 외부 링크로 직접 진입 시 히스토리가 없어 동작 안 함.
- 권고: 히스토리 유무 판단 또는 명시적
navigate("/menu")폴백 처리.
🟡 경미한 개선 제안
| 항목 | 제안 |
|---|---|
SettingForm import 순서 |
관리자 페이지 그룹과 분리해 일반 페이지 목록에 배치 |
React import |
React 17+ / 신규 JSX Transform이면 불필요할 수 있음 (import { useEffect, useState }) |
handleEditClick |
미구현 상태라면 TODO 주석에 이슈 링크 첨부 |
memberType 값 |
서버 응답의 enum 값을 그대로 렌더링 — 사용자 친화적 라벨 매핑 필요 |
| fetch abort | 컴포넌트 unmount 후 setState 방지를 위해 AbortController 사용 권장 |
✅ 잘된 점
- 컴포넌트를 함수형 + Hook으로 작성,
aria-label등 접근성 고려 시도. - CSS에
box-sizing: border-box,backdrop-filter, 스크롤바 숨김 등 세밀한 스타일링. -webkit-접두사 병행 표기(호환성).
📋 우선순위 요약
- 필수 수정: 라우트 경로 소문자화, 에러/로딩 상태 처리, 파일 끝 개행,
userNamefallback. - 권장 수정: 토큰 저장 방식 재검토(HttpOnly), 접근성 개선, 네이밍 통일.
- 개선 제안: 매직 넘버 변수화, 폰트 자체 호스팅,
AbortController적용.
There was a problem hiding this comment.
코드 리뷰 결과
🔴 심각한 문제 (Critical)
1. 라우트 경로 대소문자 불일치 (버그)
위치: App.js 97번째 줄 / Menu.js
// App.js
<Route path="/Settings" element={<Settings />} /> // 대문자 S
// Menu.js
navigate("/Settings"); // 대문자 S- 문제: 관례상 React Router 경로는 소문자 kebab-case를 사용합니다. 다른 경로들(
/team-build/result,/calendar,/protected)도 모두 소문자입니다. - 권고:
/settings로 통일하고,Menu.js의navigate("/settings")도 함께 수정하세요. URL 대소문자 혼동으로 인한 404/링크 오류 위험이 있습니다.
2. 인증 토큰 노출 및 에러 처리 미흡
위치: Settings.js fetchMemberInfo
const token = Cookies.get("authToken");
const response = await fetch("/api/member", { ... });
if (!response.ok) throw new Error(...);- 문제:
token이 없을 때도 그대로Bearer undefined로 요청을 보냅니다. 401 응답 시 로그인 페이지로 리다이렉트하는 로직이 없습니다. - 권고:
if (!token) {
navigate("/login");
return;
}
// 401 처리
if (response.status === 401) {
navigate("/login");
return;
}3. 접근 제어 미비 (보안)
- 문제:
/Settings라우트가AdminRoute/인증 보호 영역과 별도로 위치해 있습니다. 비로그인 사용자도 URL로 직접 접근 가능하며, 이 경우 사용자 정보가 빈 화면으로 노출됩니다. - 권고:
Menu.js에서isLoggedIn조건으로 버튼을 표시하는 것에 그치지 말고, 라우트 자체에도 인증 가드(예:ProtectedRoute)를 적용하세요.
🟠 주요 개선 필요 (Major)
4. console.log 남용 및 디버그 코드
위치: Settings.js handleEditClick, fetchMemberInfo catch
console.log("이름 변경 모달 열기");
console.error("회원 정보 조회 실패:", error);- 문제: 프로덕션 코드에 디버그 로그가 포함됩니다.
- 권고: 로깅 유틸리티 사용 또는 제거. 사용자에게 실패를 알리는 UI(토스트/에러 상태)를 추가하세요.
5. CSS 파일이 @font-face 중복 정의
위치: Settings.css 1~7줄
@font-face {
font-family: "Pretendard-Regular";
src: url("https://fastly.jsdelivr.net/...") format("woff");
}- 문제: 동일한 폰트 정의가
Menu.css등 다른 파일에도 이미 있을 가능성이 높습니다. 매 CSS 파일마다 외부 CDN을 재선언하면 중복 다운로드 및 유지보수 문제가 발생합니다. - 권고: 폰트 정의는
index.css또는 별도fonts.css에서 한 번만 선언하세요.
6. 매직 넘버/하드코딩된 레이아웃 값
- 문제:
padding-top: 105px,margin-bottom: 59px,letter-spacing: 0.5px등 의미 없는 수치가 난립합니다. - 권고: CSS 변수(
--space-*)로 관리하거나 최소한 주석으로 의도를 명시하세요.
7. --vh CSS 변수 의존성
위치: Settings.css
height: calc(var(--vh, 1vh) * 100);- 문제:
--vh는 모바일 브라우저 주소창 대응을 위한 커스텀 변수로,App또는 전역에서window.innerHeight기반으로 설정되어야 합니다. 이 코드가 어디서 설정되는지 확인이 필요합니다. 설정되지 않으면1vh로 폴백됩니다. - 권고: 대신
100dvh(dynamic viewport height)를 사용하거나, 변수 설정 위치를 명확히 하세요.
8. 인라인 useEffect 내 async 함수 정의
useEffect(() => {
const fetchMemberInfo = async () => { ... };
fetchMemberInfo();
}, []);- 권고: 언마운트 후 setState 경고를 방지하기 위해
AbortController또는isMounted플래그를 사용하세요. 또한userName,userType을 별도 상태로 관리하지 않고 하나의memberInfo객체로 묶으면 리렌더가 줄어듭니다.
🟡 스타일/경미한 문제 (Minor)
9. 파일 끝 개행 누락 (POSIX 위반)
- 위치:
Menu.css,Settings.css,Settings.js모두\ No newline at end of file - 권고:
.editorconfig에insert_final_newline = true설정 및 Prettier 적용.
10. CSS 클래스 네이밍 혼재
settingsContainer(camelCase) vssettings-content(kebab-case) vsmember-info-card(kebab-case)- 권고: 하나의 컨벤션(kebab-case 권장)으로 통일하세요.
11. 인라인 이벤트 핸들러 vs 명명 함수
위치: Menu.js
onClick={() => {
navigate("/Settings");
toggleMenu();
}}- 권고: 재사용성과 테스트 용이성을 위해
handleSettingsClick같은 명명 함수로 분리하세요.
12. 접근성 (a11y)
.settings-close의 X 표시가<span>두 개로만 구성 → 스크린리더가 인지하기 어려움.aria-label은 있으나role="button"은button태그라 불필요. 오히려 시각적 X자를 위한aria-hidden="true"를 span에 추가하는 편이 좋습니다..setting-value에id가 없어label과 연결되지 않음 →<label htmlFor>또는aria-labelledby사용 권장.
13. 중복된 정보 표시
- 회원 정보 카드와 회원 설정 카드에
userName이 두 번 표시됩니다. 의도된 UI라면 무방하나, 데이터 로딩 실패 시 빈 화면만 노출되므로 로딩/에러 상태 UI를 추가하세요.
14. 에러 문구가 하드코딩된 한국어
- 다국어 지원이 필요한 서비스라면 i18n 리소스로 분리하세요.
✅ 권장 조치 요약
| 우선순위 | 항목 | 조치 |
|---|---|---|
| 🔴 | 라우트 경로 대소문자 | /settings로 통일 |
| 🔴 | 인증 토큰 없음 처리 | 401/무토큰 시 로그인 리다이렉트 |
| 🔴 | 라우트 접근 제어 | 인증 가드 컴포넌트 적용 |
| 🟠 | 폰트 중복 선언 | 전역 CSS로 이동 |
| 🟠 | console.log |
제거 또는 로깅 유틸 사용 |
| 🟠 | async cleanup | AbortController 도입 |
| 🟡 | 파일 끝 개행 | .editorconfig 적용 |
| 🟡 | 클래스 네이밍 | kebab-case 통일 |
| 🟡 | 접근성 | label 연결, aria 보완 |
전반적으로: UI 구현은 완성도가 있으나, 라우트 경로 버그와 인증/에러 처리 부재가 배포 전 반드시 해결되어야 합니다.
There was a problem hiding this comment.
코드 리뷰 결과
🔴 심각한 문제 (버그/보안)
1. NameChange.js — handleConfirm에서 onClose을 호출하지 않음
onChangeName(newName)을 호출한 뒤, 부모의 handleNameChange가 성공 시에만 setIsNameModalOpen(false)를 실행합니다. API 실패 시 모달이 열린 채로 남아 사용자가 로딩 상태인지 알 수 없습니다. 또한 이름 변경 요청이 진행 중일 때 버튼이 비활성화되지 않아 중복 요청이 발생할 수 있습니다.
// 권장: 로딩 상태 및 에러 처리
const [isSubmitting, setIsSubmitting] = useState(false);
const handleConfirm = async () => {
setIsSubmitting(true);
try { await onChangeName(newName); }
finally { setIsSubmitting(false); }
};2. Settings.js — 토큰을 헤더에 그대로 노출(XSS 시 탈취 위험) 및 Cookies.get 사용
Cookies유틸이 실제로는 쿠키를 직접 다루는지, secure/httpOnly 여부가 불분명합니다.Cookies.set("userName", newName)로 사용자 이름을 쿠키에 저장하고, API 실패 시 폴백으로 다시 읽는 구조는 신뢰할 수 없는 데이터를 인증 정보처럼 신뢰하는 문제가 있습니다. 이름은 서버 값으로만 관리하는 것이 안전합니다.
3. 라우트 경로 대소문자 불일치 — /Settings vs /settings
- 라우트 정의는
path="/Settings"(대문자 S), 네비게이션도/Settings. 하지만 관례상 소문자여야 하며, 다른 라우트(/team-build/result,/calendar)와도 일관성이 없습니다. 대소문자 구분 없는 환경(예: 일부 브라우저/프록시)에서 링크가 깨질 수 있습니다.
4. 회원 정보 fetch 실패 시 무음 처리
토큰이 없거나 fetch가 실패하면 console.error만 찍고 화면에는 빈 문자열이 표시됩니다. 사용자에게 에러 상태를 보여주고 로그인 페이지로 리다이렉트해야 합니다.
5. Menu.js — isLoggedIn의 정의 위치 확인 필요
isLoggedIn이 이 스코프에 정의되어 있는지 diff에는 보이지 않습니다. 만약 상위에서 props로 받지 않는다면 ReferenceError 위험이 있습니다. (기존 코드 흐름 확인 필요)
🟡 중간 문제 (논리/품질)
6. 이름 유효성 검사 부실
- 빈 문자열만 막고, 길이 제한·금지 문자·XSS성 입력 검증이 없습니다.
- 서버에서도 반드시 재검증해야 합니다.
7. step === 2에서 뒤로 가기 수단 없음
X 버튼 외에 "이전" 버튼이 없어 사용자가 1단계로 돌아가 수정할 수 없습니다.
8. alert() 사용
접근성·일관성 측면에서 alert 대신 커스텀 토스트/인라인 에러를 권장합니다.
9. 응답 파싱 처리
fetchMemberInfo:response.json()실패 가능성 미처리.handleNameChange: 실패 시response.text()를 그대로 로그에 남기는데, 서버 에러 메시지에 내부 정보가 포함될 수 있습니다.
10. Cookies.get("authToken") 의 반환값 처리
token이 문자열 "undefined" 같은 값일 수 있습니다. 명시적으로 typeof token === "string" && token 검사 권장.
🟢 스타일/구조 문제
11. CSS 파일이 988줄 — 심각한 비대화
Settings.css에settings-*와name-modal-*/name-confirm-*클래스가 중복 정의되어 있습니다 (settings-modalvsname-modal, X 버튼, 인풋, 버튼 등 거의 동일한 규칙).- 두 벌의 모달 스타일이 존재 → 하나를 제거하거나 공통 클래스로 통합하세요.
12. 파일 끝 개행 누락
Menu.css, Settings.css, NameChange.js, Settings.js 모두 \ No newline at end of file 경고. 린터/포맷터 규칙(eol-last) 위반입니다.
13. CSS 관례 위반
- 최상위 컨테이너가
.settingsContainer(camelCase)와 나머지는 kebab-case 혼용. @font-face를 매 컴포넌트 CSS에 재정의 — 전역index.css로 옮겨 중복 로딩 방지.
14. 임시 흔적
console.log("회원 정보:", data),console.log("이름 변경 성공:", newName)는 프로덕션 로그로 부적절합니다.
15. 인라인 스타일/구조
- 로딩 상태, 에러 상태, 비활성 상태에 대한 UI 상태 표현이 전혀 없음.
✅ 체크리스트 (권장 조치)
-
handleConfirm에 로딩/성공/실패 흐름 캡슐화, 중복 제출 방지 - 이름을 쿠키에서 읽는 폴백 로직 제거, 서버 값만 사용
- 라우트를
/settings(소문자)로 통일 - fetch 실패 시 사용자 알림 + 로그인 리다이렉트
- 이름 입력 검증(길이/문자) 클라이언트·서버 양쪽
-
settings-modal-*와name-modal-*중복 CSS 정리 - 2단계에서 "이전" 버튼 추가
- 모든 파일 끝 개행 추가
-
console.log제거 -
@font-face전역 이동
There was a problem hiding this comment.
코드 리뷰 결과
🔴 보안 취약점
1. NameChange.js - alert() 사용으로 인한 UX 불일치
- 문제:
alert()는 브라우저 기본 UI로 앱 스타일과 어울리지 않으며, 프로덕션 환경에서는 커스텀 토스트/에러 UI를 사용하는 것이 일반적입니다. - 권고: 전역 에러 핸들링 유틸 또는 토스트 컴포넌트로 대체하세요.
2. Settings.js - console.log로 회원 정보 노출
- 문제:
console.log("회원 정보:", data);,console.log("이름 변경 성공:", newName);등이 남아 있어 프로덕션에서 사용자 정보가 콘솔에 노출됩니다. - 권고: 개발 로그는 제거하거나 환경 변수 기반 로깅 유틸로 대체하세요.
3. Cookies import 출처 불명
- 문제:
import Cookies from "../utils/authStorage";을 보면js-cookie같은 라이브러리를 래핑한 것으로 보이나,Cookies.set을if (Cookies.set)으로 조건 체크하는 것은 비정상적인 사용법입니다. 인증 토큰을 JS에서 접근 가능한 쿠키에 저장하는 것 자체가 XSS 공격 시 토큰 탈취 위험이 있습니다. - 권고: 인증 토큰은
HttpOnly쿠키로 서버에서 관리하고, 클라이언트는 쿠키에 직접 접근하지 않도록 하세요.
4. Settings.js - 에러 메시지 상세 노출
- 문제:
throw new Error(\회원 정보 조회 실패: ${response.status}`)` 등 서버 응답 상태를 그대로 로그에 노출합니다. - 권고: 사용자에게는 일반화된 메시지를, 로그에는 최소 정보만 남기세요.
🟠 논리적 오류 및 버그
5. NameChange.js - handleConfirm 동작 문제
const handleConfirm = () => {
onChangeName(newName);
};- 문제:
handleNameChange는 비동기(async)이며 실패 시 에러를 반환하지만,NameChange는 성공/실패 여부와 무관하게 진행 중 상태 표시가 없습니다. 또한 중복 클릭 시 API가 여러 번 호출될 수 있습니다. - 권고: 로딩 상태(
isSubmitting)를 추가하고, 버튼disabled처리 및handleConfirm을async/await로 변경하세요.
6. NameChange.js - step===2에서 뒤로가기 불가
- 문제: 2단계 확인 화면에서 이전 단계로 돌아갈 방법이 없습니다(닫기만 존재).
- 권고: "이전" 버튼을 추가하거나 2단계에서 입력값 수정이 가능하도록 하세요.
7. Settings.js - useEffect 내 fallback 로직의 순서 문제
if (data.name) {
setUserName(data.name);
} else {
const kakaoUserName = Cookies.get("userName");
...
}- 문제: 서버 응답 성공 시에도 이름이 없으면 쿠키에서 꺼내는데, 쿠키가 오래된 값일 가능성이 있어 서버와 불일치가 발생할 수 있습니다.
- 권고: 서버 응답에 항상 이름이 포함되도록 백엔드와 합의하거나, fallback을 제거하고 명시적인 에러 처리를 하세요.
8. Settings.js - 이름 변경 성공 후 쿠키 업데이트 로직
if (Cookies.set) {
Cookies.set("userName", newName);
}- 문제:
Cookies.set이 함수 존재 여부를 체크하는 것은 불필요하고 모호합니다. 만약Cookies가 제대로 된 라이브러리라면 항상 존재합니다. - 권고: 조건문 제거 또는 명확한 API 존재 검증을 하세요.
9. Settings.js - userType의 한글/영문 매핑 부재
- 문제:
data.memberType값을 그대로 표시하는데, 서버가"ADMIN","USER"같은 영문 값을 반환한다면 UI 표시가 어색합니다. - 권고: 표시용 매핑 테이블을 추가하세요. (예:
ADMIN → "관리자")
10. Settings.js - authToken 없을 때 조용히 리턴
if (!token) {
console.error("authToken이 없습니다.");
return;
}- 문제: 사용자에게 아무 피드백 없이 페이지가 빈 상태로 남습니다.
- 권고: 로그인 페이지로 리다이렉트하거나 안내 메시지를 표시하세요.
🟡 라우팅 및 접근성 문제
11. App.js - 라우트 경로 대소문자 불일치
<Route path="/Settings" element={<Settings />} />- 문제: 기존 경로(
/team-build,/calendar)는 소문자(kebab-case)인데 갑자기 대문자Settings를 사용합니다. URL은 대소문자를 구분하므로/settings로 접근하면 404가 발생합니다. - 권고:
/settings로 통일하세요. (Menu.js의navigate("/Settings")도 함께 수정)
12. App.js - 보호된 페이지 영역 배치
- 문제:
/Settings는 로그인한 사용자만 접근해야 하는 페이지인데 "보호된 페이지" 주석 위에 배치되어 있습니다. - 권고:
AdminRoute같은 보호 라우트로 감싸세요.
13. Menu.js - 접근성
- 문제: 설정 버튼은
isLoggedIn조건으로 표시되지만, 메뉴 자체가 접근 가능한 위치인지 확인 필요합니다. - 권고:
<span>설정 Settings</span>뒤의 trailing whitespace 제거, 버튼에aria-label추가 검토.
14. Settings.js - 닫기 버튼과 뒤로가기 혼재
- 문제: X 버튼은
navigate(-1)로 히스토리 뒤로가기를 하고, 하단 버튼은/menu로 이동합니다. 사용자가 특정 경로로 직접 진입한 경우navigate(-1)이 앱 밖으로 나갈 수 있습니다. - 권고: X 버튼도 명시적 경로(
/menu)로 이동하거나, history가 없을 때의 fallback을 지정하세요.
🔵 코드 스타일 및 유지보수
15. 여러 파일에 No newline at end of file
Menu.css,Settings.css,NameChange.js,Settings.js모두 파일 끝 개행이 없습니다.- 권고:
.editorconfig또는 Prettier 설정으로 자동 개행을 강제하세요.
16. Settings.css에 중복 @font-face 정의
- 문제:
Menu.css에도Pretendard-Regular폰트가 등록되어 있을 것으로 보이는데,Settings.css에서 다시@font-face를 선언합니다. - 권고: 폰트 정의는 전역 스타일 파일(예:
index.css)에 두고 재사용하세요.
17. Settings.css - 하드코딩된 색상 및 매직 넘버
- 문제:
#fff,#000,rgba(255,255,255,0.15),105px,59px등이 반복 하드코딩되어 있습니다. - 권고: CSS 변수로 토큰화하세요.
18. Settings.js - 하드코딩된 API 경로
- 문제:
fetch("/api/member"),fetch("/api/member/name")이 코드에 직접 작성됨. - 권고: API 클라이언트 모듈로 분리하세요.
19. NameChange.js - currentName 검증이 1단계에서만 수행
- 문제:
trimmedName === currentName비교는 1단계에만 있고, 2단계 확정 시에는 검증이 없습니다. - 권고: 서버 사이드 검증과 클라이언트 재검증을 병행하세요.
20. Settings.js - onChangeName에서 응답 데이터 미활용
if (!response.ok) { ... }
setUserName(newName);- 문제: 서버가 이름을 정규화(예: trim, 제한)해서 반환하더라도 클라이언트는 서버 응답을 반영하지 않습니다.
- 권고:
const updated = await response.json(); setUserName(updated.name);형태로 서버 상태를 신뢰하세요.
21. Menu.js - trailing whitespace
<span>설정 Settings</span> - 라인 끝 공백 제거 필요.
22. CSS 클래스 명명 혼재
.settingsContainer,.settings-content,.setting-item,.settings-section,.setting-section등 camelCase와 kebab-case가 혼용되어 있습니다.- 권고: BEM 또는 프로젝트 컨벤션으로 통일하세요.
✅ 요약 (우선순위)
| 우선순위 | 항목 |
|---|---|
| 높음 | 라우트 대소문자 불일치(#11), 인증 토큰 JS 접근(#3), 콘솔 로그 제거(#2), useEffect 조용한 실패(#10) |
| 중간 | 중복 클릭 방지(#5), 로딩 상태(#5), 폰트 중복(#16), API 경로 하드코딩(#18) |
| 낮음 | 스타일 컨벤션(#22), trailing whitespace(#15, #21), CSS 변수화(#17) |
전반적으로 기능은 동작 가능해 보이지만, 라우트 경로의 대소문자 불일치와 에러/로딩 상태 처리 부재, 인증 토큰 관리 방식은 반드시 수정이 필요합니다.
There was a problem hiding this comment.
코드 리뷰 결과
1. 라우팅 관련
🔴 문제: 라우트 경로 대소문자 불일치 (심각)
App.js:<Route path="/Settings" ...>— 대문자SMenu.js:navigate("/Settings")— 일치Settings.js:navigate("/menu")— 그런데 기존 라우팅이/menu인지 확인 필요
라우트 경로에 대문자를 사용하는 것은 React Router 관례에 어긋납니다. 다른 라우트(/team-build, /calendar, /protected)는 모두 소문자 kebab-case입니다. 일관성 없음.
권장: /settings로 통일하고 모든 참조를 수정하세요.
<Route path="/settings" element={<Settings />} />
// Menu.js
navigate("/settings");2. 인증/보안 문제
🔴 문제: console.log로 사용자 정보 노출
Settings.js:34—console.log("회원 정보:", data);Settings.js:105—console.log("이름 변경 성공:", newName);
프로덕션 환경에서 사용자 개인정보(이름, memberType)가 브라우저 콘솔에 노출됩니다.
권장: console.log 제거하거나 개발 환경에서만 실행되도록 조건 처리.
🟡 문제: 토큰 없을 때 리다이렉트 처리 없음
if (!token) {
console.error("authToken이 없습니다.");
return;
}토큰이 없으면 그냥 조용히 리턴하고 빈 화면이 표시됩니다. 사용자 경험이 나쁘고, 인증되지 않은 사용자가 설정 페이지 UI를 볼 수 있게 됩니다.
권장: navigate("/login") 등으로 리다이렉트.
🟡 문제: Settings 페이지가 AdminRoute 같은 보호 라우트로 감싸지지 않음
App.js에서 <Route path="/Settings" element={<Settings />} />는 보호 라우트가 아닙니다. 보호된 페이지 섹션 아래에 두거나 PrivateRoute로 감싸야 합니다.
3. NameChange.js 로직 문제
🔴 문제: onChangeName 실패 시 모달이 닫힘
Settings.js의 handleNameChange는 실패 시 alert만 띄우고 끝나지만, NameChange는 성공/실패 여부를 알 수 없습니다. 만약 상위에서 실패해도 모달이 열린 상태로 남아야 하는지, 아니면 닫혀야 하는지 명확하지 않습니다.
또한 성공 시에만 setIsNameModalOpen(false)가 호출됨 — 즉, 실패했을 때도 사용자는 모달을 여전히 볼 수 있는데, step 2 상태 그대로여서 다시 저장 시도 시 혼란이 생깁니다.
권장: 부모에서 onChangeName이 Promise<boolean> 또는 성공/실패를 리턴하도록 하고, NameChange에서 성공 시에만 step 리셋 또는 닫기.
const handleConfirm = async () => {
const ok = await onChangeName(newName);
// 부모에서 실패 시 모달 유지, 이름 재입력 단계로 돌리기 등
};🟡 문제: newName에 trim 이중 처리
handleNext에서 setNewName(trimmedName)으로 저장하지만, input의 value={newName}에는 이미 trim된 문자열이 들어가므로, 사용자가 중간에 공백을 입력하는 게 시각적으로 사라집니다(프리징 현상). 일반적으로는 input value는 raw로 두고, 제출 시점에만 trim하는 게 좋습니다.
권장: setNewName(e.target.value) 그대로 저장, 검증 시 newName.trim().
🟡 문제: Enter 키가 IME(한글) 조합 중에도 트리거됨
한글 입력 도중 엔터를 누르면 e.key === "Enter"와 함께 e.nativeEvent.isComposing === true가 될 수 있어, 조합 중 이름이 제출됩니다.
권장:
onKeyDown={(e) => {
if (e.key === "Enter" && !e.nativeEvent.isComposing) {
handleNext();
}
}}🟡 접근성: 모달에 role="dialog", aria-modal 없음
오버레이 클릭으로 닫히긴 하지만 focus trap이 없어 키보드 사용자가 배경으로 탭할 수 있습니다.
4. Settings.js 로직 문제
🟡 문제: 카카오 유저 이름 폴백 로직이 이상함
if (data.name) {
setUserName(data.name);
} else {
const kakaoUserName = Cookies.get("userName");
if (kakaoUserName) setUserName(kakaoUserName);
}서버 응답에 이름이 없을 때만 쿠키로 폴백하는데, 이러면 회원정보 조회 자체가 실패했을 때(catch문 진입)는 폴백조차 안 됩니다. 그리고 catch 블록에서도 쿠키 폴백이 필요할 수 있습니다.
권장: 서버 실패 시에도 쿠키의 userName 폴백을 확인하고, 없으면 로그아웃 처리.
🟡 문제: API 호출 중복 시 race condition
useEffect에서 fetch 후 unmount되면 setState 경고가 발생합니다.
권장:
useEffect(() => {
let mounted = true;
(async () => {
...
if (mounted) setUserName(data.name);
})();
return () => { mounted = false; };
}, []);🟡 문제: 쿠키 저장 API 불확실
if (Cookies.set) {
Cookies.set("userName", newName);
}Cookies.set 존재 여부만 체크하고 실패할 경우의 처리가 없습니다. 그리고 js-cookie라면 만료기간 미지정 시 세션 쿠키가 됩니다. 기존 authToken의 저장 정책과 맞춰야 합니다.
🟡 에러 처리: 실패 시 사용자에게 상세 정보 없음
alert("이름 변경에 실패했습니다.")만 표시. 400/401/409 등에 따라 다른 메시지가 필요할 수 있습니다.
5. CSS 문제
🟡 파일 끝 개행 없음(No newline at end of file)
Menu.css,Settings.css,NameChange.js,Settings.js모두 해당.- POSIX 표준 및 다수 린터/툴에서 경고 대상.
🟡 중복 @font-face
Settings.css에서 Pretendard-Regular를 다시 선언. 이미 다른 CSS에서 선언되어 있다면 중복 로드되어 폰트 리소스 낭비.
권장: 전역 CSS에서 한 번만 선언하거나 CSS 변수로 관리.
🟡 .settings-button + .logout-button 선택자 취약성
로그아웃 버튼 바로 앞에 설정 버튼이 있어야만 마진 적용. DOM 구조 변경 시 깨짐.
권장: .logout-button { margin-top: 4px; }로 명시하거나, 두 버튼을 감싸는 컨테이너에 gap 적용.
🟡 다크 배경 위 흰색 텍스트 대비
.setting-value 등 #fff on rgba(255,255,255,0.15) 배경은 카드 위에서 대비가 낮아 가독성이 떨어질 수 있음. WCAG AA 확인 권장.
6. 공통 코드 품질
🟡 매직 넘버/문자열
"authToken","userName"문자열이 여러 곳에 하드코딩됨. 상수로 관리 권장.- API 경로 하드코딩(
/api/member,/api/member/name).
🟡 API 에러 처리 패턴 불일치
Settings.js의 fetch:response.ok체크 → 좋음.- 다만
console.error+alert혼용,errorText를 읽지만 사용하진 않음.
🟢 개선 제안: API 유틸 분리
/api 호출이 여러 곳에 퍼지고 있어 axios/fetch wrapper를 만들어 토큰 주입, 에러 핸들링을 공통화하는 것을 고려하세요.
7. 종합 권장 사항 (우선순위)
| 우선순위 | 항목 |
|---|---|
| 🔴 높음 | /Settings → /settings 소문자 통일, <PrivateRoute>로 보호 |
| 🔴 높음 | console.log 제거 및 인증 실패 시 리다이렉트 |
| 🔴 높음 | NameChange 성공/실패 시 상태 처리 명확화 |
| 🟡 중간 | 토큰/이름 상수화, API 유틸 분리 |
| 🟡 중간 | 한글 IME 조합 중 Enter 처리 |
| 🟡 중간 | 서버 실패 시 쿠키 폴백 및 에러 메시지 다양화 |
| 🟢 낮음 | 파일 끝 개행, @font-face 중복 제거 |
| 🟢 낮음 | 모달 접근성(role, focus trap) |
8. 잘한 점
NameChange를 별도 컴포넌트로 분리해Settings와의 결합도를 낮춤.- 서버 응답 실패를
!response.ok로 명확히 처리. - 오버레이 클릭 + 자식
stopPropagation으로 모달 UX 처리.
전체적으로 기능은 의도대로 동작할 것으로 보이지만, 라우팅 명명 규칙, 인증 가드 누락, 프로덕션 로그 노출은 반드시 수정해야 할 사항입니다.
There was a problem hiding this comment.
구현하느라 고생하셨습니다. 아래 내용 확인해주시고 수정해주세요!
-
페이지 주소는
/Settings대신/settings처럼 소문자로만 설정해주세요. -
회원 정보 섹션에
/user/me엔드포인트를 사용해서 사용자의 이름을 표시해주세요./api/member같은 별도의 엔드포인트를 추가하지 말고, 기능이 필요하다면 기존 API/user/me에서 회원 구분 정보도 포함하도록 백엔드에 요청해주세요. -
메뉴 페이지로 돌아가기 버튼 클릭 시 존재하지 않는 페이지
/menu로 이동하는데, 유효한 페이지로 돌아가고 메뉴 컴포넌트를 여는 방식으로 수정해주세요. -
API fetch 시에
fetch("/api/member…")으로 요청하면 프론트엔드 호스트로 요청합니다.apiClient를 사용해서 구현해주세요.
변경 내용
setting 화면 UI 구현, menu 페이지 setting 바로가기 추가했습니다.
관련 이슈
Related to #674
검증 결과
리뷰 참고 사항
체크리스트