Skip to content

20260304 fe #255 about mypage#295

Merged
yyunee merged 7 commits intomainfrom
20260304-FE-#255-About-Mypage
Mar 10, 2026
Merged

20260304 fe #255 about mypage#295
yyunee merged 7 commits intomainfrom
20260304-FE-#255-About-Mypage

Conversation

@yyunee
Copy link
Contributor

@yyunee yyunee commented Mar 10, 2026

1) 작업한 이슈번호

#255 #281

2) 변경 요약 (What & Why)

  • 마이페이지 활동, 포인트 내역확인 api 연결, 페이지네이션 기능추가
  • 개인정보 수정 모달 추가 및 api 연결

+출석관리자 페이지 오류 수정

3) 스크린샷/동영상 (UI 변경 시)

image image

4) 상세 변경사항 (전부 다)

  1. 마이페이지 활동 / 포인트 내역 API 연결
  • 활동 내역 조회 API(/api/user/logs/attendance) 연결
  • 포인트 내역 조회 API 연결
  • API 응답 데이터를 UI에 맞게 가공하여 리스트 형태로 출력
  • 날짜 데이터를 사용자 친화적인 형식으로 변환하여 표시
  1. 페이지네이션 기능 추가
  • 서버 pageable 응답(totalPages)을 기반으로 페이지 번호 버튼 생성
  • 페이지 번호 클릭 시 해당 페이지 데이터 재요청
  • 현재 페이지는 연한 회색 배경으로 표시
  • 페이지당 조회 데이터 개수(size) 설정하여 불필요한 데이터 요청 최소화
  1. 개인정보 수정 모달 추가 및 API 연결
  • 마이페이지에서 개인정보 수정 모달 UI 추가
  • 이메일 변경 및 비밀번호 변경 기능 구현
  • /api/user/details API 연결하여 사용자 정보 수정 가능하도록 구현
  • 입력값 검증 및 비밀번호 정책 체크 후 변경 버튼 활성화 처리

5) 참고사항

활동 및 포인트 내역 조회시 로딩하는데 시간이 걸림

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 프로필 편집 모달을 통한 이메일 및 비밀번호 변경 기능 추가
    • 이메일 인증 흐름 구현
    • 비밀번호 정책 검증 (8-20자, 대문자, 소문자, 숫자, 특수문자 포함)
    • 활동 내역 및 포인트 내역에 페이지네이션 추가
  • 개선

    • 계정 보안 설정 인터페이스 단순화
    • 프로필 페이지 반응형 디자인 개선
    • 출석 관리 메뉴 제거

@yyunee yyunee requested a review from DongEun02 as a code owner March 10, 2026 05:29
@yyunee yyunee added the FE 프론트엔드 이슈 label Mar 10, 2026
@yyunee yyunee requested a review from gxuoo as a code owner March 10, 2026 05:29
@coderabbitai
Copy link

coderabbitai bot commented Mar 10, 2026

Walkthrough

PR는 마이페이지의 프로필 관리 시스템을 재구성합니다. 새로운 모달 기반 계정 보안/프로필 편집 UI를 추가하고, 활동/포인트 섹션을 목 데이터에서 실제 API 기반 페이징으로 전환하며, 세션 관리 드롭다운 바인딩을 수정합니다.

Changes

