[Setting/#121] ESLint/Prettier 설정 및 useEffect 비동기 랜더링 에러 수정#123
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthrough코드 전체에 걸친 포맷팅(세미콜론·라인엔딩), 접근성·입력 정규화, 에러 타입 가드 추가, requestAnimationFrame 기반 상태 업데이트 스케줄링 및 몇몇 훅/모달의 cleanup 개선이 적용되었습니다. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 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 |
There was a problem hiding this comment.
Actionable comments posted: 15
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
src/components/ui/button.tsx (1)
39-59:⚠️ Potential issue | 🟡 Minor폼 내부 기본 submit 동작을 방지해 주세요.
Button가 네이티브<button>로 렌더링될 때type미지정이면 기본값이submit이라, 의도치 않은 폼 제출이 발생할 수 있어요 (Line 52 주변). 공용 버튼 컴포넌트라 기본type="button"가드가 있으면 안전합니다.제안 코드
function Button({ className, variant = "default", size = "default", asChild = false, ...props }: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & { asChild?: boolean; }) { const Comp = asChild ? Slot : "button"; + const { type, ...restProps } = props; return ( <Comp data-slot="button" data-variant={variant} data-size={size} className={cn(buttonVariants({ variant, size, className }))} - {...props} + {...(!asChild ? { type: type ?? "button" } : {})} + {...restProps} /> ); }As per coding guidelines, "컴포넌트는 단일 책임, props 타입/네이밍 명확히, 접근성(aria) 체크."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ui/button.tsx` around lines 39 - 59, The Button component can render a native "button" element which defaults to type="submit" and may unintentionally submit forms; update the Button function to ensure when rendering the native element (Comp === "button" / asChild is false) you pass a default type="button" if props.type is undefined—modify the rendering logic in Button (and the spread of props) so that type="button" is explicitly applied for the native button case while preserving any user-supplied props.type and leaving Slot rendering unchanged.src/components/owner/BreakTimeModal.tsx (1)
32-45:⚠️ Potential issue | 🟠 Major모달 접근성 속성(role/aria) 보강이 필요합니다.
Line 32-45 구간은 모달 컨테이너가 렌더링되지만
role="dialog"/aria-modal="true"/제목 연결(aria-labelledby)이 없어 스크린리더에서 모달로 인식되지 않을 수 있습니다.예시 수정안
- <div - className="bg-white w-[420px] rounded-2xl p-6 relative" - onClick={(e) => e.stopPropagation()} - > + <div + role="dialog" + aria-modal="true" + aria-labelledby="break-time-modal-title" + className="bg-white w-[420px] rounded-2xl p-6 relative" + onClick={(e) => e.stopPropagation()} + > @@ - <div className="flex items-center gap-2 font-bold text-lg mb-4"> + <div + id="break-time-modal-title" + className="flex items-center gap-2 font-bold text-lg mb-4" + > <Clock className="text-orange-500" /> 브레이크 타임 설정 </div>As per coding guidelines "src/components/**: 컴포넌트는 단일 책임, props 타입/네이밍 명확히, 접근성(aria) 체크."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/owner/BreakTimeModal.tsx` around lines 32 - 45, The modal container in BreakTimeModal should include proper ARIA attributes so assistive tech recognizes it as a dialog: add role="dialog" and aria-modal="true" to the outer div (the element with className "bg-white w-[420px]..."), create a unique id (e.g., breaktime-title) on the title element (the div with Clock and "브레이크 타임 설정") and add aria-labelledby referencing that id on the modal container, and ensure the close button (onClose / <X /> button) has an accessible name (e.g., aria-label="Close" or aria-labelledby) so screen readers can announce it. Ensure these changes are implemented inside the BreakTimeModal component.src/pages/myPage/settingPage.tsx (1)
99-107:⚠️ Potential issue | 🟡 Minor
localStorage.setItem에러 핸들링이 누락되어 있어요.
localStorage.setItem은 프라이빗 브라우징 모드나 스토리지 용량 초과 시 예외를 던질 수 있어요. 현재는 에러 발생 시savedNotifications가 업데이트되어 UI와 실제 저장 상태가 불일치할 수 있습니다.🛡️ 에러 핸들링 추가 제안
const handleSave = () => { if (!isDirty) { alert("변경사항이 없습니다."); return; } - localStorage.setItem(STORAGE_KEY, JSON.stringify(notifications)); - setSavedNotifications(notifications); - alert("변경사항이 저장되었습니다."); + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(notifications)); + setSavedNotifications(notifications); + alert("변경사항이 저장되었습니다."); + } catch (e) { + console.error("설정 저장 실패", e); + alert("설정 저장에 실패했습니다. 다시 시도해주세요."); + } };As per coding guidelines,
src/pages/**파일은 에러 상태 UX를 확인해야 합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/myPage/settingPage.tsx` around lines 99 - 107, handleSave currently calls localStorage.setItem(STORAGE_KEY, JSON.stringify(notifications)) without try/catch, so exceptions (private browsing / quota) can leave setSavedNotifications desynced; wrap the localStorage.setItem call in a try/catch inside handleSave, only call setSavedNotifications(notifications) and show the success alert when setItem succeeds, and in the catch set an error state or show a user-facing error alert/message explaining the save failed and do not update savedNotifications; ensure you reference STORAGE_KEY, notifications, setSavedNotifications and isDirty in the fix.src/components/map/KakaoMap.tsx (1)
182-200:⚠️ Potential issue | 🟡 Minor
selectedLevel이 의존성 배열에 빠져있어요 (stale closure 버그 가능성).마커 클릭 핸들러 안에서
selectedLevel을 사용하는데(Line 184-186), 의존성 배열에는 포함되어 있지 않네요. 이렇게 되면selectedLevelprop이 변경되어도 이전 값이 계속 사용되는 stale closure 문제가 생길 수 있습니다.🔧 수정 제안
- }, [safeMarkers, onSelectMarker]); + }, [safeMarkers, onSelectMarker, selectedLevel]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/map/KakaoMap.tsx` around lines 182 - 200, The useEffect in the KakaoMap component that registers marker click handlers uses selectedLevel inside the click callback but does not list selectedLevel in the dependency array (currently [safeMarkers, onSelectMarker]), which can cause a stale closure; update the effect dependencies to include selectedLevel so the marker click handler closes over the latest selectedLevel value (i.e., add selectedLevel to the dependency array of the useEffect that references selectedLevel, mapRef, markersRef, infoRef and safeMarkers/onSelectMarker).
🧹 Nitpick comments (9)
src/components/store-registration/RegistrationStepper.tsx (1)
19-23: 스테퍼 접근성(현재 단계 전달)만 보강하면 더 좋아요현재 단계 정보가 색상/아이콘 중심이라 스크린리더에서 맥락이 약할 수 있어요.
aria-current="step"과 리스트 시맨틱을 추가해두면 접근성이 좋아집니다.예시 코드
- <div className="flex items-start justify-between max-w-2xl mx-auto"> + <ol className="flex items-start justify-between max-w-2xl mx-auto" aria-label="가게 등록 단계"> {steps.map((step, index) => ( - <div + <li key={step.number} className={`flex items-start ${index !== steps.length - 1 ? "flex-1" : ""}`} + aria-current={currentStep === step.number ? "step" : undefined} > ... - </div> + </li> ))} - </div> + </ol>As per coding guidelines,
src/components/**: 컴포넌트는 단일 책임, props 타입/네이밍 명확히, 접근성(aria) 체크.Also applies to: 35-40
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/store-registration/RegistrationStepper.tsx` around lines 19 - 23, Wrap the step items in proper list semantics (use an <ol> or add role="list" on the container and role="listitem" on each mapped item) and mark the current step with aria-current="step" when the step matches the active/current prop (e.g., compare step.number to a passed-in currentStep or activeStep prop, or index to activeIndex) so screen readers get clear context; update the mapping in RegistrationStepper (the steps.map rendering and the mapped div keyed by step.number) to include those aria/role attributes and ensure the component accepts/uses a clear prop name like currentStep or activeStep.src/pages/myPage/reservationPage.tsx (1)
117-133: 에러/빈 상태에 복구 액션(재시도) 버튼을 추가하면 UX가 좋아집니다페이지 단의 에러 상태에서 사용자가 즉시 회복할 방법이 없어 이탈 가능성이 큽니다. 에러 블록에
다시 시도버튼을 두고fetchReservations를 연결하는 걸 권장드립니다.As per coding guidelines, "src/pages/**: 라우팅/레이아웃 영향, 로딩/에러/empty 상태 UX 확인."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/myPage/reservationPage.tsx` around lines 117 - 133, The error block currently renders only the error message; add a retry action so users can recover by calling fetchReservations. Update the JSX around the conditional that renders error (the branch that shows {error}) to include a button labeled like "다시 시도" that calls the existing fetchReservations handler onClick, styled consistently with the page (use the same tailwind classes as surrounding divs) and ensure the button has an accessible label/aria attributes; optionally add the same retry button to the empty state branch (where reservations.length === 0) that also invokes fetchReservations.src/layouts/PublicLayout.tsx (1)
58-62: 사용자 경험 개선을 권장합니다.네이티브
confirm()과alert()은 브라우저 기본 다이얼로그로, 스타일링이 불가능하고 접근성이 제한적입니다.프로젝트에 이미 UI 컴포넌트 라이브러리(
@/components/ui/button등)가 있으니, 커스텀 모달 컴포넌트로 교체하면 일관된 디자인과 더 나은 접근성(키보드 네비게이션, 스크린 리더 지원 등)을 제공할 수 있습니다.💡 개선 예시
// useConfirmModal 같은 커스텀 훅 활용 예시 const { openConfirm } = useConfirmModal(); const handleLogout = async () => { const confirmed = await openConfirm({ title: "로그아웃", message: "로그아웃 하시겠습니까?", }); if (!confirmed) return; await logout(); clearAuth(); // toast 라이브러리 활용 toast.success("로그아웃 되었습니다."); nav("/", { replace: true }); };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/layouts/PublicLayout.tsx` around lines 58 - 62, Replace the native confirm()/alert() usage in the logout flow with the app's accessible UI modal and toast: replace the inline confirm() call in the logout handler (the function that calls logout() and clearAuth()) by invoking a custom confirm modal hook such as useConfirmModal().openConfirm (or equivalent) to await user confirmation, then call await logout() and clearAuth() only if confirmed, and replace alert("로그아웃 되었습니다.") with a toast.success(...) call and optional navigation (e.g., nav("/", { replace: true })); update the handler name (e.g., handleLogout) and imports to use the modal hook and toast component instead of window.confirm/alert.src/api/bookings.ts (1)
28-40: 타입 안전성 개선 제안
params변수에any타입이 사용되고 있어요. PR 설명에서 언급하신 것처럼 향후 개선이 필요한 부분이네요.또한 Line 39의
as BookingResponse타입 단언도 제거할 수 있을 것 같아요. API 응답 타입이 이미 제네릭으로 지정되어 있다면 불필요한 단언입니다.🔧 타입 안전성 개선 예시
+interface GetBookingsParams { + page: number; + status?: ApiBookingStatus; +} + export const getBookings = async ( status?: ApiBookingStatus, page: number = 1, ): Promise<BookingResponse> => { - const params: any = { page }; - if (status) params.status = status; + const params: GetBookingsParams = { page }; + if (status) { + params.status = status; + } const response = await api.get<{ result: BookingResponse }>( "/api/v1/users/bookings", { params }, ); - return response.data.result as BookingResponse; + return response.data.result; };As per coding guidelines: "응답 타입 안전성 유지."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/api/bookings.ts` around lines 28 - 40, The function getBookings uses an untyped params: any and also uses an unnecessary type assertion on the return; replace params: any with a proper typed object (e.g. const params: { page: number; status?: ApiBookingStatus } = { page }; set params.status = status if provided) so the compiler knows the shape, and remove the trailing "as BookingResponse" by returning response.data.result directly (api.get already uses the generic { result: BookingResponse }), keeping references to getBookings, params, ApiBookingStatus, api.get and BookingResponse to locate and update the code.src/pages/myPage/settingPage.tsx (1)
28-36:ToggleButton의 클릭 영역이 실제 동작과 불일치해요.
div에cursor-pointer가 있어서 전체 영역이 클릭 가능해 보이지만, 실제로 클릭 이벤트는Switch버튼에만 연결되어 있어요. 사용자가 레이블이나 설명 텍스트를 클릭하면 토글이 안 되어서 혼란스러울 수 있습니다.✨ 전체 영역 클릭 가능하게 수정 제안
function ToggleButton({ label, description, enabled, onClick, }: { label: string; description: string; enabled: boolean; onClick: () => void; }) { return ( - <div className="cursor-pointer flex items-center justify-between"> + <div + className="cursor-pointer flex items-center justify-between" + onClick={onClick} + role="button" + tabIndex={0} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + onClick(); + } + }} + > <div className="space-y-0.5"> <p className="font-medium text-gray-900">{label}</p> <p className="text-sm text-gray-500">{description}</p> </div> - <Switch enabled={enabled} onClick={onClick} /> + <Switch enabled={enabled} onClick={(e) => e.stopPropagation()} /> </div> ); }이렇게 하면 전체 영역 클릭과 키보드 접근성(Enter/Space)도 지원할 수 있어요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/myPage/settingPage.tsx` around lines 28 - 36, The container div's cursor-pointer makes the whole row look clickable but only the Switch component handles clicks; move the click/toggle behavior to the container so label/description clicks also toggle: attach the onClick handler to the outer div (the one rendering className="cursor-pointer flex items-center justify-between") and add keyboard support by giving that div role="button", tabIndex={0}, and an onKeyDown that triggers the same toggle on Enter/Space; keep the Switch component receiving its enabled prop but remove or ignore its own onClick to avoid duplicate handlers so Toggle behavior is centralized.src/components/map/KakaoMap.tsx (2)
82-85: 빈catch블록이 디버깅을 어렵게 만들 수 있어요.
relayout()호출에서 에러를 조용히 무시하고 있는데, 문제가 생겼을 때 원인 파악이 어려워질 수 있습니다. 프로덕션에서는 무시해도 괜찮지만, 개발 환경에서라도 로그를 남기면 디버깅에 도움이 될 것 같아요.💡 개발 환경 로깅 예시
try { mapRef.current.relayout(); - } catch {} + } catch (e) { + if (process.env.NODE_ENV === "development") { + console.warn("relayout failed:", e); + } + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/map/KakaoMap.tsx` around lines 82 - 85, The empty catch around mapRef.current.relayout() in KakaoMap silences errors and hinders debugging; change it to catch the error and log it (including the error object/message) when present — e.g., use console.error or your app logger inside the catch so failures in relayout() are visible, and optionally gate the log behind a development check (process.env.NODE_ENV !== 'production') to avoid noisy logs in production.
70-71: SSR 환경에서window접근 시 에러 가능성이 있어요.
useState(!!window.kakao?.maps)부분이 렌더 시점에window를 직접 접근하고 있는데, SSR(서버 사이드 렌더링)이나 테스트 환경에서는window가 없어서 에러가 날 수 있습니다. 지금 당장 문제없다면 괜찮지만, 나중을 대비해서 방어 코드를 넣어두면 안전할 것 같아요.🛡️ 방어 코드 예시
- const [sdkReady, setSdkReady] = useState(!!window.kakao?.maps); + const [sdkReady, setSdkReady] = useState(() => + typeof window !== "undefined" && !!window.kakao?.maps + );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/map/KakaoMap.tsx` around lines 70 - 71, The current initialization of sdkReady uses direct access to window (useState(!!window.kakao?.maps)), which can throw in SSR/test environments; change it to a safe lazy initializer or initialize false and detect the SDK presence inside a useEffect: replace the initial useState for sdkReady with a function that checks typeof window !== "undefined" before referencing window.kakao (e.g., useState(() => typeof window !== "undefined" && !!window.kakao?.maps) or useState(false) and then call setSdkReady(!!window.kakao?.maps) inside useEffect), and keep sdkError/state management (sdkError, setSdkError) unchanged. Ensure the check uses typeof window !== "undefined" and reference the symbols useState, sdkReady, setSdkReady, window.kakao?.maps, sdkError, setSdkError in KakaoMap.tsx.src/hooks/common/useModalPresence.ts (1)
20-22: 불필요하게 복잡한 상태 업데이터에요.setEntered((prev) => (prev ? false : prev));이 로직은
prev가 truthy면false, falsy면prev를 반환합니다. 결국 항상false가 됩니다 (false를 반환하거나 이미 falsy인prev를 반환).💡 단순화 제안
} else { const raf = requestAnimationFrame(() => { - setEntered((prev) => (prev ? false : prev)); + setEntered(false); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/hooks/common/useModalPresence.ts` around lines 20 - 22, The state updater inside the requestAnimationFrame callback in useModalPresence.ts is unnecessarily complicated and always results in false; replace the functional updater setEntered((prev) => (prev ? false : prev)) with a direct update that sets entered to false (e.g., call setEntered(false) or setEntered(() => false)) so the intent is clear and the logic is simplified; keep the change scoped to the requestAnimationFrame callback where setEntered is invoked.src/pages/SearchPage.tsx (1)
248-249: 불필요하게 복잡한 상태 업데이트 로직이에요.setIsSearchingUI((prev) => (prev === false ? prev : false));이 로직은 결국 항상
false를 반환합니다. 더 단순하게 작성할 수 있어요.💡 단순화 제안
if (searchQuery.isSuccess || searchQuery.isError) { const raf = requestAnimationFrame(() => { - setIsSearchingUI((prev) => (prev === false ? prev : false)); + setIsSearchingUI(false); }); return () => cancelAnimationFrame(raf); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/SearchPage.tsx` around lines 248 - 249, The state updater passed to setIsSearchingUI is overly complex and always results in false; replace the updater with a direct setIsSearchingUI(false) inside the requestAnimationFrame callback (and keep or remove the raf variable as needed), referencing the setIsSearchingUI call within the requestAnimationFrame callback to simplify the logic.
🤖 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/map/KakaoMap.tsx`:
- Around line 73-76: The centerRef useRef and accompanying useEffect are unused
and should be removed: delete the declaration centerRef = useRef(center) and the
effect that sets centerRef.current, and if any code elsewhere references
centerRef replace those uses with the direct center prop; search for symbols
centerRef, useRef, and the effect block in KakaoMap to ensure no remaining
references and keep only the live center prop.
In `@src/components/owner/BreakTimeModal.tsx`:
- Line 25: The current validation uses string comparison for time ranges
(isInvalid = start >= end || start < openTime || end > closeTime) which fails if
inputs like "9:00" lack zero-padding; change to numeric minute-based comparison
by parsing start, end, openTime, and closeTime into total minutes (e.g.,
hours*60 + minutes) and then compute isInvalid using those minute values
(compare startMinutes, endMinutes, openMinutes, closeMinutes) so comparisons are
robust regardless of formatting.
In `@src/components/owner/TableCreateModal.tsx`:
- Around line 32-55: The labels for the columns and rows inputs in
TableCreateModal are not programmatically associated with their inputs; add
unique id attributes to the two inputs (e.g., "cols-input" and "rows-input") and
set the corresponding label htmlFor attributes to those ids so assistive tech
can map them; update the JSX around the inputs that use value={cols}
onChange={(e) => setCols(...)} and value={rows} onChange={(e) => setRows(...)}
to include the ids and change the label elements to use htmlFor to match.
- Around line 14-27: The modal wrapper in TableCreateModal lacks accessibility
attributes: add role="dialog" and aria-modal="true" to the inner modal container
(the div with className "bg-white ..."), ensure the modal title element has an
id (e.g. titleId) and set aria-labelledby="{titleId}" on that same container,
and add an accessible label to the close button (the button using onClose and <X
/>) via aria-label="Close dialog" (or similar); keep existing stopPropagation
and onClose behavior intact and update prop types/comments if needed to reflect
the accessible title id usage.
- Around line 40-70: The inputs for cols/rows can produce NaN or non-integer
values because Number(e.target.value) allows intermediate states; update the
onChange handlers for setCols and setRows to parse integers (e.g., parseInt with
radix 10) and store a safe integer/fallback (use 1 when parsing yields NaN), and
in the onClick confirm block convert/round to integers and guard again (inspect
cols and rows values, compute parsedCols/parsedRows, replace NaN with 1, then
clamp between 1 and 10 as you already do with colClamped/rowClamped) before
calling onConfirm(cols, rows). Ensure you reference setCols, setRows, cols,
rows, colClamped/rowClamped and onConfirm when making these changes.
In `@src/components/reservation/modals/ReservationMenuModal.tsx`:
- Around line 50-61: In ReservationMenuModal's useEffect, the equality check
inside the requestAnimationFrame callback is inverted: in the setSelectedMenus
updater you currently return prev when prev !== nextMenus and return nextMenus
when equal; flip this logic so that when JSON.stringify(prev) ===
JSON.stringify(nextMenus) you return prev (skip update), otherwise return
nextMenus (apply update). Update the updater function used by setSelectedMenus
(inside the raf) to use the corrected condition for draft.selectedMenus, leaving
the requestAnimationFrame/cancelAnimationFrame behavior intact.
In `@src/components/reservation/modals/ReservationModal.tsx`:
- Around line 168-174: The effect that resets seatType and selectedTableId on
changes to people, date, time is also running on initial mount and right after
initialDraft is applied; update the useEffect so it only clears values when
those deps actually change after initialization: add a ref (e.g., prevDepsRef or
initializedRef) and compare previous people/date/time to current ones (or set an
initialized flag after applying initialDraft in the component), then call
setSeatType(null) and setSelectedTableId(null) only when a real change is
detected (use the existing useEffect, setSeatType, setSelectedTableId, and
initialDraft variables to locate where to add the ref/comparison).
In `@src/hooks/common/useModalMotion.ts`:
- Around line 7-12: The rAF callback incorrectly forces entered to true twice,
preventing exit animations; change the requestAnimationFrame callback to
setEntered(open) (or setEntered(false) when !open and setEntered(true) when
open) instead of always calling setEntered(true), and ensure you still capture
the rAF id and cancel it on cleanup; update references in useModalMotion
(variables open, setEntered, entered, and the rAF id) so
backdropMotionClass(entered) and panelMotionClass(entered) can receive the
proper false state on close (consider following useModalPresence branching if
finer timing is needed).
In `@src/hooks/common/useModalPresence.ts`:
- Around line 14-17: In useModalPresence, the current requestAnimationFrame
callback sets both rendered and entered at once causing the enter transition to
be skipped; change it to a two-step RAF sequence: first call
requestAnimationFrame to setRendered(true) only (leaving entered false), then
inside a second requestAnimationFrame (chained or queued) setEntered(true).
Update the raf handling (the raf variable and any cancellation/cleanup logic) to
track both RAF ids or chain properly so both frames are cancellable, and ensure
setRendered and setEntered are only called in their respective frames.
In `@src/layouts/PublicLayout.tsx`:
- Line 40: Replace the loose catch typing "catch (e: any)" with a safer "catch
(e: unknown)" and add explicit type-narrowing logic to handle known error shapes
(e.g., use axios's isAxiosError(e) when axios is used, or check e instanceof
Error) inside the catch block in the PublicLayout member info fetch routine;
ensure you still early-return on cancelled, call clearAuth() and nav("/", {
replace: true }) for 401/403 responses when using axios, otherwise log a clear
"[member/info] unexpected error" plus the error message/stack for non-axios or
non-Error cases so error handling is type-safe and preserves existing behavior.
In `@src/pages/myPage/MyInfoPage.tsx`:
- Around line 39-51: 현재 useState의 lazy initializer로 original을 설정하면 비동기 로드된 data가
들어와도 업데이트되지 않으므로 original이 빈 값으로 고정됩니다; 수정 방법은 컴포넌트가 data를 받아올 때마다 original(및
draft, serverProfileUrl)을 동기화하는 useEffect를 추가하는 것입니다: 감지할 의존성은 data이고, effect
내부에서 data가 존재하면 const newForm = { email: data.email ?? "", nickname: data.name
?? "", phone: phoneNumber(data.phoneNumber ?? "") }를 만들어 setOriginal(newForm),
setDraft(newForm)와 setServerProfileUrl(toAbsolute(data.profileImageUrl ??
null))를 호출하면 됩니다; 대상 식별자: original, setOriginal, setDraft, setServerProfileUrl,
phoneNumber, toAbsolute, data, useEffect.
- Around line 54-59: The current useMemo for displayProfileSrc (depending on
shownFile and serverProfileUrl) creates object URLs without revoking them,
causing memory leaks; replace the useMemo with a useState and useEffect: create
the object URL inside a useEffect when shownFile is truthy, set it into
displayProfileSrc state, and in the effect cleanup call URL.revokeObjectURL on
the previous object URL; if shownFile is falsy set displayProfileSrc to
serverProfileUrl. Ensure you import/use useState and useEffect and remove the
URL.createObjectURL usage from the old useMemo block and revoke previous URLs in
every cleanup.
In `@src/pages/myPage/reservationPage.tsx`:
- Around line 28-49: The fetchReservations function can suffer from
response-race when activeTab changes quickly: ensure only the latest request
updates state by attaching a request identifier or using an AbortController tied
to activeTab; e.g., create a local token/id or controller before calling
getBookings in fetchReservations, store it in a ref (e.g., latestRequestId or
latestAbortController) and after the await verify the token still matches (or
that the controller wasn't aborted) before calling setLoading(false) and
updating reservations; apply the same pattern to the other fetch call(s)
referenced (lines ~82-84) so stale responses do not overwrite current tab data.
- Around line 30-31: Previous error messages are not cleared, causing stale
error text to persist; update any handlers that call setLoading(true) (e.g., the
functions where setLoading(true) appears) to also call setError(null) at fetch
start (or immediately after a successful response) so the error state is
reset—apply this change around the spots where setLoading(true) is used
(including the blocks referenced near lines 30, 73-79, and 117-119) and ensure
success paths also call setError(null) after completing the operation.
In `@src/pages/SearchPage.tsx`:
- Around line 187-214: The effect reads and updates geoMap while only listing
results in its dependency array, which can cause a stale-closure bug; update the
effect to avoid adding geoMap as a dependency by using a functional updater or a
ref: inside the useEffect that references geoMap (the run function which filters
results and calls setGeoMap) replace direct reads of geoMap with either a ref
that you keep in sync or call setGeoMap(prev => { const next = new Map(prev); /*
use next as current geoMap, populate from geocodeAddress for targets not in next
*/ return next; }); keep results, isValidLatLng, geocodeAddress and kakao usage
in scope and do not forget to clear the cancelled flag on cleanup to preserve
current behavior.
---
Outside diff comments:
In `@src/components/map/KakaoMap.tsx`:
- Around line 182-200: The useEffect in the KakaoMap component that registers
marker click handlers uses selectedLevel inside the click callback but does not
list selectedLevel in the dependency array (currently [safeMarkers,
onSelectMarker]), which can cause a stale closure; update the effect
dependencies to include selectedLevel so the marker click handler closes over
the latest selectedLevel value (i.e., add selectedLevel to the dependency array
of the useEffect that references selectedLevel, mapRef, markersRef, infoRef and
safeMarkers/onSelectMarker).
In `@src/components/owner/BreakTimeModal.tsx`:
- Around line 32-45: The modal container in BreakTimeModal should include proper
ARIA attributes so assistive tech recognizes it as a dialog: add role="dialog"
and aria-modal="true" to the outer div (the element with className "bg-white
w-[420px]..."), create a unique id (e.g., breaktime-title) on the title element
(the div with Clock and "브레이크 타임 설정") and add aria-labelledby referencing that
id on the modal container, and ensure the close button (onClose / <X /> button)
has an accessible name (e.g., aria-label="Close" or aria-labelledby) so screen
readers can announce it. Ensure these changes are implemented inside the
BreakTimeModal component.
In `@src/components/ui/button.tsx`:
- Around line 39-59: The Button component can render a native "button" element
which defaults to type="submit" and may unintentionally submit forms; update the
Button function to ensure when rendering the native element (Comp === "button" /
asChild is false) you pass a default type="button" if props.type is
undefined—modify the rendering logic in Button (and the spread of props) so that
type="button" is explicitly applied for the native button case while preserving
any user-supplied props.type and leaving Slot rendering unchanged.
In `@src/pages/myPage/settingPage.tsx`:
- Around line 99-107: handleSave currently calls
localStorage.setItem(STORAGE_KEY, JSON.stringify(notifications)) without
try/catch, so exceptions (private browsing / quota) can leave
setSavedNotifications desynced; wrap the localStorage.setItem call in a
try/catch inside handleSave, only call setSavedNotifications(notifications) and
show the success alert when setItem succeeds, and in the catch set an error
state or show a user-facing error alert/message explaining the save failed and
do not update savedNotifications; ensure you reference STORAGE_KEY,
notifications, setSavedNotifications and isDirty in the fix.
---
Nitpick comments:
In `@src/api/bookings.ts`:
- Around line 28-40: The function getBookings uses an untyped params: any and
also uses an unnecessary type assertion on the return; replace params: any with
a proper typed object (e.g. const params: { page: number; status?:
ApiBookingStatus } = { page }; set params.status = status if provided) so the
compiler knows the shape, and remove the trailing "as BookingResponse" by
returning response.data.result directly (api.get already uses the generic {
result: BookingResponse }), keeping references to getBookings, params,
ApiBookingStatus, api.get and BookingResponse to locate and update the code.
In `@src/components/map/KakaoMap.tsx`:
- Around line 82-85: The empty catch around mapRef.current.relayout() in
KakaoMap silences errors and hinders debugging; change it to catch the error and
log it (including the error object/message) when present — e.g., use
console.error or your app logger inside the catch so failures in relayout() are
visible, and optionally gate the log behind a development check
(process.env.NODE_ENV !== 'production') to avoid noisy logs in production.
- Around line 70-71: The current initialization of sdkReady uses direct access
to window (useState(!!window.kakao?.maps)), which can throw in SSR/test
environments; change it to a safe lazy initializer or initialize false and
detect the SDK presence inside a useEffect: replace the initial useState for
sdkReady with a function that checks typeof window !== "undefined" before
referencing window.kakao (e.g., useState(() => typeof window !== "undefined" &&
!!window.kakao?.maps) or useState(false) and then call
setSdkReady(!!window.kakao?.maps) inside useEffect), and keep sdkError/state
management (sdkError, setSdkError) unchanged. Ensure the check uses typeof
window !== "undefined" and reference the symbols useState, sdkReady,
setSdkReady, window.kakao?.maps, sdkError, setSdkError in KakaoMap.tsx.
In `@src/components/store-registration/RegistrationStepper.tsx`:
- Around line 19-23: Wrap the step items in proper list semantics (use an <ol>
or add role="list" on the container and role="listitem" on each mapped item) and
mark the current step with aria-current="step" when the step matches the
active/current prop (e.g., compare step.number to a passed-in currentStep or
activeStep prop, or index to activeIndex) so screen readers get clear context;
update the mapping in RegistrationStepper (the steps.map rendering and the
mapped div keyed by step.number) to include those aria/role attributes and
ensure the component accepts/uses a clear prop name like currentStep or
activeStep.
In `@src/hooks/common/useModalPresence.ts`:
- Around line 20-22: The state updater inside the requestAnimationFrame callback
in useModalPresence.ts is unnecessarily complicated and always results in false;
replace the functional updater setEntered((prev) => (prev ? false : prev)) with
a direct update that sets entered to false (e.g., call setEntered(false) or
setEntered(() => false)) so the intent is clear and the logic is simplified;
keep the change scoped to the requestAnimationFrame callback where setEntered is
invoked.
In `@src/layouts/PublicLayout.tsx`:
- Around line 58-62: Replace the native confirm()/alert() usage in the logout
flow with the app's accessible UI modal and toast: replace the inline confirm()
call in the logout handler (the function that calls logout() and clearAuth()) by
invoking a custom confirm modal hook such as useConfirmModal().openConfirm (or
equivalent) to await user confirmation, then call await logout() and clearAuth()
only if confirmed, and replace alert("로그아웃 되었습니다.") with a toast.success(...)
call and optional navigation (e.g., nav("/", { replace: true })); update the
handler name (e.g., handleLogout) and imports to use the modal hook and toast
component instead of window.confirm/alert.
In `@src/pages/myPage/reservationPage.tsx`:
- Around line 117-133: The error block currently renders only the error message;
add a retry action so users can recover by calling fetchReservations. Update the
JSX around the conditional that renders error (the branch that shows {error}) to
include a button labeled like "다시 시도" that calls the existing fetchReservations
handler onClick, styled consistently with the page (use the same tailwind
classes as surrounding divs) and ensure the button has an accessible label/aria
attributes; optionally add the same retry button to the empty state branch
(where reservations.length === 0) that also invokes fetchReservations.
In `@src/pages/myPage/settingPage.tsx`:
- Around line 28-36: The container div's cursor-pointer makes the whole row look
clickable but only the Switch component handles clicks; move the click/toggle
behavior to the container so label/description clicks also toggle: attach the
onClick handler to the outer div (the one rendering className="cursor-pointer
flex items-center justify-between") and add keyboard support by giving that div
role="button", tabIndex={0}, and an onKeyDown that triggers the same toggle on
Enter/Space; keep the Switch component receiving its enabled prop but remove or
ignore its own onClick to avoid duplicate handlers so Toggle behavior is
centralized.
In `@src/pages/SearchPage.tsx`:
- Around line 248-249: The state updater passed to setIsSearchingUI is overly
complex and always results in false; replace the updater with a direct
setIsSearchingUI(false) inside the requestAnimationFrame callback (and keep or
remove the raf variable as needed), referencing the setIsSearchingUI call within
the requestAnimationFrame callback to simplify the logic.
🪄 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: 9cbcdc66-e161-4e7f-ae36-409c60639e32
⛔ Files ignored due to path filters (8)
.prettierignoreis excluded by none and included by none.prettierrcis excluded by none and included by noneREADME.mdis excluded by none and included by noneeslint.config.jsis excluded by none and included by noneindex.htmlis excluded by none and included by nonepackage.jsonis excluded by none and included by nonepnpm-lock.yamlis excluded by!**/pnpm-lock.yaml,!pnpm-lock.yamland included by nonevite.config.tsis excluded by none and included by none
📒 Files selected for processing (34)
src/api/bookings.tssrc/api/owner/reservation.tssrc/api/owner/table.tssrc/components/customer-support/SupportModal.tsxsrc/components/map/KakaoMap.tsxsrc/components/owner/BreakTimeModal.tsxsrc/components/owner/StoreSettings.tsxsrc/components/owner/TableCreateModal.tsxsrc/components/reservation/modals/ReservationMenuModal.tsxsrc/components/reservation/modals/ReservationModal.tsxsrc/components/restaurant/RestaurantCard.tsxsrc/components/restaurant/RestaurantList.tsxsrc/components/store-registration/RegistrationStepper.tsxsrc/components/ui/button.tsxsrc/components/ui/calendar.tsxsrc/components/ui/card.tsxsrc/components/ui/checkbox.tsxsrc/components/ui/dialog.tsxsrc/components/ui/input.tsxsrc/components/ui/label.tsxsrc/components/ui/popover.tsxsrc/components/ui/separator.tsxsrc/hooks/common/useInView.tssrc/hooks/common/useModalMotion.tssrc/hooks/common/useModalPresence.tssrc/index.csssrc/layouts/PublicLayout.tsxsrc/lib/utils.tssrc/main.tsxsrc/pages/SearchPage.tsxsrc/pages/myPage/MyInfoPage.tsxsrc/pages/myPage/reservationPage.tsxsrc/pages/myPage/settingPage.tsxsrc/types/api.ts
💤 Files with no reviewable changes (3)
- src/types/api.ts
- src/components/owner/StoreSettings.tsx
- src/components/customer-support/SupportModal.tsx
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/pages/myPage/MyInfoPage.tsx`:
- Line 10: The lazy-initializer + useMemo approach breaks sync with
asynchronously loaded data and can leak blob URLs; revert to using useEffect to
synchronize async `data` into component state: add a `serverProfileUrl` setter
(state via useState) and simplify existing useState usage instead of lazy init,
then implement an effect that updates `displayProfileSrc` whenever
`serverProfileUrl` or `fileRef` changes and revokes any created object URLs in
the effect cleanup to avoid memory leaks; reference the existing symbols
`useMemo`, `useRef`, `useState`, `displayProfileSrc`, `serverProfileUrl`, and
ensure the effect replaces the removed initialization so server images display
and original state stays in sync.
- Line 29: Add the missing state setter for serverProfileUrl and synchronize it
from the loaded data: update the useState call to destructure both
serverProfileUrl and setServerProfileUrl, then add a useEffect that watches data
and calls setServerProfileUrl(data.profileImageUrl ?? null) (also sync
original/draft profile state there if present) so displayProfileSrc can reflect
the server image instead of always falling back to null.
🪄 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: d15eb2cd-0338-47e9-8b9c-64be1956e675
📒 Files selected for processing (1)
src/pages/myPage/MyInfoPage.tsx
|
지금 뭔가 꼬여서 잠시만요.. |
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (2)
src/components/reservation/modals/ReservationModal.tsx (1)
82-99:⚠️ Potential issue | 🟠 Major초기 draft 복원 직후에 좌석 선택이 다시 지워질 수 있어요.
Line 82-99에서
initialDraft를 RAF로 반영한 다음 렌더에서, Line 168-179 effect가 아직도 최초 기본값을 기준으로 비교하고 있습니다. 지금은prevDepsRef.current를 한 번도 갱신하지 않아서initialDraft.seatType/initialDraft.tableId가 바로null로 초기화될 수 있고, 이후에도 “직전 값”이 아니라 “마운트 시점 값” 기준으로 reset 여부가 결정됩니다.prevDepsRef를 초기화 시점에도 함께 맞춰 두고, effect 안에서도 최신 값으로 갱신해야 의도한 동작이 유지됩니다.예시 수정
const { rate: depositRate } = useDepositRate(restaurant.id); const { rendered, entered } = useModalPresence(open, 220); const didInitRef = useRef(false); + const prevDepsRef = useRef({ + people: 2, + date: undefined as Date | undefined, + time: "", + }); useEffect(() => { if (!open) { didInitRef.current = false; return; } if (didInitRef.current) return; didInitRef.current = true; + const nextDeps = initialDraft + ? { + people: initialDraft.people, + date: initialDraft.date, + time: initialDraft.time, + } + : { people: 2, date: undefined, time: "" }; const raf = requestAnimationFrame(() => { + prevDepsRef.current = nextDeps; if (initialDraft) { setPeople(initialDraft.people); setDate(initialDraft.date); setTime(initialDraft.time); setSeatType(initialDraft.seatType); setTablePref(initialDraft.tablePref); setSelectedTableId(initialDraft.tableId); } else { setPeople(2); setDate(undefined); setTime(""); setSeatType(null); setTablePref("split_ok"); setSelectedTableId(null); } }); return () => cancelAnimationFrame(raf); }, [open, initialDraft]); - const prevDepsRef = useRef({ people, date, time }); useEffect(() => { const prev = prevDepsRef.current; const changed = - prev.people !== people || prev.date !== date || prev.time != time; + prev.people !== people || prev.date !== date || prev.time !== time; + prevDepsRef.current = { people, date, time }; if (!changed) return; const raf = requestAnimationFrame(() => { setSeatType(null); setSelectedTableId(null); });Also applies to: 168-179
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/reservation/modals/ReservationModal.tsx` around lines 82 - 99, When applying initialDraft inside the requestAnimationFrame block (where you call setPeople, setDate, setTime, setSeatType, setTablePref, setSelectedTableId) you must also initialize/update the component's prevDepsRef to the same values so the later reset effect (the effect using prevDepsRef that checks seatType/tableId around lines 168-179) compares against the current "previous" values instead of the mount-time defaults; additionally, inside that reset effect update prevDepsRef.current after performing the comparison/reset so subsequent renders use the latest previous state when deciding whether to clear seatType or tableId.src/pages/myPage/MyInfoPage.tsx (1)
60-65:⚠️ Potential issue | 🟠 Major여기서 만든 blob URL은 cleanup이 꼭 필요해요.
shownFile이나serverProfileUrl이 바뀔 때마다 새URL.createObjectURL(...)가 만들어지는데, 지금은 해제가 없어서 브라우저 메모리에 계속 남습니다. 이 값은useMemo보다useEffect + URL.revokeObjectURL패턴으로 관리하는 편이 안전해요.예시 수정
- const displayProfileSrc = useMemo(() => { - if (shownFile) { - return URL.createObjectURL(shownFile); - } - return serverProfileUrl; - }, [shownFile, serverProfileUrl]); + const [displayProfileSrc, setDisplayProfileSrc] = useState<string | null>(null); + + useEffect(() => { + if (!shownFile) { + setDisplayProfileSrc(serverProfileUrl); + return; + } + + const objectUrl = URL.createObjectURL(shownFile); + setDisplayProfileSrc(objectUrl); + + return () => { + URL.revokeObjectURL(objectUrl); + }; + }, [shownFile, serverProfileUrl]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/myPage/MyInfoPage.tsx` around lines 60 - 65, The displayProfileSrc memo currently creates blob URLs with URL.createObjectURL(shownFile) but never revokes them, leaking memory; replace the useMemo approach by creating and cleaning up the blob URL inside a useEffect that watches [shownFile, serverProfileUrl]: when shownFile exists, call URL.createObjectURL(shownFile), set it into state (e.g., displayProfileSrc state), and in the effect’s cleanup call URL.revokeObjectURL(previousBlob) to release it; ensure when shownFile is falsy you set displayProfileSrc to serverProfileUrl and revoke any prior blob URL.
🤖 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/layouts/PublicLayout.tsx`:
- Around line 61-67: handleLogout currently aborts local logout when logout()
throws; wrap the remote call in a try/catch or use a try { await logout() }
catch (_) {} finally { clearAuth(); alert("로그아웃 되었습니다."); nav("/", { replace:
true }); } so clearAuth() and the redirect always run even if logout() fails;
refer to handleLogout, logout(), clearAuth(), and nav for locating the fix.
In `@src/pages/myPage/MyInfoPage.tsx`:
- Around line 47-58: The effect that syncs server data (useEffect watching data)
overwrites the user's in-progress edits by always calling setDraft(newForm);
modify this so it only replaces the draft when the user is not actively editing
(check isEditing) or on initial load (e.g., when original is null), while still
updating setOriginal(newForm) and setServerProfileUrl(data.profileImage ??
null); in short, inside the useEffect that runs on data change, guard the
setDraft(newForm) call with a condition like if (!isEditing || !original)
(referencing useEffect, setDraft, setOriginal, setServerProfileUrl, isEditing,
data, phoneNumber) so ongoing edits are preserved during image upload-triggered
refetches.
---
Duplicate comments:
In `@src/components/reservation/modals/ReservationModal.tsx`:
- Around line 82-99: When applying initialDraft inside the requestAnimationFrame
block (where you call setPeople, setDate, setTime, setSeatType, setTablePref,
setSelectedTableId) you must also initialize/update the component's prevDepsRef
to the same values so the later reset effect (the effect using prevDepsRef that
checks seatType/tableId around lines 168-179) compares against the current
"previous" values instead of the mount-time defaults; additionally, inside that
reset effect update prevDepsRef.current after performing the comparison/reset so
subsequent renders use the latest previous state when deciding whether to clear
seatType or tableId.
In `@src/pages/myPage/MyInfoPage.tsx`:
- Around line 60-65: The displayProfileSrc memo currently creates blob URLs with
URL.createObjectURL(shownFile) but never revokes them, leaking memory; replace
the useMemo approach by creating and cleaning up the blob URL inside a useEffect
that watches [shownFile, serverProfileUrl]: when shownFile exists, call
URL.createObjectURL(shownFile), set it into state (e.g., displayProfileSrc
state), and in the effect’s cleanup call URL.revokeObjectURL(previousBlob) to
release it; ensure when shownFile is falsy you set displayProfileSrc to
serverProfileUrl and revoke any prior blob URL.
🪄 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: 08efadcb-1d02-4c65-8e8a-401e7886206c
⛔ Files ignored due to path filters (2)
package.jsonis excluded by none and included by nonepnpm-lock.yamlis excluded by!**/pnpm-lock.yaml,!pnpm-lock.yamland included by none
📒 Files selected for processing (12)
src/api/bookings.tssrc/api/owner/reservation.tssrc/api/owner/table.tssrc/components/map/KakaoMap.tsxsrc/components/owner/BreakTimeModal.tsxsrc/components/owner/TableCreateModal.tsxsrc/components/reservation/modals/ReservationMenuModal.tsxsrc/components/reservation/modals/ReservationModal.tsxsrc/hooks/common/useModalMotion.tssrc/hooks/common/useModalPresence.tssrc/layouts/PublicLayout.tsxsrc/pages/myPage/MyInfoPage.tsx
💤 Files with no reviewable changes (1)
- src/components/map/KakaoMap.tsx
✅ Files skipped from review due to trivial changes (4)
- src/components/reservation/modals/ReservationMenuModal.tsx
- src/api/owner/table.ts
- src/api/owner/reservation.ts
- src/api/bookings.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- src/hooks/common/useModalMotion.ts
- src/components/owner/BreakTimeModal.tsx
- src/components/owner/TableCreateModal.tsx
- src/hooks/common/useModalPresence.ts
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/components/owner/BreakTimeModal.tsx (1)
59-81:⚠️ Potential issue | 🟡 Minor접근성 개선 필요:
<label>과<input>연결이 누락되었어요현재
<label>과<input>이htmlFor/id로 연결되어 있지 않아서, 스크린 리더 사용자가 어떤 입력 필드인지 파악하기 어려워요. 코딩 가이드라인에 따라 접근성(aria) 체크가 필요합니다.♿ 접근성 개선 코드
<div> - <label className="text-sm font-bold text-gray-600">시작 시간</label> + <label htmlFor="break-start-time" className="text-sm font-bold text-gray-600">시작 시간</label> <input + id="break-start-time" type="time" min={openTime} max={closeTime} value={start} onChange={(e) => setStart(e.target.value)} className="w-full mt-1 border rounded-lg p-2 cursor-pointer" /> </div> <div> - <label className="text-sm font-bold text-gray-600">종료 시간</label> + <label htmlFor="break-end-time" className="text-sm font-bold text-gray-600">종료 시간</label> <input + id="break-end-time" type="time" min={openTime} max={closeTime} value={end} onChange={(e) => setEnd(e.target.value)} className="w-full mt-1 border rounded-lg p-2 cursor-pointer" /> </div>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/owner/BreakTimeModal.tsx` around lines 59 - 81, The labels in BreakTimeModal are not associated with their inputs; add id attributes to the start and end time inputs (e.g., "start-time" and "end-time") and set the corresponding label htmlFor values to those ids so screen readers can map the <label> to the correct <input>; ensure the same ids are used on the inputs that bind value={start} / value={end} and onChange handlers (setStart, setEnd), and optionally add aria-describedby or aria-label if additional context is needed.
♻️ Duplicate comments (3)
src/layouts/PublicLayout.tsx (1)
61-67:⚠️ Potential issue | 🟠 Major로그아웃 API 실패 시 로컬 로그아웃이 누락됩니다.
Line 64에서
logout()이 throw하면clearAuth()와 리다이렉트가 실행되지 않아 사용자 입장에서 로그아웃이 실패 상태로 남습니다. 원격 호출 성공 여부와 관계없이 로컬 정리는finally에서 항상 실행되도록 바꿔주세요.수정 예시
const handleLogout = async () => { if (!confirm("로그아웃 하시겠습니까?")) return; - await logout(); - clearAuth(); - alert("로그아웃 되었습니다."); - nav("/", { replace: true }); + try { + await logout(); + } catch (e) { + console.error("[logout] failed", e); + } finally { + clearAuth(); + alert("로그아웃 되었습니다."); + nav("/", { replace: true }); + } };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/layouts/PublicLayout.tsx` around lines 61 - 67, The handleLogout function currently calls logout() and then clearAuth()/nav(), but if logout() throws those local cleanup steps are skipped; change handleLogout to await logout() inside a try/catch and move clearAuth() and nav("/", { replace: true }) into a finally block so local logout/redirect always runs regardless of remote API success (keep or show an error alert in catch as needed); reference handleLogout, logout(), clearAuth, and nav when making the change.src/pages/myPage/MyInfoPage.tsx (2)
60-65:⚠️ Potential issue | 🟠 Major
createObjectURL은useMemo보다 cleanup 있는 effect로 관리하는 게 맞습니다.Line 62에서 blob URL을 만들고 있는데 해제 코드가 없어서, 파일을 바꾸거나 페이지를 벗어날 때 브라우저 메모리에 남습니다. 지금 구조는
serverProfileUrl만 바뀌어도 같은shownFile로 새 URL을 다시 만들 수 있어서 누수가 더 빨라집니다.예시 수정
- const displayProfileSrc = useMemo(() => { - if (shownFile) { - return URL.createObjectURL(shownFile); - } - return serverProfileUrl; - }, [shownFile, serverProfileUrl]); + const [displayProfileSrc, setDisplayProfileSrc] = useState<string | null>(null); + + useEffect(() => { + if (!shownFile) { + setDisplayProfileSrc(serverProfileUrl); + return; + } + + const objectUrl = URL.createObjectURL(shownFile); + setDisplayProfileSrc(objectUrl); + + return () => { + URL.revokeObjectURL(objectUrl); + }; + }, [shownFile, serverProfileUrl]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/myPage/MyInfoPage.tsx` around lines 60 - 65, The blob URL created in displayProfileSrc via useMemo (when shownFile is set and URL.createObjectURL is called) lacks cleanup, causing memory leaks; replace the useMemo blob-creation with a useEffect that watches shownFile and serverProfileUrl, create the object URL inside that effect when shownFile exists, store it in state or a ref used by displayProfileSrc, and in the effect's cleanup call URL.revokeObjectURL on the previously created URL so changing shownFile or unmounting releases the blob.
55-55:⚠️ Potential issue | 🟠 Major편집 중 refetch에서는
draft를 덮어쓰지 않는 쪽이 안전해요.
uploadImage성공 뒤memberInfo가 다시 불러와지면, Line 55의setDraft(newForm)때문에 사용자가 입력 중이던 닉네임/전화번호가 서버값으로 돌아갑니다. 초기 로드이거나 편집이 끝난 경우에만draft를 동기화해야 편집 UX가 깨지지 않습니다.예시 수정
useEffect(() => { - if (data) { - const newForm = { - email: data.email ?? "", - nickname: data.name ?? "", - phone: phoneNumber(data.phoneNumber ?? ""), - }; - setOriginal(newForm); - setDraft(newForm); - setServerProfileUrl(data.profileImage ?? null); - } - }, [data]); + if (!data) return; + + const newForm = { + email: data.email ?? "", + nickname: data.name ?? "", + phone: phoneNumber(data.phoneNumber ?? ""), + }; + + setOriginal(newForm); + setServerProfileUrl(data.profileImage ?? null); + if (!isEditing) { + setDraft(newForm); + } + }, [data, isEditing]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/myPage/MyInfoPage.tsx` at line 55, The current setDraft(newForm) in the memberInfo refetch path overwrites the user's in-progress edits (draft) after uploadImage; change the logic so draft is only replaced on initial load or when not actively editing: in the refetch/response handler that uses newForm and setDraft, add a guard that checks the edit state (e.g., an isEditing/isEditingDraft flag or whether draft is empty/unchanged) and only call setDraft(newForm) when not editing (initial load or after edit completion); update or introduce the edit-state flag used by the MyInfoPage component so uploadImage and memberInfo refetches respect it and do not clobber draft during user edits.
🤖 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/owner/BreakTimeModal.tsx`:
- Around line 43-48: The close button with the <X /> icon lacks an accessible
name; update the button element (the one using onClose and rendering <X />) to
include an appropriate aria-label (e.g., aria-label="닫기" or "Close") so screen
readers announce its purpose, and ensure the decorative <X /> icon is marked
aria-hidden="true" if it doesn't convey additional information.
In `@src/components/reservation/modals/ReservationModal.tsx`:
- Around line 168-179: The useEffect comparing prevDepsRef.current to current
people, date, time never updates prevDepsRef, so comparisons always use the
initial values; inside the useEffect (the one that schedules
requestAnimationFrame and calls setSeatType and setSelectedTableId) update
prevDepsRef.current = { people, date, time } after handling the change and also
use strict equality for time (change prev.time != time to prev.time !== time) to
match the other comparisons and avoid loose equality pitfalls; keep
requestAnimationFrame/cancelAnimationFrame behavior as-is.
---
Outside diff comments:
In `@src/components/owner/BreakTimeModal.tsx`:
- Around line 59-81: The labels in BreakTimeModal are not associated with their
inputs; add id attributes to the start and end time inputs (e.g., "start-time"
and "end-time") and set the corresponding label htmlFor values to those ids so
screen readers can map the <label> to the correct <input>; ensure the same ids
are used on the inputs that bind value={start} / value={end} and onChange
handlers (setStart, setEnd), and optionally add aria-describedby or aria-label
if additional context is needed.
---
Duplicate comments:
In `@src/layouts/PublicLayout.tsx`:
- Around line 61-67: The handleLogout function currently calls logout() and then
clearAuth()/nav(), but if logout() throws those local cleanup steps are skipped;
change handleLogout to await logout() inside a try/catch and move clearAuth()
and nav("/", { replace: true }) into a finally block so local logout/redirect
always runs regardless of remote API success (keep or show an error alert in
catch as needed); reference handleLogout, logout(), clearAuth, and nav when
making the change.
In `@src/pages/myPage/MyInfoPage.tsx`:
- Around line 60-65: The blob URL created in displayProfileSrc via useMemo (when
shownFile is set and URL.createObjectURL is called) lacks cleanup, causing
memory leaks; replace the useMemo blob-creation with a useEffect that watches
shownFile and serverProfileUrl, create the object URL inside that effect when
shownFile exists, store it in state or a ref used by displayProfileSrc, and in
the effect's cleanup call URL.revokeObjectURL on the previously created URL so
changing shownFile or unmounting releases the blob.
- Line 55: The current setDraft(newForm) in the memberInfo refetch path
overwrites the user's in-progress edits (draft) after uploadImage; change the
logic so draft is only replaced on initial load or when not actively editing: in
the refetch/response handler that uses newForm and setDraft, add a guard that
checks the edit state (e.g., an isEditing/isEditingDraft flag or whether draft
is empty/unchanged) and only call setDraft(newForm) when not editing (initial
load or after edit completion); update or introduce the edit-state flag used by
the MyInfoPage component so uploadImage and memberInfo refetches respect it and
do not clobber draft during user edits.
🪄 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: 3936fba9-a543-4e76-9324-285a4d8770cb
⛔ Files ignored due to path filters (8)
.prettierignoreis excluded by none and included by none.prettierrcis excluded by none and included by noneREADME.mdis excluded by none and included by noneeslint.config.jsis excluded by none and included by noneindex.htmlis excluded by none and included by nonepackage.jsonis excluded by none and included by nonepnpm-lock.yamlis excluded by!**/pnpm-lock.yaml,!pnpm-lock.yamland included by nonevite.config.tsis excluded by none and included by none
📒 Files selected for processing (32)
src/api/bookings.tssrc/api/owner/reservation.tssrc/api/owner/table.tssrc/components/customer-support/SupportModal.tsxsrc/components/map/KakaoMap.tsxsrc/components/owner/BreakTimeModal.tsxsrc/components/owner/StoreSettings.tsxsrc/components/owner/TableCreateModal.tsxsrc/components/reservation/modals/ReservationMenuModal.tsxsrc/components/reservation/modals/ReservationModal.tsxsrc/components/restaurant/RestaurantCard.tsxsrc/components/restaurant/RestaurantList.tsxsrc/components/store-registration/RegistrationStepper.tsxsrc/components/ui/button.tsxsrc/components/ui/calendar.tsxsrc/components/ui/checkbox.tsxsrc/components/ui/dialog.tsxsrc/components/ui/input.tsxsrc/components/ui/label.tsxsrc/components/ui/popover.tsxsrc/components/ui/separator.tsxsrc/hooks/common/useInView.tssrc/hooks/common/useModalPresence.tssrc/index.csssrc/layouts/PublicLayout.tsxsrc/lib/utils.tssrc/main.tsxsrc/pages/SearchPage.tsxsrc/pages/myPage/MyInfoPage.tsxsrc/pages/myPage/reservationPage.tsxsrc/pages/myPage/settingPage.tsxsrc/types/api.ts
💤 Files with no reviewable changes (4)
- src/components/owner/StoreSettings.tsx
- src/components/customer-support/SupportModal.tsx
- src/types/api.ts
- src/components/map/KakaoMap.tsx
✅ Files skipped from review due to trivial changes (17)
- src/lib/utils.ts
- src/components/restaurant/RestaurantCard.tsx
- src/index.css
- src/components/ui/dialog.tsx
- src/main.tsx
- src/components/ui/input.tsx
- src/components/ui/button.tsx
- src/components/ui/popover.tsx
- src/components/ui/checkbox.tsx
- src/components/store-registration/RegistrationStepper.tsx
- src/api/owner/table.ts
- src/api/bookings.ts
- src/components/ui/label.tsx
- src/components/ui/separator.tsx
- src/pages/SearchPage.tsx
- src/pages/myPage/reservationPage.tsx
- src/components/restaurant/RestaurantList.tsx
🚧 Files skipped from review as they are similar to previous changes (7)
- src/components/reservation/modals/ReservationMenuModal.tsx
- src/hooks/common/useInView.ts
- src/pages/myPage/settingPage.tsx
- src/components/owner/TableCreateModal.tsx
- src/hooks/common/useModalPresence.ts
- src/api/owner/reservation.ts
- src/components/ui/calendar.tsx
* [Docs/#118] Issue/PR 템플릿 수정 (프로젝트 재정비) (#119) * docs: PR 템플릿 수정 * docs: issue/PR template 수정 * docs: 템플릿 수정에 맞게 Readme변경 * refactor: 사용되지 않는 파일 삭제 * refactor: 미사용 ProfileAvatar 컴포넌트를 MyInfoPage에 적용 * refactor: 사용되지 않는 패키지 삭제 * refactor: 사용되지 않는 export 정리 * 사용되지 않는 함수 toRestaurantSummary 삭제 * refactor: 좌석 타입 라벨을 table.ts 공통 상수로 통합 * refactor: 사용되지 않는 export 타입, 인터페이스 정리 * refactor: 사용되지 않는 타입, 인터페이스 삭제 * [Setting/#121] ESLint/Prettier 설정 및 useEffect 비동기 랜더링 에러 수정 (#123) * setting: ESLint/Prettier 설정 및 useEffect 비동기 랜더링 에러 수정 * fix: vercel build에러 수정 * fix: 코드래빗 일부수정 * fix: 코드래빗 수정사항 반영 * fix: lint error 수정 (#126) * [Setting/#124] Husky 및 lint-staged 기반 pre-commit 자동 검사 환경 설정 (#127) * fix: lint error 수정 * setting: Husky 도입 및 pre-commit lint 검사 설정 * setting: lint-staged 및 자동 포맷 검사 설정 * fix: coderabbit 반영 * fix: coderabbit 수정사항 반영 * [Setting/#128] GitHub Actions CI 환경설정 추가 (#129) * setting: GitHub Actions CI 설정 추가 * fix: GitHub Actions pnpm 버전 중복 지정으로 인한 오류 수정 * fix: node버전 기존 24에서 22로 수정 * [Setting/#130] ESLint warning 정리 및 any 타입 제거 리팩토링 (#133) * fix:any타입 에러 수정중 * fix: lint 에러 수정중(중간저장목적) * fix: lint에러 수정중(중간저장목적) * fix: lint에러 수정중 중간 저장 * fix: lint에러 수정중 중간점검 * fix: lint 에러 수정완료 * fix: build 에러 수정 * fix: 코드래빗 수정사항 반영 * fix: 화면 오류확인해서 삭제했던 globals.css 복구 * fix: 코드래빗 수정사항 반영 * fix: 예약금결제 스크롤 에러 수정 (#135) --------- Co-authored-by: Dew <eidnwq@gmail.com>
🔢 관련 이슈 링크
📌 변경사항PR
💻 작업내용
Lint/Formatter 도입
최신 ESLint 및 Prettier 설정을 완료해서 팀 내 코드 컨벤션을 통일했습니다
VSCode 자동화
.vscode/setting.json설정을 통해 저장시에 자동 포맷팅 및 Lint Fix가 동작하도록 구성했습니다랜더링 최적화
데이터 동기화 방식 개선
useEffect대신 useState의 Lazy Initializer를 사용해서 불필요한 리랜더링을 방지했습니다
🪧 미완성 작업
타입 안정성 고도화 : 지금 Lint Warning으로 분류된 91개 any타입을 각각 구체적인 Interface나 type으로 변경작업 필요합니다.
🤔 논의 사항 및 참고 사항
✅ 체크리스트
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Style