Skip to content

[Feature/#203] 마이페이지 skeleton UI 구현 - #206

Merged
jjjsun merged 1 commit into
developfrom
feature/#203
May 13, 2026
Merged

jjjsun merged 1 commit into
developfrom
feature/#203

Conversation

@jjjsun

@jjjsun jjjsun commented May 13, 2026

Copy link
Copy Markdown
Collaborator

🚨 관련 이슈

Closed #203

✨ 변경사항

  • 🐞 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.)

✏️ 작업 내용

마이페이지(Setting) 페이지 내에 API로딩상태 분기 하고,
ProfileSection과 PasswordSection의 스켈레톤 UI 추가하였습니다

💻 작업 화면

image

😅 미완성 작업

N/A

📢 논의 사항 및 참고 사항

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

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능
    • 설정 페이지 로딩 시 스켈레톤 UI 표시로 향상된 로딩 상태 피드백 제공
    • 데이터 로드 중 저장 버튼 비활성화로 사용자 경험 개선

Review Change Stack

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

vercel Bot commented May 13, 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 13, 2026 2:45am

@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown
📝 Walkthrough

🔄 전체 변경 사항

Setting 페이지에서 API 데이터 로딩 중 스켈레톤 UI를 표시하도록 구현했습니다. 새로운 ProfileSectionSkeleton과 PasswordSectionSkeleton 컴포넌트를 추가하고, Setting 페이지의 isLoading 상태로 로딩 중일 때 스켈레톤을, 로딩 완료 후 실제 데이터를 렌더링하도록 조건부 처리했습니다.

📋 변경 사항

스켈레톤 로딩 UI

레이어 / 파일 설명
스켈레톤 로딩 컴포넌트
src/components/setting/ProfileSectionSkeleton.tsx, src/components/setting/PasswordSectionSkeleton.tsx
ProfileSection과 PasswordSection의 레이아웃을 모방한 스켈레톤 컴포넌트 두 개를 새로 추가합니다. Skeleton과 SkeletonCircle 프리미티브를 조합하여 사용자 정보 로딩 중에 표시할 플레이스홀더를 구성합니다.
Setting 페이지 로딩 상태 관리
src/pages/setting/Setting.tsx
isLoading 상태를 추가하여 getMyInfo() 비동기 작업 중 상태를 추적합니다. 로딩 중에는 스켈레톤 컴포넌트를 표시하고, 로딩 완료 후 실제 데이터 섹션을 렌더링합니다. finally 블록으로 API 성공/실패 여부와 관계없이 로딩 상태를 해제하고, 저장 버튼의 disabled 조건에 로딩 상태를 포함합니다.

📝 상세 검토 포인트

구조 및 상태 관리

  • isLoading 상태: 페이지 진입 시 getMyInfo() 호출 직전에 true로 설정되고, API 응답(성공/실패 모두) 후 finally 블록에서 false로 설정되어 명확한 생명주기를 가집니다.
  • 조건부 렌더링: isLoading 상태에 따라 스켈레톤 컴포넌트와 실제 섹션을 정확히 교체하여 표시합니다.

사용성

  • UX 개선: 로딩 중 빈 화면 대신 스켈레톤 UI를 표시하여 사용자에게 진행 상황을 시각적으로 전달합니다.
  • 버튼 상태: 로딩 중 저장 버튼 비활성화로 중복 제출 및 상태 불일치를 방지합니다.

안정성

  • 에러 처리: finally 블록 사용으로 API 요청 실패 시에도 로딩 상태가 정확히 해제되어 페이지가 영구적으로 로딩 상태에 머물지 않습니다.

🔗 관련 PR

  • WhereYouAd/WhereYouAd-Frontend#170: Setting.tsx의 ProfileSection/PasswordSection 렌더링 로직과 직접 연결되어 있으며, 이 PR의 스켈레톤 컴포넌트와 조화롭게 작동합니다.
  • WhereYouAd/WhereYouAd-Frontend#201: getMyInfo fetch 로직을 변경한 PR으로, 현재 PR의 로딩 상태 관리와 함께 고려되어야 합니다.