Cohort / File(s) Summary
새 프로필 편집 모달 컴포넌트
EditProfileModal.jsx, EditProfileModal.module.css, ChangeInfoForm.jsx, EmailVerify.jsx
다단계 프로필 편집 모달(이메일/비밀번호 변경), 이메일 인증 컴포넌트, 비밀번호 정책 검증 폼, 그리고 전체 모달 UI 스타일링 추가
프로필 카드 및 계정 보안
ProfileCard.jsx, ProfileCard.module.css, AccountSecurity.jsx
프로필 카드에 동적 역할 표시 추가, 계정 보안을 EditProfileModal을 여는 버튼으로 대체, 레이아웃을 그리드에서 플렉스로 변경 및 반응형 조정
활동/포인트 섹션 데이터 페칭
ActivitySection.jsx, PointsSection.jsx, ActivityModal.jsx, ActivityModal.module.css
목 데이터 props에서 API 기반 페이징 로직으로 전환, 클라이언트 측 페이네이션 UI 추가, ActivityModal의 data prop 제거
마이페이지 메뉴 및 유틸리티
MyPageMenu.jsx, MyPageMenu.module.css, myPageMenuMock.js, myPageMenu.js
메뉴에서 출석 항목 제거, 새 API 헬퍼 함수(getUserPoints, getActivityLogs) 추가, 목 데이터 생성기 제거
API 통합
userApi.js
updateUserDetails 함수 추가 (이메일/비밀번호 업데이트용)
기타
SessionManagementCard.jsx, Mypage.jsx, Mypage.module.css
handleAddRound 함수 제거, 세션 드롭다운 바인딩 수정(undefined → 빈 문자열), AccountSecurity 컴포넌트 import/사용 제거, 컨테이너 패딩 감소

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant EditProfileModal
    participant EmailVerify
    participant API
    participant Toast

    User->>EditProfileModal: 모달 열기
    EditProfileModal->>EditProfileModal: 메뉴 표시
    User->>EditProfileModal: "이메일 변경하기" 선택
    EditProfileModal->>EmailVerify: 이메일 인증 스텝으로 전환
    User->>EmailVerify: 이메일 입력
    User->>EmailVerify: "인증번호 전송" 버튼 클릭
    EmailVerify->>API: POST /api/user/email/verification 호출
    API-->>EmailVerify: 인증번호 전송 완료
    EmailVerify->>Toast: 성공 메시지 표시
    EmailVerify->>EmailVerify: 카운트다운 타이머 시작 (180초)
    User->>EmailVerify: 인증번호 입력
    User->>EmailVerify: "인증하기" 버튼 클릭
    EmailVerify->>API: POST /api/user/email/verify 호출
    API-->>EmailVerify: 인증 성공
    EmailVerify->>EditProfileModal: onVerified(email) 콜백
    EditProfileModal->>EditProfileModal: 최종 제출 스텝으로 이동
    User->>EditProfileModal: "확인" 버튼 클릭
    EditProfileModal->>API: PATCH /api/user/details { email }
    API-->>EditProfileModal: 업데이트 완료
    EditProfileModal->>Toast: 성공 메시지 표시
    EditProfileModal->>User: 모달 닫기
Loading
sequenceDiagram
    participant User
    participant EditProfileModal
    participant ChangeInfoForm
    participant API
    participant Toast

    User->>EditProfileModal: 모달 열기
    EditProfileModal->>EditProfileModal: 메뉴 표시
    User->>EditProfileModal: "비밀번호 변경하기" 선택
    EditProfileModal->>EditProfileModal: 인증 스텝으로 전환
    EditProfileModal->>ChangeInfoForm: 비밀번호 입력 폼 (현재 비밀번호 필드만)
    User->>ChangeInfoForm: 현재 비밀번호 입력
    EditProfileModal->>EditProfileModal: 형식 스텝으로 이동
    EditProfileModal->>ChangeInfoForm: 새 비밀번호/확인 폼으로 전환
    ChangeInfoForm->>ChangeInfoForm: 비밀번호 정책 검증 (길이, 대문자, 소문자, 숫자, 특수문자)
    User->>ChangeInfoForm: 새 비밀번호 입력
    ChangeInfoForm->>ChangeInfoForm: 유효성 확인
    ChangeInfoForm->>EditProfileModal: onValidChange({ currentPassword, newPassword })
    User->>EditProfileModal: "확인" 버튼 클릭
    EditProfileModal->>API: PATCH /api/user/details { currentPassword, newPassword }
    API-->>EditProfileModal: 업데이트 완료
    EditProfileModal->>Toast: 성공 메시지 표시
    EditProfileModal->>User: 모달 닫기
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • DongEun02
  • gxuoo

Poem

🐰 프로필 편집의 새로운 길,
이메일과 비밀번호 검증으로 빛나네요!
API 데이터 페칭에 페이지 나누기,
목 데이터는 안녕하고, 실제 흐름이 흐르네요! ✨
계정 보안 모달이 우아하게 춤을 춘답니다 💫

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning 제목이 PR의 주요 변경사항을 명확하게 설명하지 못하고 있습니다. '20260304 fe #255 about mypage'는 날짜, 레이블, 이슈 번호만 포함한 매우 추상적인 표현으로, 실제 구현 내용(API 연동, 모달 추가, 페이지네이션 등)을 구체적으로 전달하지 않습니다. 구체적인 변경사항을 명시하는 제목으로 변경하세요. 예: 'Mypage: Add email/password change modals with API integration and pagination' 또는 'Mypage: Implement user profile edit flows and activity/points pagination'
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 20260304-FE-#255-About-Mypage

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

