Skip to content

[Feature/#147] 마이페이지 UI구현 - #170

Merged
jjjsun merged 9 commits into
developfrom
feature/#147
May 5, 2026
Merged

jjjsun merged 9 commits into
developfrom
feature/#147

Conversation

@jjjsun

@jjjsun jjjsun commented May 4, 2026

Copy link
Copy Markdown
Collaborator

🚨 관련 이슈

Closed #147

✨ 변경사항

  • 🐞 BugFix Something isn't working
  • 💻 CrossBrowsing Browser compatibility
  • 🌏 Deploy Deploy
  • 🎨 Design Markup & styling
  • 📃 Docs Documentation writing and editing (README.md, etc.)
  • ✨ Feature Feature
  • 🔨 Refactor Code refactoring
  • ⚙️ Setting Development environment setup
  • ✅ Test Test related (storybook, jest, etc.)

✏️ 작업 내용

  • 개인 설정(마이페이지) UI 구현
  • 프로필 정보 카드 및 비밀번호 변경 카드 구성
  • 변경사항 감지한 후에 저장 버튼 활성화 로직 추가

1. 프로필 정보 Section

프로필 이미지 업로드 초기화 UI 구현
이름, 소속조직(조직,직책), 이메일 UI 구현하였고, 소속조직은 없을경우 안내문구가 나오도록 구현하였습니다.
소속 조직과 이메일은 readOnly로 구현하여 수정이 불가능하게 하였고, hover tooltip을 추가하여 사용자가 읽기전용임을 인지하도록 구현하였습니다.

2. 비밀번호 변경 Section

현재 비밀번호, 새 비밀번호, 새 비밀번호 확인 입력 필드 UI 구현하였고, eye아이콘을 사용하여 비밀번호 보기/숨기기 토글을 추가하였습니다.
비밀번호이기에 영문,숫자,특수문자포함 8~16자가 되는지 유효성 검사 로직도 추가하였습니다.

3. 변경사항 저장 버튼

변경사항이 없으면 disabled로 회색처리하였고, 변경사항있을경우에만 저장되도록 설정하였습니다.

4. 반응형

반응형은 전체화면과 tablet 두가지 반응형 적용해두었습니다.

💻 작업 화면

전체 화면

screencapture-localhost-5173-setting-2026-05-04-15_54_13

tablet 전체화면

screencapture-localhost-5173-setting-2026-05-04-15_54_43

소속 조직 없을시 화면

screencapture-localhost-5173-setting-2026-05-04-15_55_18

비밀번호 로직 실패시 화면

image

😅 미완성 작업

로그아웃 버튼

📢 논의 사항 및 참고 사항

조직, 이메일부분을 text로 받기에는 사용자에게 변경가능의혼동을 줄수있어서 변경불가능하도록 변경하였고,
조직부분은 다수의 워크스페이스 소속이 가능한 방침으로변경하였기에 나열형식으로 변경하였습니다.
이미지부분도 프로필이미지이기에 rounded네모보다는 원형이 더 어울리는 이미지이기에 변경하였습니다.

마이페이지 API 데이터값에 phoneNumber가 있어서, 전화번호 값 추가하였습니다.

변경사항이 있을때만 변경사항저장하기 버튼이 활성화되도록 설정하였습니다.

💬 리뷰어 가이드 (P-Rules)
P1: 필수 반영 (Critical) - 버그 가능성, 컨벤션 위반. 해결 전 머지 불가.
P2: 적극 권장 (Recommended) - 더 나은 대안 제시. 가급적 반영 권장.
P3: 제안 (Suggestion) - 아이디어 공유. 반영 여부는 드라이버 자율.
P4: 단순 확인/칭찬 (Nit) - 사소한 오타, 칭찬 등 피드백.

Summary by CodeRabbit

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 설정 페이지 추가: 프로필 편집, 이미지 업로드(미리보기/변경/초기화), 비밀번호 변경 UI 포함
    • 비밀번호 필드별 표시/숨김 토글 및 실시간 검증 피드백 추가
    • 사이드바 및 라우트에 설정 메뉴 항목 추가
  • Bug Fixes

    • 권한 저장 흐름 개선 및 저장 성공/오류 알림 처리 강화

@jjjsun
jjjsun requested review from Seojegyeong and YermIm May 4, 2026 06:13
@jjjsun jjjsun self-assigned this May 4, 2026
@jjjsun jjjsun added 🎨 Html&css 마크업 & 스타일링 ✨ Feature 기능 개발 labels May 4, 2026
@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

설정 페이지(Setting)를 새로 구현하고 프로필 편집(이미지 업로드 포함) 및 비밀번호 변경 UI와 상태·유효성 로직을 추가했습니다. 라우트와 사이드바 네비게이션에 setting 경로를 등록했고, 이미지 업로드 훅과 권한 테이블의 저장 시뮬레이션 로직을 포함했습니다.

Changes

마이페이지(Setting) 기능 구현

Layer / File(s) Summary
데이터/타입
src/components/setting/PasswordSection.tsx
PasswordSection의 prop 타입 TPasswordSectionProps 추가(세 필드 값/셋터, errors).
커스텀 훅
src/hooks/common/useImageUploader.ts
useImageUploader 훅 추가: fileRef, file, preview, openFilePicker, onPickFile, resetImage 반환. object URL 생성/정리 구현.
UI 컴포넌트(구현)
src/components/setting/ProfileSection.tsx, src/components/setting/PasswordSection.tsx
ProfileSection: 프로필 이미지 입력/미리보기, 이름 편집, 이메일/전화 비활성 입력, 조직 리스트 렌더링(전화번호 prop 추가). PasswordSection: 현재/새 비밀번호/확인 입력과 가시성 토글, errors 표시.
페이지 통합/로직
src/pages/setting/Setting.tsx
Setting 페이지 전체 구현: savedProfile/draftProfile 분리, 비밀번호 유효성 검사(validatePassword), 변경 감지(hasChanges), 저장 핸들러(검증 후 커밋) 및 Profile/Password 섹션과 훅 연결.
라우팅 및 네비게이션
src/routes/MainRoutes.tsx, src/constants/sidebarNav.ts
Setting 라우트를 lazy-load로 MainRoutes에 추가하고 사이드바/푸터 네비에 /setting 경로 추가.
문서/테스트 없음 UI·훅·페이지 구현에는 테스트나 문서 변경이 포함되지 않음.

PermissionTable 저장 시뮬레이션

Layer / File(s) Summary
핵심 로직 변경
src/components/workspace/PermissionTable.tsx
handleSaveChanges의 placeholder를 대체: 500ms 대기 후 draftPermissionStatesavedPermissionState로 복사하고 성공 toast 노출. 기존 에러/로딩 처리 유지.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 분

Possibly related PRs

Suggested reviewers

  • Seojegyeong
  • YermIm
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
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 (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 마이페이지 UI 구현이라는 주요 변경사항을 명확하게 요약하고 있습니다.
Description check ✅ Passed PR 설명이 템플릿 구조를 따르고 있으며, 변경사항, 작업 내용, 미완성 작업, 참고사항이 모두 포함되어 있고 스크린샷도 첨부되어 있습니다.
Linked Issues check ✅ Passed PR의 코드 변경사항이 #147 이슈의 모든 필수 체크리스트 항목을 충족하고 있습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 마이페이지 UI 구현이라는 명확한 목표 범위 내에 있으며, 관련 없는 변경은 발견되지 않습니다.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/#147

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.

@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown

📚 Storybook 배포 완료

항목 링크
📖 Storybook https://69a147b60a56365d9e2185ef-iaauptohlg.chromatic.com/
🔍 Chromatic https://www.chromatic.com/build?appId=69a147b60a56365d9e2185ef&number=238

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 8aebcca and c388674.

⛔ Files ignored due to path filters (3)
  • src/assets/icon/common/camera.svg is excluded by !**/*.svg and included by src/**
  • src/assets/icon/common/lock.svg is excluded by !**/*.svg and included by src/**
  • src/assets/icon/common/userProfileCircle.svg is excluded by !**/*.svg and included by src/**
📒 Files selected for processing (6)
  • src/components/setting/PasswordSection.tsx
  • src/components/setting/ProfileSection.tsx
  • src/components/workspace/PermissionTable.tsx
  • src/hooks/common/useImageUploader.ts
  • src/pages/setting/Setting.tsx
  • src/routes/MainRoutes.tsx
💤 Files with no reviewable changes (1)
  • src/components/workspace/PermissionTable.tsx

Comment thread src/components/setting/PasswordSection.tsx
Comment thread src/components/setting/ProfileSection.tsx Outdated
Comment thread src/hooks/common/useImageUploader.ts
Comment thread src/hooks/common/useImageUploader.ts
Comment thread src/pages/setting/Setting.tsx Outdated
Comment thread src/pages/setting/Setting.tsx Outdated
Comment thread src/pages/setting/Setting.tsx Outdated

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

P4: 확인했습니다!

@Seojegyeong

Copy link
Copy Markdown
Collaborator

P3: develop pull 받아오고 진행하셔야 할 것 같습니다! 또한 페이지 네비게이션 헤더에 표시하여 페이지 내 pageHeader 제거되었으니 참고해주세요!

@YermIm

YermIm commented May 4, 2026

Copy link
Copy Markdown
Collaborator

P3: 현재 조직과 직책이 분리되어 있어서 한 눈에 잘 들어오지 않는 것 같습니다. 조직 | 직책이나 조직(직책)과 같이 한 쌍으로 인식되도록 해도 좋을 것 같아요! 수고하셨습니다!

@vercel

vercel Bot commented May 5, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
where-you-ad Ready Ready Preview, Comment May 5, 2026 5:14am

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

♻️ 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

📥 Commits

Reviewing files that changed from the base of the PR and between c388674 and 55692a7.

📒 Files selected for processing (6)
  • src/components/setting/PasswordSection.tsx
  • src/components/setting/ProfileSection.tsx
  • src/constants/sidebarNav.ts
  • src/hooks/common/useImageUploader.ts
  • src/pages/setting/Setting.tsx
  • src/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

@jjjsun
jjjsun merged commit 1b5680e into develop May 5, 2026
4 checks passed
@jjjsun
jjjsun deleted the feature/#147 branch May 5, 2026 05:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 기능 개발 🎨 Html&css 마크업 & 스타일링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨ [Feature] 마이페이지 UI 구현

3 participants