Conversation
📝 WalkthroughWalkthrough설정 페이지의 상태와 저장 로직을 전용 훅으로 분리했습니다. 타임라인 화면의 상태, 렌더링, 삭제 흐름을 컴포넌트와 훅으로 재구성했습니다. 권한 표시, 초대 UI, 워크스페이스 저장 조건, 로그아웃 캐시 처리를 변경했습니다. Changes설정 상태 및 저장 흐름
타임라인 화면 구조화
워크스페이스 권한 및 UI 변경
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant Setting
participant useSettingSave
participant AccountAPI
participant NotificationAPI
Setting->>useSettingSave: 설정 저장 요청
useSettingSave->>AccountAPI: 프로필·비밀번호 저장
useSettingSave->>NotificationAPI: 알림 설정 저장
useSettingSave-->>Setting: 저장 결과와 상태 반환
sequenceDiagram
participant Timeline
participant useTimelinePanel
participant useTimelineSummaryPolling
participant TimelineAPI
Timeline->>useTimelinePanel: 타임라인 바 선택
useTimelinePanel->>TimelineAPI: 상세 데이터 조회
Timeline->>useTimelineSummaryPolling: 요약 생성 요청
useTimelineSummaryPolling->>TimelineAPI: 상세 데이터 폴링
TimelineAPI-->>useTimelineSummaryPolling: 요약 데이터 반환
Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 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 |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (8)
src/hooks/setting/useSettingProfile.ts (1)
68-92: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift회원 정보 조회를
useCoreQuery로 옮기는 방안을 검토해주세요.현재
getMyInfo호출을useEffect+useState로 직접 관리합니다. 같은 코호트의useSettingNotifications.ts는useCoreQuery를 사용합니다. 서버 상태를 수동으로 다루면 캐시 공유, 재요청, 에러 재시도를 직접 구현해야 합니다.
savedProfile은useCoreQuery로 가져오고,draftProfile만 로컬 상태로 유지하는 구조를 제안합니다. 저장 성공 후에는applyAccountSaveSuccess대신 쿼리 무효화로 동기화할 수 있습니다.코딩 가이드라인의 "Server state hooks: use
useCoreQueryanduseCoreMutationfromsrc/hooks/customQuery.ts. Avoid directuseQuery/useMutation" 규칙을 근거로 남깁니다.🤖 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/hooks/setting/useSettingProfile.ts` around lines 68 - 92, Refactor the getMyInfo flow in useSettingProfile to use useCoreQuery, exposing the fetched server data as savedProfile instead of managing its loading and request lifecycle through useEffect and local state. Keep draftProfile as local editable state, map the query data into the existing profile shape, and use the query’s loading/error handling with the existing toast behavior. Ensure profile synchronization after saving uses query invalidation rather than applyAccountSaveSuccess.Source: Coding guidelines
src/pages/setting/Setting.tsx (1)
211-221: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win저장 중 버튼을 비활성화해주세요.
disabled조건에서isSaving이 빠졌습니다. 저장이 진행되는 동안에도hasChanges는true이므로 버튼을 계속 누를 수 있습니다. 중복 실행은useSettingSave의isSavingRef가 막지만, 사용자는 반응 없는 클릭을 반복하게 됩니다.
Button은isLoadingprop을 지원합니다.isLoading을 쓰면 스피너 표시와disabled처리가 함께 적용됩니다.♻️ 제안 수정
size="small" aria-label="개인 설정 변경사항 저장 버튼" onClick={handleSave} - disabled={!hasChanges || isLoading || isNotificationSectionLoading} + isLoading={isSaving} + disabled={!hasChanges || isLoading || isNotificationSectionLoading} className="tablet:w-full" > {isSaving ? "저장 중..." : "저장"}🤖 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 211 - 221, Update the save Button in Setting.tsx to pass isSaving through its supported isLoading prop, so it displays loading feedback and becomes disabled during saving. Preserve the existing disabled conditions for other loading and change states.src/components/common/dropdownmenu/DropdownMenu.tsx (1)
180-185: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win스크롤 위치 계산을
requestAnimationFrame으로 합쳐 주세요.Line 180-185에서 모든 capture-phase
scroll이벤트마다getBoundingClientRect()기반 계산과 상태 setter 호출이 실행됩니다.src/components/timeline/TimelineBar.tsx:104-177처럼 타임라인에서 사용하는 메뉴는 스크롤 이벤트 빈도가 높을 수 있습니다.scroll,resize,ResizeObserver콜백을 한 프레임당 한 번만 처리하면 불필요한 레이아웃 측정과 UI 끊김 위험을 줄일 수 있습니다. cleanup에서는 예약된 프레임도cancelAnimationFrame으로 취소해 주세요.제안 코드
const updatePosition = () => { if (placement === "bottom") { setResolvedPlacement("bottom"); } else if (placement === "top") { setResolvedPlacement("top"); } else { setResolvedPlacement(resolveAutoPlacement(el, items.length)); } if (!fullWidth) { setHorizontalAlign(resolveHorizontalAlign(el)); } else { setHorizontalAlign("right"); } }; + let frameId: number | null = null; + const schedulePositionUpdate = () => { + if (frameId !== null) return; + frameId = requestAnimationFrame(() => { + frameId = null; + updatePosition(); + }); + }; + updatePosition(); - window.addEventListener("resize", updatePosition); - window.addEventListener("scroll", updatePosition, true); + window.addEventListener("resize", schedulePositionUpdate); + window.addEventListener("scroll", schedulePositionUpdate, true); - const resizeObserver = new ResizeObserver(updatePosition); + const resizeObserver = new ResizeObserver(schedulePositionUpdate); resizeObserver.observe(el); return () => { - window.removeEventListener("resize", updatePosition); - window.removeEventListener("scroll", updatePosition, true); + window.removeEventListener("resize", schedulePositionUpdate); + window.removeEventListener("scroll", schedulePositionUpdate, true); + if (frameId !== null) cancelAnimationFrame(frameId); resizeObserver.disconnect(); };🤖 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/common/dropdownmenu/DropdownMenu.tsx` around lines 180 - 185, Update the DropdownMenu position-update flow around updatePosition so scroll, resize, and ResizeObserver callbacks schedule a single requestAnimationFrame per frame instead of measuring and setting state immediately. Store the scheduled frame handle, perform the existing getBoundingClientRect calculation and state update inside the frame callback, and cancel any pending frame during cleanup alongside removing listeners and disconnecting resizeObserver.src/hooks/timeline/useTimelinePeriod.ts (1)
5-8: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value인터페이스 이름 오타를 정정해 주세요.
IUserTimelinePeriodParams는IUseTimelinePeriodParams가 맞습니다. 다른 훅 파라미터 타입(IUseTimelinePanelParams)과 명명이 어긋납니다.♻️ 제안 수정
-interface IUserTimelinePeriodParams { +interface IUseTimelinePeriodParams { scrollRef: RefObject<HTMLDivElement | null>; hasNoTimelines: boolean; } export function useTimelinePeriod({ scrollRef, hasNoTimelines, -}: IUserTimelinePeriodParams) { +}: IUseTimelinePeriodParams) {🤖 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/hooks/timeline/useTimelinePeriod.ts` around lines 5 - 8, Rename the interface IUserTimelinePeriodParams to IUseTimelinePeriodParams and update every reference to this type in the useTimelinePeriod hook so it matches the existing IUseTimelinePanelParams naming convention.src/hooks/timeline/useTimelinePanel.ts (2)
14-14: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueexport 방식을 다른 타임라인 훅과 맞춰 주세요.
useTimelinePeriod,useTimelineEditModal,useTimelineSummaryPolling은 named export입니다. 이 훅만 default export라Timeline.tsx의 import 문이 혼재합니다.🤖 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/hooks/timeline/useTimelinePanel.ts` at line 14, Update useTimelinePanel to use a named export instead of a default export, matching useTimelinePeriod, useTimelineEditModal, and useTimelineSummaryPolling, and adjust Timeline.tsx to import the hook using the named-export form.
17-26: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win서버 상태를 로컬 state로 복제하지 말고 파생값으로 계산하세요.
panelData는detail에서 순수 변환으로 얻는 값입니다. 현재 구조는useState+useEffect조합이라 렌더가 한 번 더 발생하고,handleBarClick에서setPanelData(null)로 수동 초기화하는 코드도 필요합니다.useMemo로 파생하면 상태 동기화 코드가 사라지고, 선택 바가 바뀌는 즉시 이전 패널 데이터가 남지 않습니다.As per path instructions: "Hook 사용: useEffect 의존성 배열 및 불필요한 사용 검토."♻️ 제안 리팩터
-import { useEffect, useState } from "react"; +import { useMemo, useState } from "react"; @@ const [isPanelOpen, setIsPanelOpen] = useState(false); const [selectedBarId, setSelectedBarId] = useState<number | null>(null); - const [panelData, setPanelData] = useState<ITimelineSummaryPanelData | null>( - null, - ); const { data: detail } = useTimelineDetail(selectedBarId); - useEffect(() => { - if (!detail) return; - setPanelData(buildTimelineSummaryPanel(detail)); - }, [detail]); + const panelData: ITimelineSummaryPanelData | null = useMemo( + () => (detail ? buildTimelineSummaryPanel(detail) : null), + [detail], + ); @@ const handleBarClick = (bar: ITimelineCampaignBar) => { setSelectedBarId(bar.id); - setPanelData(null); setIsPanelOpen(true); };🤖 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/hooks/timeline/useTimelinePanel.ts` around lines 17 - 26, Replace the panelData useState/useEffect synchronization with a useMemo-derived value based on detail and buildTimelineSummaryPanel. Remove the related setPanelData(null) reset from handleBarClick and update any remaining references to use the memoized panelData value.Source: Path instructions
src/components/timeline/TimelineCanvas.tsx (1)
58-66: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win기간 변경으로 바뀌는 빈 상태 안내에 라이브 리전을 추가해 주세요.
이 안내는 사용자가 기간이나 보기 단위를 바꿀 때 동적으로 나타납니다. 현재는 라이브 리전이 없어 스크린 리더가 변경을 알리지 않습니다.
role="status"를 추가하면 변경 시점에 안내가 전달됩니다.As per path instructions: "접근성: 시맨틱 HTML, ARIA 속성 사용 확인."♻️ 제안 수정
- <div className="flex min-h-0 flex-1 flex-col items-center justify-center gap-2 px-6 py-16 text-center"> + <div + role="status" + className="flex min-h-0 flex-1 flex-col items-center justify-center gap-2 px-6 py-16 text-center" + >🤖 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/timeline/TimelineCanvas.tsx` around lines 58 - 66, hasNoVisibleBars 빈 상태 안내 컨테이너에 role="status"를 추가해 기간 또는 보기 단위 변경으로 동적으로 표시될 때 스크린 리더에 내용이 전달되도록 수정하세요. 기존 메시지와 레이아웃은 그대로 유지하세요.Source: Path instructions
src/hooks/timeline/useTimelineEditModal.ts (1)
36-39: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win조회 종료 시 로딩 토스트를 정리하세요.
sonner@2.0.7의toast.loading에 고정 ID를 지정하세요.editTimelineId가 없거나isEditDetailLoading이false가 되면toast.dismiss(ID)로 토스트를 닫으세요.🤖 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/hooks/timeline/useTimelineEditModal.ts` around lines 36 - 39, Update the loading toast effect in useTimelineEditModal to use a fixed toast ID with toast.loading, and dismiss that ID via toast.dismiss whenever editTimelineId is absent or isEditDetailLoading becomes false. Preserve showing the loading toast while a timeline edit detail request is active.
🤖 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.
Inline comments:
In `@src/hooks/setting/useSettingNotifications.ts`:
- Around line 88-100: The buildOrgBody function in
src/hooks/setting/useSettingNotifications.ts lines 88-100 must stop implicitly
reading alertClicks and alertReport from savedWorkspaceNotif; require these
values as explicit inputs. Update the buildOrgBody call in
src/hooks/setting/useSettingSave.ts lines 112-142 to pass
notifications.draftWorkspaceNotif.clickAlarm and
notifications.draftWorkspaceNotif.weeklyReport explicitly, preserving the latest
values during the same save operation.
- Around line 160-169: Update handleConnectDiscord to parse the trimmed URL with
new URL() and require HTTPS with the expected Discord webhook hostname,
rejecting malformed URLs and any other host before saving or connecting. Match
the validation behavior used by handleConnectSlack and preserve the existing
error handling.
In `@src/hooks/timeline/useTimelinePeriod.ts`:
- Around line 17-22: Update the useTimelinePeriod call to receive the calculated
totalWidth, and add totalWidth to the scroll-position useEffect dependencies so
scrolling is recalculated when column widths change. Compute totalWidth before
invoking useTimelinePeriod while preserving the existing no-timeline and
viewUnit behavior.
---
Nitpick comments:
In `@src/components/common/dropdownmenu/DropdownMenu.tsx`:
- Around line 180-185: Update the DropdownMenu position-update flow around
updatePosition so scroll, resize, and ResizeObserver callbacks schedule a single
requestAnimationFrame per frame instead of measuring and setting state
immediately. Store the scheduled frame handle, perform the existing
getBoundingClientRect calculation and state update inside the frame callback,
and cancel any pending frame during cleanup alongside removing listeners and
disconnecting resizeObserver.
In `@src/components/timeline/TimelineCanvas.tsx`:
- Around line 58-66: hasNoVisibleBars 빈 상태 안내 컨테이너에 role="status"를 추가해 기간 또는 보기
단위 변경으로 동적으로 표시될 때 스크린 리더에 내용이 전달되도록 수정하세요. 기존 메시지와 레이아웃은 그대로 유지하세요.
In `@src/hooks/setting/useSettingProfile.ts`:
- Around line 68-92: Refactor the getMyInfo flow in useSettingProfile to use
useCoreQuery, exposing the fetched server data as savedProfile instead of
managing its loading and request lifecycle through useEffect and local state.
Keep draftProfile as local editable state, map the query data into the existing
profile shape, and use the query’s loading/error handling with the existing
toast behavior. Ensure profile synchronization after saving uses query
invalidation rather than applyAccountSaveSuccess.
In `@src/hooks/timeline/useTimelineEditModal.ts`:
- Around line 36-39: Update the loading toast effect in useTimelineEditModal to
use a fixed toast ID with toast.loading, and dismiss that ID via toast.dismiss
whenever editTimelineId is absent or isEditDetailLoading becomes false. Preserve
showing the loading toast while a timeline edit detail request is active.
In `@src/hooks/timeline/useTimelinePanel.ts`:
- Line 14: Update useTimelinePanel to use a named export instead of a default
export, matching useTimelinePeriod, useTimelineEditModal, and
useTimelineSummaryPolling, and adjust Timeline.tsx to import the hook using the
named-export form.
- Around line 17-26: Replace the panelData useState/useEffect synchronization
with a useMemo-derived value based on detail and buildTimelineSummaryPanel.
Remove the related setPanelData(null) reset from handleBarClick and update any
remaining references to use the memoized panelData value.
In `@src/hooks/timeline/useTimelinePeriod.ts`:
- Around line 5-8: Rename the interface IUserTimelinePeriodParams to
IUseTimelinePeriodParams and update every reference to this type in the
useTimelinePeriod hook so it matches the existing IUseTimelinePanelParams naming
convention.
In `@src/pages/setting/Setting.tsx`:
- Around line 211-221: Update the save Button in Setting.tsx to pass isSaving
through its supported isLoading prop, so it displays loading feedback and
becomes disabled during saving. Preserve the existing disabled conditions for
other loading and change states.
🪄 Autofix
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 Plus
Run ID: 6551169e-ecba-421b-8f67-d27d85137dcd
⛔ Files ignored due to path filters (1)
src/assets/icon/common/close.svgis excluded by!**/*.svgand included bysrc/**
📒 Files selected for processing (24)
src/components/common/dropdownmenu/DropdownMenu.tsxsrc/components/landing/GuideWorkspace.tsxsrc/components/setting/NotificationSection.tsxsrc/components/setting/ProfileSection.tsxsrc/components/timeline/TimelineBar.tsxsrc/components/timeline/TimelineCanvas.tsxsrc/components/timeline/TimelineDeleteModal.tsxsrc/components/timeline/TimelineToolbar.tsxsrc/components/workspace/InviteMemberModal.tsxsrc/components/workspace/PermissionTable.tsxsrc/hooks/auth/useLogout.tssrc/hooks/setting/useSettingNotifications.tssrc/hooks/setting/useSettingPassword.tssrc/hooks/setting/useSettingProfile.tssrc/hooks/setting/useSettingSave.tssrc/hooks/timeline/useTimelineEditModal.tssrc/hooks/timeline/useTimelinePanel.tssrc/hooks/timeline/useTimelinePeriod.tssrc/hooks/timeline/useTimelineSummaryPolling.tssrc/pages/dashboard/timeline/Timeline.tsxsrc/pages/setting/Setting.tsxsrc/pages/workspace/WorkspaceSetting.tsxsrc/types/setting/settingPage.tssrc/types/workspace/workspace.ts
🚨 관련 이슈
N/A
✨ 변경사항
✏️ 작업 내용
N/A
😅 미완성 작업
N/A
📢 논의 사항 및 참고 사항
N/A
Summary by CodeRabbit
새로운 기능
개선 사항