@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: 12

🧹 Nitpick comments (1)
frontend/src/components/attendancemanage/SessionManagementCard.jsx (1)

4-7: 선택 사항: 사용되지 않는 import 정리

코드 정리를 위해 useContext(Line 4)와 addRound(Line 7)가 이 파일에서 더 이상 사용되지 않으므로 제거를 고려해 보세요. 또한 handleAddRounds(Line 23)도 이 컴포넌트에서는 사용되지 않고 RoundDayPicker에서만 사용됩니다.

♻️ 제안된 수정
-import { useContext, useEffect, useState } from 'react';
+import { useEffect, useState } from 'react';
 import { toast } from 'react-toastify';
 import { useAttendance } from '../../contexts/AttendanceContext';
-import { getRounds, addRound } from '../../utils/attendanceManage';
+import { getRounds } from '../../utils/attendanceManage';

그리고 useAttendance에서 handleAddRounds 제거:

 const {
   sessions,
   roundsVersion,
-  handleAddRounds,
   openAddRoundsModal,
   selectedSessionId,
   setSelectedSessionId,
 } = useAttendance();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/attendancemanage/SessionManagementCard.jsx` around
lines 4 - 7, Remove unused imports and props: delete the unused useContext
import and the addRound import from SessionManagementCard.jsx and remove the
unused handleAddRounds prop usage from this component (handleAddRounds is only
used inside RoundDayPicker). Update any destructuring from useAttendance to stop
expecting handleAddRounds so the context hook usage matches actual consumers
(e.g., adjust the useAttendance() destructure in SessionManagementCard to
exclude handleAddRounds). Ensure RoundDayPicker continues to receive and use
handleAddRounds from useAttendance where needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/mypage/ActivitySection.jsx`:
- Around line 11-27: fetchActivityLogs currently leaves the UI ambiguous during
network delays because it only updates items and totalPages; add explicit
loading and error state handling: introduce local state variables (e.g.,
loading, setLoading and error, setError) in ActivitySection.jsx,
setLoading(true) before awaiting getActivityLogs(page, size), clear error on
start, set items and totalPages on success, setError(error) in the catch block,
and setLoading(false) in a finally block; also update the component render
(including the JSX referenced around the fetch usage lines ~51-65) to show a
loading indicator when loading is true, an error message when error is set, and
only show the "no items" / list UI when not loading and no error.

In `@frontend/src/components/mypage/ChangeInfoForm.jsx`:
- Around line 22-29: The effect in ChangeInfoForm (useEffect) currently only
calls onValidChange when isValid is true, so the parent never sees validation
become false; change the effect to call onValidChange on every validation change
(dependencies: currentPassword, newPassword, confirmPassword) and pass the
validation state and data — e.g., call onValidChange(isValid ? {
currentPassword, newPassword } : null) or pass an object like { valid: isValid,
data: isValid ? { currentPassword, newPassword } : null } so the parent
(EditProfileModal) can clear formValid and passwordData when invalid.

In `@frontend/src/components/mypage/EditProfileModal.jsx`:
- Around line 90-118: Add an isSubmitting state flag and use it to prevent
duplicate requests: at the top of handleSubmit check if isSubmitting and return
early, set isSubmitting = true before any await/updateUserDetails call, and
ensure you set isSubmitting = false in a finally block so it always resets; also
disable the primary button (e.g., disabled={isSubmitting} or show a loading
state) so the UI cannot trigger parallel submits during the mode/step flows
(references: handleSubmit, updateUserDetails, mode, step); apply the same
isSubmitting guard and UI disable to the other submit handler(s) in this
component that perform PATCH requests (the second submit path handling
email/password flows).
- Around line 124-129: The modal lacks ARIA dialog semantics; update the modal
container (the element with className styles.modal) to include role="dialog" and
aria-modal="true", add an id to the header element that renders getHeaderTitle()
(e.g., "edit-profile-modal-title") and connect it via aria-labelledby on the
dialog, and ensure the header <h1> uses that id so screen readers recognize the
title; make these changes inside the EditProfileModal component (look for
styles.overlay, styles.modal, and the getHeaderTitle() usage).