👥 추천 검토자

  • YermIm
  • Seojegyeong

🎯 2 (Simple) | ⏱️ ~12분

🚥 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 구현을 명확하게 요약하고 있으며 이슈 번호 #203을 포함하고 있습니다.
Description check ✅ Passed PR 설명이 템플릿 구조를 따르고 있으며 관련 이슈, 변경사항, 작업 내용, 스크린샷을 모두 포함하고 있습니다.
Linked Issues check ✅ Passed PR이 이슈 #203의 모든 요구사항을 충족합니다. ProfileSectionSkeleton과 PasswordSectionSkeleton 컴포넌트 추가, Setting 페이지의 API 로딩 상태 분기 처리, 로딩 중 스켈레톤 표시 및 완료 후 실제 데이터 렌더링이 구현되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 이슈 #203의 범위 내에 있습니다. 스켈레톤 컴포넌트 추가와 Setting 페이지의 로딩 상태 처리만 포함되어 있으며 불필요한 변경사항은 없습니다.

✏️ 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/#203

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

Copy link
Copy Markdown

📚 Storybook 배포 완료

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

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/pages/setting/Setting.tsx (1)

144-174: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

비동기 작업의 정리(cleanup) 처리 필요

useEffect 내의 비동기 함수(fetchMyInfo)가 컴포넌트 언마운트 시 적절히 정리되지 않고 있습니다. 요청이 진행 중에 컴포넌트가 언마운트되면 상태 업데이트 경고(memory leak)가 발생할 수 있어요.

AbortController를 사용해 요청을 취소하는 정리 로직을 추가해주세요. 코드베이스의 useClickStream.ts에서도 이 패턴을 사용하고 있으니 참고하면 됩니다.

또한 의존성 배열을 []로 수정하세요. setPreview는 React의 useState에서 반환되는 안정적인 참조이므로 매 렌더링마다 동일하며, 현재는 마운트 시에만 데이터를 페칭하는 것이 의도인 것으로 보입니다.

