Skip to content

feat: setting page 구현 - #684

Open
pwc0319 wants to merge 7 commits into
mainfrom
feat/#674-setting-page
Open

pwc0319 wants to merge 7 commits into
mainfrom
feat/#674-setting-page

Conversation

@pwc0319

@pwc0319 pwc0319 commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

변경 내용

setting 화면 UI 구현, menu 페이지 setting 바로가기 추가했습니다.

관련 이슈

Related to #674

검증 결과

  • 확인 방법:
  • 결과:

리뷰 참고 사항

체크리스트

  • 변경 내용을 직접 검토했습니다.
  • 변경 범위에 맞게 검증하고, 위에 결과를 기록했습니다.
  • 비밀 값이나 개인정보가 포함되지 않았는지 확인했습니다.
  • (해당하는 경우) 변경 사항에 맞춰 관련 문서를 최신화했습니다.

@pwc0319 pwc0319 self-assigned this Oct 1, 2026
@pwc0319 pwc0319 linked an issue Oct 1, 2026 that may be closed by this pull request
2 tasks

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

코드 리뷰 결과

🔴 주요 문제점

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);
}
  • 문제: 사용자에게 에러 상태가 전혀 표시되지 않음. 네트워크 실패 시 화면이 빈 채로 남음.
  • 권고: error state를 추가해 사용자에게 재시도/알림 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) vs settings-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- 접두사 병행 표기(호환성).

📋 우선순위 요약

  1. 필수 수정: 라우트 경로 소문자화, 에러/로딩 상태 처리, 파일 끝 개행, userName fallback.
  2. 권장 수정: 토큰 저장 방식 재검토(HttpOnly), 접근성 개선, 네이밍 통일.
  3. 개선 제안: 매직 넘버 변수화, 폰트 자체 호스팅, AbortController 적용.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

코드 리뷰 결과

🔴 심각한 문제 (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) vs settings-content(kebab-case) vs member-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 구현은 완성도가 있으나, 라우트 경로 버그와 인증/에러 처리 부재가 배포 전 반드시 해결되어야 합니다.

@pwc0319 pwc0319 added the 화영 label Oct 3, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

코드 리뷰 결과

🔴 심각한 문제 (버그/보안)

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-modal vs name-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 전역 이동

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

코드 리뷰 결과

🔴 보안 취약점

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)

전반적으로 기능은 동작 가능해 보이지만, 라우트 경로의 대소문자 불일치와 에러/로딩 상태 처리 부재, 인증 토큰 관리 방식은 반드시 수정이 필요합니다.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

코드 리뷰 결과

1. 라우팅 관련

🔴 문제: 라우트 경로 대소문자 불일치 (심각)

  • App.js: <Route path="/Settings" ...> — 대문자 S
  • Menu.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 처리.

전체적으로 기능은 의도대로 동작할 것으로 보이지만, 라우팅 명명 규칙, 인증 가드 누락, 프로덕션 로그 노출은 반드시 수정해야 할 사항입니다.

@psvm203 psvm203 left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

구현하느라 고생하셨습니다. 아래 내용 확인해주시고 수정해주세요!

  1. 페이지 주소는 /Settings 대신 /settings처럼 소문자로만 설정해주세요.

  2. 회원 정보 섹션에 /user/me 엔드포인트를 사용해서 사용자의 이름을 표시해주세요. /api/member 같은 별도의 엔드포인트를 추가하지 말고, 기능이 필요하다면 기존 API /user/me에서 회원 구분 정보도 포함하도록 백엔드에 요청해주세요.

  3. 메뉴 페이지로 돌아가기 버튼 클릭 시 존재하지 않는 페이지 /menu로 이동하는데, 유효한 페이지로 돌아가고 메뉴 컴포넌트를 여는 방식으로 수정해주세요.

  4. API fetch 시에 fetch("/api/member…") 으로 요청하면 프론트엔드 호스트로 요청합니다. apiClient를 사용해서 구현해주세요.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Client] feat: setting page UI 구현

2 participants