In `@frontend/src/components/mypage/EditProfileModal.module.css`:
- Around line 1-27: The overlay and modal lack vertical scroll control causing
the modal bottom to be inaccessible on small viewports; update the .overlay to
allow scrolling (e.g., overflow-y: auto; -webkit-overflow-scrolling: touch) and
set .modal to a max-height based on the viewport (e.g., max-height: calc(100vh -
40px)) with overflow-y: auto so its internal content can scroll while keeping
the modal centered and paddings intact; apply these changes to the .overlay and
.modal rules to ensure the bottom buttons remain reachable on
mobile/virtual-keyboard scenarios.

In `@frontend/src/components/mypage/EmailVerify.jsx`:
- Around line 19-20: The AbortController created in the EmailVerify component
isn't wired to the actual request: set abortRef.current to the new controller
(and call abortRef.current.abort() if an existing controller exists) before
making the network call, and pass controller.signal into the fetch/axios request
so the previous request can be cancelled; update the code around the controller
creation (where controller is instantiated) and the request invocation (where
signal should be supplied) to use abortRef and controller.signal accordingly.
- Around line 46-58: The current "currentEmail" verification allows any email
because handleSend calls sendVerificationNumber({ email }) with only the
user-supplied value; change the flow so the checked email is the authenticated
user's email (not an arbitrary input) or enforce server-side matching: update
the client-side handlers (handleSend and the corresponding verify handler around
lines where sendVerificationNumber and verifyVerificationNumber are called) to
fetch/use the logged-in user's email from the auth state (or disable editing of
the currentEmail input) before calling sendVerificationNumber, and/or update the
API endpoints invoked by sendVerificationNumber/verifyVerificationNumber to
validate that the target email equals the authenticated user's email
(session/token) so an attacker cannot verify a different address; reference
handleSend, sendVerificationNumber, verifyVerificationNumber, and the
EditProfileModal currentEmail step when making the change.

In `@frontend/src/components/mypage/PointsSection.jsx`:
- Line 1: The page fetch logic in PointsSection is vulnerable to out-of-order
responses when users click page buttons rapidly; update the component
(PointsSection) so the effect/handler that calls the data loader (e.g.,
fetchItems or fetchPoints) cancels stale requests or ignores late responses:
either use an AbortController and pass its signal to the fetch call inside
useEffect and abort the previous controller on cleanup, or implement a monotonic
requestId/sequence token checked before calling setItems/setLoading; ensure this
change touches the useEffect that depends on currentPage (and any page change
handler) so only the latest response updates state (items, loading, error) and
previous responses are ignored/aborted.

In `@frontend/src/components/mypage/ProfileCard.jsx`:
- Around line 9-15: The roleMap constant is missing an entry for PENDING_MEMBER
so pending users fall back to the generic label; update the roleMap object
(symbol: roleMap in ProfileCard.jsx) to include a PENDING_MEMBER key with the
appropriate Korean label (e.g., '승인대기' or similar) and ensure any other nearby
mappings (lines around the existing ROLE keys) are consistent with backend
Role.java so pending members display correctly.
- Around line 26-27: The debug console log in ProfileCard.jsx prints the full
/api/user/details response (api.get('/api/user/details')) which exposes PII
(email, phoneNumber); remove the console.log('사용자 정보:', response.data) from the
release code or replace it with a safe log that only outputs non-PII fields
(e.g., username/id) by destructuring response.data to exclude email and
phoneNumber before logging or simply delete the log line in the function that
handles the API response.

In `@frontend/src/utils/axios.js`:
- Around line 2-5: The axios instance is created with baseURL set to an empty
string while BASE_URL is computed, causing API requests and the token refresh
flow (which already uses BASE_URL) to target different hosts in production;
update the axios.create call (the exported api instance) to use baseURL:
BASE_URL so normal API calls and the token-reissue/refresh requests use the same
origin/host, and keep withCredentials: true to preserve credentials across both
flows.

In `@frontend/src/utils/myPageMenu.js`:
- Around line 19-28: getActivityLogs currently returns a raw array but
ActivitySection.jsx expects an object with content and totalPages; update
getActivityLogs to normalize the API response: if response.data is an object
with response.data.content return that as-is, otherwise wrap the array into {
content: response.data, totalPages: 1 } (or compute totalPages from a response
header if backend provides total count) so callers (ActivitySection.jsx) always
receive { content, totalPages } from getActivityLogs.

