[Feature/#147] 마이페이지 UI구현 - #170
Conversation
📝 WalkthroughWalkthrough설정 페이지(Setting)를 새로 구현하고 프로필 편집(이미지 업로드 포함) 및 비밀번호 변경 UI와 상태·유효성 로직을 추가했습니다. 라우트와 사이드바 네비게이션에 setting 경로를 등록했고, 이미지 업로드 훅과 권한 테이블의 저장 시뮬레이션 로직을 포함했습니다. Changes마이페이지(Setting) 기능 구현
PermissionTable 저장 시뮬레이션
Sequence DiagramsequenceDiagram
participant User as 사용자
participant Setting as SettingPage
participant Profile as ProfileSection
participant Hook as useImageUploader
participant Browser as 브라우저
User->>Setting: 설정 페이지 열기
Setting->>Profile: ProfileSection 렌더(프롭 전달)
User->>Profile: 이미지 변경 클릭
Profile->>Hook: openFilePicker() 호출
Hook->>Browser: 파일 선택 대화창 트리거
Browser->>Hook: 파일 선택
Hook->>Profile: preview(objectURL) 전달
Profile->>User: 이미지 미리보기 표시
User->>Setting: 변경사항 저장 클릭
Setting->>Setting: validatePassword() 실행
alt 유효하면
Setting->>Setting: draftProfile -> savedProfile 커밋
Setting->>User: 성공 상태(토스트/버튼 비활성화)
else 유효하지 않음
Setting->>User: 에러 메시지 표시
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 분 Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
📚 Storybook 배포 완료
|
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/setting/PasswordSection.tsx`:
- Around line 64-98: The visibility toggle buttons for the password inputs lack
accessible names and state; update the three button elements that call
setShowCurrent, setShowNew, and setShowConfirm (the buttons rendering EyeIcon /
EyeOffIcon) to include an appropriate aria-label (e.g., "Toggle current password
visibility", "Toggle new password visibility", "Toggle confirm password
visibility") and expose the current state via aria-pressed={showCurrent},
aria-pressed={showNew}, and aria-pressed={showConfirm} respectively so screen
readers receive both role and state information.
In `@src/components/setting/ProfileSection.tsx`:
- Around line 112-145: Change the disabled props on the Input components for
org.name, org.position and the email field to readOnly so they remain focusable
and keyboard-accessible (update the Input usages where value={org.name},
value={org.position}, and value={email} with disabled={true} currently); then
wire the explanatory tooltip/helper to the input via aria-describedby (or render
a visible helper text element) so the message ("조직 정보는 ...", "이메일은 변경할 수 없습니다.")
is announced to assistive tech and reachable by keyboard; ensure the
rightElement={<CheckIcon...>} remains and the container with class "group
relative" still wraps the input so styling/hover works while relying on ARIA for
accessibility.
In `@src/hooks/common/useImageUploader.ts`:
- Around line 24-30: resetImage 함수가 상태만 초기화해도 실제 <input type="file">의
값(fileRef.current.value)이 남아 있어 같은 파일을 다시 선택해도 onChange가 트리거되지 않습니다;
resetImage에서 setFile(null) 및 setPreview(...) 호출 뒤에 fileRef.current가 유효한지 확인한 후
fileRef.current.value = ""로 파일 입력의 값을 명시적으로 초기화하도록 수정하세요 (참고 심볼: resetImage,
fileRef, setFile, setPreview).
- Around line 18-30: The preview blob URL created in useImageUploader (via
URL.createObjectURL in the fileSelect handler and setPreview) is only revoked on
file replace/reset; add a useEffect in the hook that watches preview and returns
a cleanup function which calls URL.revokeObjectURL(prev) to ensure the blob URL
is revoked on component unmount or when preview changes, keep existing revoke
logic in resetImage and the file replace path (functions: setPreview,
resetImage, the file-select handler) but remove duplicate leaks by centralizing
unmount cleanup in the useEffect.
In `@src/pages/setting/Setting.tsx`:
- Around line 29-31: hasChanges currently only compares savedProfile vs
draftProfile (useMemo with savedProfile and draftProfile) so changing only the
password or image doesn't mark unsaved changes; update the hasChanges
calculation (and the similar logic around lines referenced 96-102) to also
consider the password input and image state by including those states in the
dependency array and comparison — e.g., include the password field
(draftPassword or passwordInput) and image identifier/state (draftImage or
imagePreview) when computing hasChanges, or explicitly OR in checks like
passwordInput !== "" or imageChanged flag so the save button becomes enabled
when either the profile object differs OR the password input is non-empty OR the
image was changed; adjust the useMemo declaration and any related variables
(hasChanges, savedProfile, draftProfile, draftPassword/passwordInput,
draftImage/imagePreview) accordingly.
- Around line 59-63: The save handler always calls validatePassword(), blocking
saves when profile-only changes are made; modify handleSave to run password
validation only when the user has touched any password field (e.g.,
currentPassword, newPassword, confirmPassword) or when a passwordDirty flag is
true: first check whether any of those fields are non-empty or a passwordTouched
state is set, and only then call validatePassword() and setPasswordErrors();
otherwise skip validation and proceed with the normal save flow. Update any
input handlers to set a passwordTouched/passwordDirty flag (or derive from
values) so validatePassword() is invoked only when appropriate; consider moving
this logic into a custom hook if you want to separate concerns.
- Around line 26-27: The local variable `file` from useImageUploader is unused
causing TS6133 and image changes not being saved; wire the uploader output into
the draftProfile state so uploads/resets are part of the saved draft.
Concretely, in Setting.tsx locate the useImageUploader destructure (fileRef,
file, preview, openFilePicker, onPickFile, resetImage) and: (1) consume `file`
by updating draftProfile (e.g., set draftProfile.avatar or equivalent) whenever
`file` or `preview` changes or inside the onPickFile handler so the draft
reflects the new image, and (2) have resetImage also clear the draftProfile
image field so resets are captured; this removes the unused-variable error and
ensures profile image changes are included in saving.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 2d5a1cb5-1b9a-4922-88c2-1c2dafabd8cb
⛔ Files ignored due to path filters (3)
src/assets/icon/common/camera.svgis excluded by!**/*.svgand included bysrc/**src/assets/icon/common/lock.svgis excluded by!**/*.svgand included bysrc/**src/assets/icon/common/userProfileCircle.svgis excluded by!**/*.svgand included bysrc/**
📒 Files selected for processing (6)
src/components/setting/PasswordSection.tsxsrc/components/setting/ProfileSection.tsxsrc/components/workspace/PermissionTable.tsxsrc/hooks/common/useImageUploader.tssrc/pages/setting/Setting.tsxsrc/routes/MainRoutes.tsx
💤 Files with no reviewable changes (1)
- src/components/workspace/PermissionTable.tsx
|
P3: develop pull 받아오고 진행하셔야 할 것 같습니다! 또한 페이지 네비게이션 헤더에 표시하여 페이지 내 pageHeader 제거되었으니 참고해주세요! |
|
P3: 현재 조직과 직책이 분리되어 있어서 한 눈에 잘 들어오지 않는 것 같습니다. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src/components/setting/ProfileSection.tsx (1)
108-145:⚠️ Potential issue | 🟠 Major | ⚡ Quick win읽기 전용 필드 접근성이 아직 깨져 있어요 (
disabled제거 + 포커스 기반 안내 필요)Line 110, Line 129, Line 141에서
disabled가 걸려 있어 키보드 포커스가 막히고, Line 118/133/145 툴팁도 hover 기반이라 안내가 전달되지 않습니다. 이 케이스는readOnly만 유지하고aria-describedby로 설명을 연결해 주세요. 툴팁 노출도group-focus-within을 같이 써서 키보드 접근 가능하게 맞추는 게 좋습니다.수정 예시
@@ - <Input - value={`${org.name} (${org.position})`} - disabled - inputClassName="text-text-main" - containerClassName="bg-gray-100" - readOnly - /> + <Input + value={`${org.name} (${org.position})`} + inputClassName="text-text-main" + containerClassName="bg-gray-100" + readOnly + aria-describedby="org-readonly-help" + /> @@ - <div className="pointer-events-none absolute top-full mt-1 whitespace-nowrap rounded bg-gray-800 px-2 py-1 text-xs text-white opacity-0 transition-opacity group-hover:opacity-100"> + <div + id="org-readonly-help" + className="pointer-events-none absolute top-full mt-1 whitespace-nowrap rounded bg-gray-800 px-2 py-1 text-xs text-white opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100" + > 조직 정보는 별도 조직페이지에서 수정할 수 있습니다. </div> @@ <Input label="이메일" value={email} - disabled={true} rightElement={<CheckIcon className="w-6 h-6 text-chart-3" />} readOnly + aria-describedby="email-readonly-help" /> - <div className="pointer-events-none absolute top-full mt-1 whitespace-nowrap rounded bg-gray-800 px-2 py-1 text-xs text-white opacity-0 transition-opacity group-hover:opacity-100"> + <div + id="email-readonly-help" + className="pointer-events-none absolute top-full mt-1 whitespace-nowrap rounded bg-gray-800 px-2 py-1 text-xs text-white opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100" + > 이메일은 변경할 수 없습니다. </div> @@ <Input label="전화번호" value={phoneNumber} - disabled={true} rightElement={<CheckIcon className="w-6 h-6 text-chart-3" />} readOnly + aria-describedby="phone-readonly-help" /> - <div className="pointer-events-none absolute top-full mt-1 whitespace-nowrap rounded bg-gray-800 px-2 py-1 text-xs text-white opacity-0 transition-opacity group-hover:opacity-100"> + <div + id="phone-readonly-help" + className="pointer-events-none absolute top-full mt-1 whitespace-nowrap rounded bg-gray-800 px-2 py-1 text-xs text-white opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100" + > 전화번호는 변경할 수 없습니다. </div>As per coding guidelines, "7. 접근성: 시맨틱 HTML, ARIA 속성 사용 확인."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/setting/ProfileSection.tsx` around lines 108 - 145, Three input fields use disabled which prevents keyboard focus and their explanatory tooltip is hover-only; replace disabled with readOnly on the Input components (the ones rendering email, phoneNumber and the org name Input) and add aria-describedby attributes pointing to unique tooltip IDs (create IDs for the three tooltip divs) so screen readers announce the guidance; also update the tooltip containers to include the CSS state selector group-focus-within alongside group-hover (e.g., keep the existing tooltip markup for the org/email/phone tooltips but add group-focus-within to the class list) so keyboard focus reveals the tooltip.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@src/components/setting/ProfileSection.tsx`:
- Around line 108-145: Three input fields use disabled which prevents keyboard
focus and their explanatory tooltip is hover-only; replace disabled with
readOnly on the Input components (the ones rendering email, phoneNumber and the
org name Input) and add aria-describedby attributes pointing to unique tooltip
IDs (create IDs for the three tooltip divs) so screen readers announce the
guidance; also update the tooltip containers to include the CSS state selector
group-focus-within alongside group-hover (e.g., keep the existing tooltip markup
for the org/email/phone tooltips but add group-focus-within to the class list)
so keyboard focus reveals the tooltip.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: d78a7d5d-56f6-4d3b-a5b7-3086b80733e3
📒 Files selected for processing (6)
src/components/setting/PasswordSection.tsxsrc/components/setting/ProfileSection.tsxsrc/constants/sidebarNav.tssrc/hooks/common/useImageUploader.tssrc/pages/setting/Setting.tsxsrc/routes/MainRoutes.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
- src/components/setting/PasswordSection.tsx
- src/pages/setting/Setting.tsx
- src/hooks/common/useImageUploader.ts
- src/routes/MainRoutes.tsx
🚨 관련 이슈
Closed #147
✨ 변경사항
✏️ 작업 내용
1. 프로필 정보 Section
프로필 이미지 업로드 초기화 UI 구현
이름, 소속조직(조직,직책), 이메일 UI 구현하였고, 소속조직은 없을경우 안내문구가 나오도록 구현하였습니다.
소속 조직과 이메일은 readOnly로 구현하여 수정이 불가능하게 하였고, hover tooltip을 추가하여 사용자가 읽기전용임을 인지하도록 구현하였습니다.
2. 비밀번호 변경 Section
현재 비밀번호, 새 비밀번호, 새 비밀번호 확인 입력 필드 UI 구현하였고, eye아이콘을 사용하여 비밀번호 보기/숨기기 토글을 추가하였습니다.
비밀번호이기에 영문,숫자,특수문자포함 8~16자가 되는지 유효성 검사 로직도 추가하였습니다.
3. 변경사항 저장 버튼
변경사항이 없으면 disabled로 회색처리하였고, 변경사항있을경우에만 저장되도록 설정하였습니다.
4. 반응형
반응형은 전체화면과 tablet 두가지 반응형 적용해두었습니다.
💻 작업 화면
전체 화면
tablet 전체화면
소속 조직 없을시 화면
비밀번호 로직 실패시 화면
😅 미완성 작업
로그아웃 버튼
📢 논의 사항 및 참고 사항
조직, 이메일부분을 text로 받기에는 사용자에게 변경가능의혼동을 줄수있어서 변경불가능하도록 변경하였고,
조직부분은 다수의 워크스페이스 소속이 가능한 방침으로변경하였기에 나열형식으로 변경하였습니다.
이미지부분도 프로필이미지이기에 rounded네모보다는 원형이 더 어울리는 이미지이기에 변경하였습니다.
마이페이지 API 데이터값에 phoneNumber가 있어서, 전화번호 값 추가하였습니다.
변경사항이 있을때만 변경사항저장하기 버튼이 활성화되도록 설정하였습니다.
Summary by CodeRabbit
Summary by CodeRabbit
릴리스 노트
New Features
Bug Fixes