🤖 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/pages/setting/Setting.tsx` around lines 144 - 174, The effect's async
fetchMyInfo should be cancellable to avoid state updates after unmount: create
an AbortController, pass its signal to getMyInfo (or cancel the underlying
request), and check controller.signal.aborted before calling setIsLoading,
setDraftProfile, setSavedProfile, and setPreview; add a cleanup that aborts the
controller. Also change the useEffect dependency array to [] (setPreview is
stable) so fetchMyInfo runs only on mount. Ensure getMyInfo supports/receives
the AbortSignal or that you handle cancellation around the fetch promise before
calling the setters.
🧹 Nitpick comments (3)
src/components/setting/ProfileSectionSkeleton.tsx (1)

3-41: ⚡ Quick win

스켈레톤 컴포넌트에 접근성 속성을 추가해주세요.

현재 스켈레톤 UI가 시각적으로만 로딩 상태를 표시하고 있어요. 스크린 리더 사용자들에게도 로딩 중임을 알릴 수 있도록 적절한 ARIA 속성을 추가하는 것이 좋습니다.

♻️ 접근성 개선 제안
 export default function ProfileSectionSkeleton() {
   return (
-    <div className="bg-white border border-gray-100 rounded-component-lg p-8 shadow-Soft">
+    <div 
+      className="bg-white border border-gray-100 rounded-component-lg p-8 shadow-Soft"
+      role="status"
+      aria-label="프로필 정보 로딩 중"
+    >
       <div className="mb-7 flex items-start gap-4">

As per coding guidelines: "접근성: 시맨틱 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/ProfileSectionSkeleton.tsx` around lines 3 - 41,
ProfileSectionSkeleton currently renders only visual skeletons; update the root
container in ProfileSectionSkeleton to communicate loading to assistive tech by
adding role="status", aria-live="polite", and aria-busy="true" (and an
accessible label like aria-label="Loading profile" or an offscreen "Loading
profile" text). Mark the presentational Skeleton and SkeletonCircle elements as
aria-hidden="true" so screen readers ignore decorative shapes, and ensure the
container exposes a single readable loading message for screen readers. Locate
these changes in the ProfileSectionSkeleton component and adjust
Skeleton/SkeletonCircle usage so they remain decorative while the root status
conveys the loading state.
src/pages/setting/Setting.tsx (1)

144-174: ⚡ Quick win

컴포넌트 언마운트 시 race condition을 방지해주세요.

현재 컴포넌트가 언마운트된 후에도 API 응답이 도착하면 setIsLoading(false)가 실행되어 React에서 경고가 발생할 수 있습니다. AbortController나 cleanup 함수를 사용해서 언마운트 시 상태 업데이트를 방지하는 것이 좋습니다.

♻️ cleanup 추가 제안
 useEffect(() => {
+  let isMounted = true;
+  
   const fetchMyInfo = async () => {
     try {
       setIsLoading(true);
       const res = await getMyInfo();

       const profileData = {
         name: res.data.name,
         organizations:
           res.data.organizations?.map((org) => ({
             name: org.orgName,
             position: org.myRole,
           })) ?? [],
         email: res.data.email,
         phoneNumber: res.data.phoneNumber,
       };
-      setSavedProfile({
-        name: res.data.name,
-        profileImageUrl: res.data.profileImageUrl,
-      });
-      setDraftProfile(profileData);
-      setPreview(res.data.profileImageUrl);
+      if (isMounted) {
+        setSavedProfile({
+          name: res.data.name,
+          profileImageUrl: res.data.profileImageUrl,
+        });
+        setDraftProfile(profileData);
+        setPreview(res.data.profileImageUrl);
+      }
     } catch (error) {
-      toast.error("회원 정보를 불러오는데 실패했습니다");
+      if (isMounted) {
+        toast.error("회원 정보를 불러오는데 실패했습니다");
+      }
       console.error(error);
     } finally {
-      setIsLoading(false);
+      if (isMounted) {
+        setIsLoading(false);
+      }
     }
   };
   fetchMyInfo();
+  
+  return () => {
+    isMounted = false;
+  };
 }, [setPreview]);

As per coding guidelines: "Hook 사용: useEffect 의존성 배열 및 불필요한 사용 검토."

🤖 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/pages/setting/Setting.tsx` around lines 144 - 174, The effect's async
fetchMyInfo may update state after unmount causing a race; modify useEffect to
guard updates by either using an AbortController (pass its signal to getMyInfo
if supported) or a mounted flag: inside useEffect create const controller = new
AbortController() (or let mounted = true), pass controller.signal to getMyInfo
(or check mounted) and in the cleanup call controller.abort() (or set mounted =
false); before calling setIsLoading, setSavedProfile, setDraftProfile,
setPreview ensure the request wasn't aborted (or mounted is true) and handle
abort exceptions silently so no state updates occur after unmount; keep the
effect dependencies minimal (remove setPreview from the array if unnecessary).
src/components/setting/PasswordSectionSkeleton.tsx (1)

3-30: ⚡ Quick win

스켈레톤 컴포넌트에 접근성 속성을 추가해주세요.

ProfileSectionSkeleton과 마찬가지로, 스크린 리더 사용자를 위한 ARIA 속성이 필요합니다.

♻️ 접근성 개선 제안
 export default function PasswordSectionSkeleton() {
   return (
-    <div className="bg-white border border-gray-100 rounded-component-lg p-8 shadow-Soft">
+    <div 
+      className="bg-white border border-gray-100 rounded-component-lg p-8 shadow-Soft"
+      role="status"
+      aria-label="비밀번호 정보 로딩 중"
+    >
       <div className="mb-7">

As per coding guidelines: "접근성: 시맨틱 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/PasswordSectionSkeleton.tsx` around lines 3 - 30, The
PasswordSectionSkeleton component lacks ARIA attributes for screen readers;
update PasswordSectionSkeleton to wrap the skeleton UI in an accessible status
region (e.g., add role="status" and an informative aria-label like "Loading
password settings") and include a visually hidden text node (e.g., "Loading
password settings") so assistive tech announces the state; also mark purely
decorative skeleton pieces (the Skeleton and SkeletonCircle instances) as
aria-hidden to avoid noise. Ensure these changes are made inside the
PasswordSectionSkeleton function around the top-level container and on the
Skeleton/SkeletonCircle elements.
🤖 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.

Outside diff comments:
In `@src/pages/setting/Setting.tsx`:
- Around line 144-174: The effect's async fetchMyInfo should be cancellable to
avoid state updates after unmount: create an AbortController, pass its signal to
getMyInfo (or cancel the underlying request), and check
controller.signal.aborted before calling setIsLoading, setDraftProfile,
setSavedProfile, and setPreview; add a cleanup that aborts the controller. Also
change the useEffect dependency array to [] (setPreview is stable) so
fetchMyInfo runs only on mount. Ensure getMyInfo supports/receives the
AbortSignal or that you handle cancellation around the fetch promise before
calling the setters.

---

Nitpick comments:
In `@src/components/setting/PasswordSectionSkeleton.tsx`:
- Around line 3-30: The PasswordSectionSkeleton component lacks ARIA attributes
for screen readers; update PasswordSectionSkeleton to wrap the skeleton UI in an
accessible status region (e.g., add role="status" and an informative aria-label
like "Loading password settings") and include a visually hidden text node (e.g.,
"Loading password settings") so assistive tech announces the state; also mark
purely decorative skeleton pieces (the Skeleton and SkeletonCircle instances) as
aria-hidden to avoid noise. Ensure these changes are made inside the
PasswordSectionSkeleton function around the top-level container and on the
Skeleton/SkeletonCircle elements.

In `@src/components/setting/ProfileSectionSkeleton.tsx`:
- Around line 3-41: ProfileSectionSkeleton currently renders only visual
skeletons; update the root container in ProfileSectionSkeleton to communicate
loading to assistive tech by adding role="status", aria-live="polite", and
aria-busy="true" (and an accessible label like aria-label="Loading profile" or
an offscreen "Loading profile" text). Mark the presentational Skeleton and
SkeletonCircle elements as aria-hidden="true" so screen readers ignore
decorative shapes, and ensure the container exposes a single readable loading
message for screen readers. Locate these changes in the ProfileSectionSkeleton
component and adjust Skeleton/SkeletonCircle usage so they remain decorative
while the root status conveys the loading state.

In `@src/pages/setting/Setting.tsx`:
- Around line 144-174: The effect's async fetchMyInfo may update state after
unmount causing a race; modify useEffect to guard updates by either using an
AbortController (pass its signal to getMyInfo if supported) or a mounted flag:
inside useEffect create const controller = new AbortController() (or let mounted
= true), pass controller.signal to getMyInfo (or check mounted) and in the
cleanup call controller.abort() (or set mounted = false); before calling
setIsLoading, setSavedProfile, setDraftProfile, setPreview ensure the request
wasn't aborted (or mounted is true) and handle abort exceptions silently so no
state updates occur after unmount; keep the effect dependencies minimal (remove
setPreview from the array if unnecessary).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0540e1e5-623f-4837-abc8-c651517b2cd2

📥 Commits

Reviewing files that changed from the base of the PR and between 136d470 and 2cd8820.

📒 Files selected for processing (3)
  • src/components/setting/PasswordSectionSkeleton.tsx
  • src/components/setting/ProfileSectionSkeleton.tsx
  • src/pages/setting/Setting.tsx

@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: 확인했습니다!

@jjjsun
jjjsun merged commit 27f8916 into develop May 13, 2026
4 checks passed
@jjjsun
jjjsun deleted the feature/#203 branch May 13, 2026 15:11
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