---

Nitpick comments:
In `@frontend/src/components/attendancemanage/SessionManagementCard.jsx`:
- Around line 4-7: Remove unused imports and props: delete the unused useContext
import and the addRound import from SessionManagementCard.jsx and remove the
unused handleAddRounds prop usage from this component (handleAddRounds is only
used inside RoundDayPicker). Update any destructuring from useAttendance to stop
expecting handleAddRounds so the context hook usage matches actual consumers
(e.g., adjust the useAttendance() destructure in SessionManagementCard to
exclude handleAddRounds). Ensure RoundDayPicker continues to receive and use
handleAddRounds from useAttendance where needed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 037bda20-b1ec-4ced-95de-7e53e2d0a247

📥 Commits

Reviewing files that changed from the base of the PR and between 5fc1c0d and 8e4747d.

⛔ Files ignored due to path filters (2)
  • frontend/src/assets/right-arrow.svg is excluded by !**/*.svg
  • frontend/src/assets/setting_icon.svg is excluded by !**/*.svg
📒 Files selected for processing (19)
  • frontend/src/components/attendancemanage/SessionManagementCard.jsx
  • frontend/src/components/mypage/AccountSecurity.jsx
  • frontend/src/components/mypage/ActivityModal.module.css
  • frontend/src/components/mypage/ActivitySection.jsx
  • frontend/src/components/mypage/ChangeInfoForm.jsx
  • frontend/src/components/mypage/EditProfileModal.jsx
  • frontend/src/components/mypage/EditProfileModal.module.css
  • frontend/src/components/mypage/EmailVerify.jsx
  • frontend/src/components/mypage/MyPageMenu.jsx
  • frontend/src/components/mypage/MyPageMenu.module.css
  • frontend/src/components/mypage/PointsSection.jsx
  • frontend/src/components/mypage/ProfileCard.jsx
  • frontend/src/components/mypage/ProfileCard.module.css
  • frontend/src/pages/Mypage.jsx
  • frontend/src/pages/Mypage.module.css
  • frontend/src/utils/axios.js
  • frontend/src/utils/myPageMenu.js
  • frontend/src/utils/myPageMenuMock.js
  • frontend/src/utils/userApi.js
💤 Files with no reviewable changes (2)
  • frontend/src/components/mypage/MyPageMenu.jsx
  • frontend/src/pages/Mypage.jsx

Copy link

@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

♻️ Duplicate comments (2)
frontend/src/components/mypage/EditProfileModal.jsx (1)

130-138: ⚠️ Potential issue | 🟠 Major

aria-labelledby가 끊겨 있어서 모달 제목이 전달되지 않습니다.

Line 134는 edit-profile-modal-title를 참조하지만, Line 138의 <h1>에 해당 id가 없습니다. 현재 상태에선 스크린리더가 이 dialog의 accessible name을 읽지 못합니다.

수정 예시
       <div
         className={styles.modal}
         role="dialog"
         aria-modal="true"
         aria-labelledby="edit-profile-modal-title"
       >
         <div className={styles.modalHeaderColumn}>
           <div className={styles.modalHeader}>
-            <h1>{getHeaderTitle()}</h1>
+            <h1 id="edit-profile-modal-title">{getHeaderTitle()}</h1>
           </div>
         </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/mypage/EditProfileModal.jsx` around lines 130 - 138,
The modal's aria-labelledby points to "edit-profile-modal-title" but the <h1>
rendered by getHeaderTitle() has no matching id, so add
id="edit-profile-modal-title" to the heading element (the <h1> that displays
getHeaderTitle()) in EditProfileModal.jsx so the dialog's accessible name is
announced correctly by screen readers; alternatively, update the dialog's
aria-labelledby to match an existing element id if you prefer a different
target.
frontend/src/components/mypage/ActivitySection.jsx (1)

11-27: ⚠️ Potential issue | 🟡 Minor

로딩/에러 상태를 UI에서 구분해 주세요.

PR 설명대로 이 화면은 응답 지연이 체감될 수 있는데, 현재는 fetch 중에도 실패 시에도 리스트 영역만 그대로 렌더링됩니다. 그래서 빈 내역인지, 아직 로딩 중인지, 요청이 실패한 건지 구분이 안 됩니다. loading/error 상태를 두고 리스트·빈 상태·에러 표시를 분기하는 편이 안전합니다.

Also applies to: 51-65

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/mypage/ActivitySection.jsx` around lines 11 - 27, Add
explicit loading and error state handling around the fetchActivityLogs flow:
introduce React state variables (e.g., loading and error via useState), set
loading = true before calling getActivityLogs(page, size) and set loading =
false in finally; on success clear error and call setItems and setTotalPages as
now; on catch set error to the caught error (and optionally clear items). Update
the ActivitySection render logic to branch on loading (show spinner), error
(show error message/retry), non-empty items (render list), and empty items
(render empty state). Apply the same pattern to the other similar fetch block
referenced (lines with the second fetch call).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/mypage/ActivitySection.jsx`:
- Around line 11-31: The fetchActivityLogs calls can race and allow earlier
(slower) responses to overwrite state; update fetchActivityLogs/useEffect to
ensure only the latest request updates state by adding a cancellation or
sequence guard: either create and use an AbortController for each call and abort
the previous controller before starting a new fetch (wiring the
controller.signal into getActivityLogs), or maintain a monotonic requestId
(useRef) that you increment before each call and check when responses arrive,
only calling setItems/setTotalPages if the response's requestId matches the
latest; update getActivityLogs invocation and the useEffect that triggers
fetchActivityLogs(page) to use this mechanism so stale responses can’t update
the component state.

In `@frontend/src/components/mypage/EditProfileModal.jsx`:
- Around line 37-42: The decorative right-arrow images inside the
EditProfileModal.jsx menu buttons are currently using alt=">" which creates
unnecessary screen-reader noise; update both <img> instances (the ones inside
the buttons that call handleMenuSelect and use styles.menuItem) to be treated as
purely decorative by setting alt="" and adding aria-hidden="true" on the <img>
elements so screen readers ignore them while preserving visual UI.
- Around line 56-61: The verify step currently accepts any email from
EmailVerify via onVerified and only sets verifiedEmail without confirming it
belongs to the logged-in account; update the flow so it either (A) performs
ownership validation by comparing the returned email to the current user's email
(e.g., compare verifiedEmail to currentUser.email from props/context inside this
component before enabling the "continue" button or advancing the step) and
reject/mask advancement if they differ, or (B) remove the email verification
step entirely from the password-change flow; additionally consider changing
EmailVerify/onVerified semantics to return an explicit verified flag (not an
arbitrary email) so this component can enforce ownership using verified === true
&& returnedEmail === currentUser.email before calling setVerifiedEmail or
progressing the step.

---

Duplicate comments:
In `@frontend/src/components/mypage/ActivitySection.jsx`:
- Around line 11-27: Add explicit loading and error state handling around the
fetchActivityLogs flow: introduce React state variables (e.g., loading and error
via useState), set loading = true before calling getActivityLogs(page, size) and
set loading = false in finally; on success clear error and call setItems and
setTotalPages as now; on catch set error to the caught error (and optionally
clear items). Update the ActivitySection render logic to branch on loading (show
spinner), error (show error message/retry), non-empty items (render list), and
empty items (render empty state). Apply the same pattern to the other similar
fetch block referenced (lines with the second fetch call).

In `@frontend/src/components/mypage/EditProfileModal.jsx`:
- Around line 130-138: The modal's aria-labelledby points to
"edit-profile-modal-title" but the <h1> rendered by getHeaderTitle() has no
matching id, so add id="edit-profile-modal-title" to the heading element (the
<h1> that displays getHeaderTitle()) in EditProfileModal.jsx so the dialog's
accessible name is announced correctly by screen readers; alternatively, update
the dialog's aria-labelledby to match an existing element id if you prefer a
different target.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 644eb249-f264-42ec-be1a-b6550fb24c71

📥 Commits

Reviewing files that changed from the base of the PR and between 8e4747d and ed6b824.

📒 Files selected for processing (6)
  • frontend/src/components/mypage/ActivitySection.jsx
  • frontend/src/components/mypage/ChangeInfoForm.jsx
  • frontend/src/components/mypage/EditProfileModal.jsx
  • frontend/src/components/mypage/MyPageMenu.jsx
  • frontend/src/components/mypage/ProfileCard.jsx
  • frontend/src/utils/myPageMenuMock.js
💤 Files with no reviewable changes (2)
  • frontend/src/utils/myPageMenuMock.js
  • frontend/src/components/mypage/MyPageMenu.jsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/components/mypage/ProfileCard.jsx
  • frontend/src/components/mypage/ChangeInfoForm.jsx

Copy link

@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.

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/mypage/MyPageMenu.jsx (1)

63-68: ⚠️ Potential issue | 🔴 Critical

data prop 누락으로 런타임 에러 발생 확인됨

ActivityModal을 호출할 때 data prop이 제공되지 않았으나, ActivityModal.jsx는 여전히 data 파라미터를 선언하고 사용합니다:

const ActivityModal = ({ isOpen, onClose, title, kind, data }) => {
  ...
  {kind === 'attendance' && <AttendanceSection items={data.items} />}
  {kind === 'activity' && <ActivitySection items={data.items} />}
  {kind === 'points' && <PointsSection items={data.items} />}

kind이 'activity', 'points', 또는 'attendance'일 때 data.items 접근 시 TypeError: Cannot read property 'items' of undefined 에러가 발생합니다.

주의: ActivitySectionPointsSectionitems prop을 받지 않으며 내부에서 데이터를 페칭합니다. ActivityModal을 수정하여 자식 컴포넌트들의 자체 데이터 페칭 방식과 동기화하거나, MyPageMenu에서 data prop을 전달해야 합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/mypage/MyPageMenu.jsx` around lines 63 - 68, The
modal is crashing because ActivityModal expects a data prop but MyPageMenu
wasn’t passing it; update the MyPageMenu ActivityModal invocation to include
data={selectedItem?.data} (or the correct payload on selectedItem) and in
ActivityModal (component) guard any access to data.items (e.g., only read
data.items when kind === 'attendance' && data) while not passing items to
ActivitySection or PointsSection (since they fetch their own data); reference
components: MyPageMenu, ActivityModal, AttendanceSection, ActivitySection,
PointsSection, and prop selectedItem to locate the changes.
♻️ Duplicate comments (1)
frontend/src/utils/axios.js (1)

2-6: ⚠️ Potential issue | 🟠 Major

프로덕션 환경에서 API 호스트 불일치 문제가 여전히 존재합니다.

BASE_URL을 계산해 두고도 baseURL을 빈 문자열로 고정하고 있습니다. Line 20의 토큰 재발급 요청은 BASE_URL을 사용하는 반면, 일반 API 요청은 상대 경로로 나가므로 프로덕션에서 nginx proxy 설정 없이는 두 흐름이 서로 다른 호스트를 바라보게 됩니다.

이전 리뷰에서 수정된 것으로 표시되었으나, 현재 코드에서는 여전히 동일한 패턴이 보입니다. 의도적인 변경인지 확인이 필요합니다.

수정 제안
 const BASE_URL = import.meta.env.DEV ? '' : import.meta.env.VITE_API_URL || '';
 export const api = axios.create({
-  baseURL: '',
+  baseURL: BASE_URL,
   withCredentials: true,
 });

,

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/utils/axios.js` around lines 2 - 6, The axios instance `api` is
created with baseURL set to an empty string while `BASE_URL` is computed,
causing host mismatches (token refresh at line ~20 uses BASE_URL but other
requests do not); update the `axios.create` call to use the `BASE_URL` constant
(i.e., set baseURL: BASE_URL) and ensure any token refresh/request helpers (the
token reissue call referenced around line 20) also use the same `api` instance
or `BASE_URL` so all requests target the same host.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@frontend/src/components/mypage/MyPageMenu.jsx`:
- Around line 63-68: The modal is crashing because ActivityModal expects a data
prop but MyPageMenu wasn’t passing it; update the MyPageMenu ActivityModal
invocation to include data={selectedItem?.data} (or the correct payload on
selectedItem) and in ActivityModal (component) guard any access to data.items
(e.g., only read data.items when kind === 'attendance' && data) while not
passing items to ActivitySection or PointsSection (since they fetch their own
data); reference components: MyPageMenu, ActivityModal, AttendanceSection,
ActivitySection, PointsSection, and prop selectedItem to locate the changes.

---

Duplicate comments:
In `@frontend/src/utils/axios.js`:
- Around line 2-6: The axios instance `api` is created with baseURL set to an
empty string while `BASE_URL` is computed, causing host mismatches (token
refresh at line ~20 uses BASE_URL but other requests do not); update the
`axios.create` call to use the `BASE_URL` constant (i.e., set baseURL: BASE_URL)
and ensure any token refresh/request helpers (the token reissue call referenced
around line 20) also use the same `api` instance or `BASE_URL` so all requests
target the same host.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 85c24525-eeec-4329-a81b-42aa31343c72

📥 Commits

Reviewing files that changed from the base of the PR and between ed6b824 and 7c3930d.

📒 Files selected for processing (2)
  • frontend/src/components/mypage/MyPageMenu.jsx
  • frontend/src/utils/axios.js

Copy link

@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.

🧹 Nitpick comments (1)
frontend/src/components/mypage/ActivityModal.jsx (1)

21-40: 접근성 향상을 위해 포커스 트래핑 추가를 권장합니다.

현재 role="dialog", aria-modal="true" 등 기본적인 접근성 속성은 잘 구현되어 있습니다. 다만, 완전한 모달 접근성을 위해 다음 사항을 고려해 주세요:

  1. 모달이 열릴 때 포커스를 모달 내부 첫 번째 요소로 이동
  2. Tab 키로 모달 외부로 포커스가 나가지 않도록 포커스 트래핑
  3. 모달이 닫힐 때 원래 트리거 요소로 포커스 복귀
♻️ 포커스 트래핑을 위한 useRef 활용 예시
+import { useEffect, useRef } from 'react';
-import { useEffect } from 'react';
 import styles from './ActivityModal.module.css';
 // ...

 const ActivityModal = ({ isOpen, onClose, title, kind }) => {
+  const modalRef = useRef(null);
+
   useEffect(() => {
     if (!isOpen) return;
+
+    // 모달이 열리면 포커스 이동
+    modalRef.current?.focus();

     const onKeyDown = (e) => {
       if (e.key === 'Escape') onClose();
     };
     // ...
   }, [isOpen, onClose]);

   // ...
   return (
     <div className={styles.modalOverlay} /* ... */>
-      <div className={styles.modalContent} onClick={(e) => e.stopPropagation()}>
+      <div
+        ref={modalRef}
+        tabIndex={-1}
+        className={styles.modalContent}
+        onClick={(e) => e.stopPropagation()}
+      >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/mypage/ActivityModal.jsx` around lines 21 - 40, Add
focus trapping to the ActivityModal component: store the previously focused
element in a ref when the modal mounts, move focus into the modal (e.g., to the
first focusable child inside the element referenced by modalContentRef) in
useEffect, and restore focus to the saved element when onClose is called or the
component unmounts; add a keydown handler on the modal (mounted on the element
referenced by modalContentRef or modalOverlay) that intercepts Tab and Shift+Tab
to cycle focus among focusable elements inside the modal so focus cannot escape,
and ensure the close button (closeButton) still triggers onClose and focus
restoration.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@frontend/src/components/mypage/ActivityModal.jsx`:
- Around line 21-40: Add focus trapping to the ActivityModal component: store
the previously focused element in a ref when the modal mounts, move focus into
the modal (e.g., to the first focusable child inside the element referenced by
modalContentRef) in useEffect, and restore focus to the saved element when
onClose is called or the component unmounts; add a keydown handler on the modal
(mounted on the element referenced by modalContentRef or modalOverlay) that
intercepts Tab and Shift+Tab to cycle focus among focusable elements inside the
modal so focus cannot escape, and ensure the close button (closeButton) still
triggers onClose and focus restoration.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ee9c911c-8319-48d2-a4fb-ad20ad37a590

📥 Commits

Reviewing files that changed from the base of the PR and between 7c3930d and c4bf624.

📒 Files selected for processing (1)
  • frontend/src/components/mypage/ActivityModal.jsx

Copy link
Contributor

@discipline24 discipline24 left a comment

Choose a reason for hiding this comment

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

고생하셨습니다~

@yyunee yyunee merged commit 1de99bf into main Mar 10, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

FE 프론트엔드 이슈

Projects

None yet

Development

Successfully merging this pull request may close these issues.

❗ [버그][출석] 출석관리 페이지 버그 수정 필요 ⚙️ [기능추가][회원] 마이페이지 출석 활동 로그 페이지 구현

2 participants