[REFACTOR] 컴포넌트 구조 정리 - #221
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthrough여러 공유 컴포넌트(Footer, BottomSheet, ChooseWeek 등 9개)를 삭제하고, 해당 로직과 UI를 각 페이지 파일로 직접 이동하여 통합하는 리팩토링. 모듈식 아키텍처에서 페이지 기반 자체 포함 구조로 전환. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). 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: 16
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
src/pages/meal-plan/meal-plan-create-page.tsx (4)
41-42: 🛠️ Refactor suggestion | 🟠 Major상수 네이밍을
UPPER_SNAKE_CASE로 통일해 주세요.Line 41의
regenerate, Line 167의week, Line 238의dayKor는 상수 규칙과 불일치합니다.수정 예시
-const regenerate = true; +const REGENERATE = true; @@ -const week: { label: string; value: DayOfWeek }[] = [ +const WEEK_DAYS: { label: string; value: DayOfWeek }[] = [ @@ -const dayKor: Record<string, string> = { +const DAY_KOR: Record<string, string> = {- regenerate: regenerate, + regenerate: REGENERATE, @@ - {week.map((day, idx) => ( + {WEEK_DAYS.map((day, idx) => ( @@ - const date = dayKor[day] ?? day; + const date = DAY_KOR[day] ?? day;As per coding guidelines
Constants must be UPPER_SNAKE_CASE.Also applies to: 167-175, 238-246
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/meal-plan/meal-plan-create-page.tsx` around lines 41 - 42, Rename the lowercase constants to UPPER_SNAKE_CASE and update all their usages: change regenerate -> REGENERATE, week -> WEEK (or WEEK_CONST if more descriptive), and dayKor -> DAY_KOR (or DAY_KOREAN) wherever referenced in meal-plan-create-page component; ensure you update imports/exports, any JSX/handler references, and tests so identifiers match exactly and run a quick build/grep to catch remaining occurrences.
44-57:⚠️ Potential issue | 🟠 Major결과 단계 전환 시점이 너무 이릅니다.
Line 44에서 먼저
setStep("result")를 호출해, Line 54~56에서familyRoomId가 없을 때 조기 종료되면 결과 화면 상태로 남을 수 있습니다. 검증/생성 성공 이후에 단계 전환하세요.수정 예시
const handleCreate = async () => { - if (step === "create") setStep("result"); + if (familyRoomId == null) { + toast.error("가족방 정보를 찾을 수 없어요."); + return; + } + + if (step === "create") setStep("result"); const body: CreateMealPlan = { weekStartDate: weekParam === "THIS" ? getThisMonday() : getNextMonday(), selectedSlots: [...lunchSlots, ...dinnerSlots], regenerate: regenerate, }; setIsLoading(true); //로딩 시작 try { - if (familyRoomId == null) { - alert("familyRoomId 없음"); - return; - } const response = await postCreateMealPlans({ familyRoomId: familyRoomId, createMeal: body, });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/meal-plan/meal-plan-create-page.tsx` around lines 44 - 57, The call to setStep("result") is happening too early and can leave the UI in the result state if validation fails (e.g., when familyRoomId is null); move the setStep("result") invocation to after successful validation and/or after the meal-plan creation completes (i.e., after you check familyRoomId and after the try block succeeds). Specifically, keep the body construction (CreateMealPlan), setIsLoading(true), and the familyRoomId null check (familyRoomId == null) before changing step, and only call setStep("result") once creation succeeds (inside the try after the API/create call and before setIsLoading(false) in the success path).
71-76: 🧹 Nitpick | 🔵 Trivial
catch타입을unknown으로 고정해 타입 안정성을 지켜주세요.Line 71의
error: any와 Line 280의 암묵적 타입 처리는 타입 안전성을 낮춥니다. 에러를unknown으로 받고 좁혀서 메시지를 추출하세요.수정 예시
- } catch (error: any) { - if (error.response?.data?.code === "MEAL_PLAN_409") { + } catch (error: unknown) { + const axiosError = error as { response?: { data?: { code?: string; message?: string } } }; + if (axiosError.response?.data?.code === "MEAL_PLAN_409") { toast.error("이미 생성된 다음주 식단이 있어요"); } else { - toast.error(error.response?.data?.message); + toast.error(axiosError.response?.data?.message ?? "식단 생성에 실패했어요."); } @@ - } catch (error) { - alert("주간 식단 확정 실패" + error); + } catch (_error: unknown) { + toast.error("주간 식단 확정에 실패했어요."); }As per coding guidelines
Focus on: Strict TypeScript typing.Also applies to: 280-282
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/meal-plan/meal-plan-create-page.tsx` around lines 71 - 76, Change both catch clauses in meal-plan-create-page.tsx from catch(error: any) to catch(error: unknown) and narrow the type before accessing response/data; implement a small type guard (e.g., isAxiosError or isErrorWithResponse) to check that error is an object with response?.data?.code and response?.data?.message, then handle the "MEAL_PLAN_409" branch and fallback message using the safely extracted fields; ensure a final fallback toast.error for unknown errors so no any casts are used when reading error.response.data.
28-28: 🧹 Nitpick | 🔵 TrivialZustand 구독 범위를 줄여 불필요한 리렌더를 방지하세요.
Line 28의
useProfileStore().isLeader는 스토어 전체 구독 경로가 되어 리렌더가 늘어날 수 있습니다. selector 형태로 바꾸는 편이 안전합니다.수정 예시
- const isLeader = useProfileStore().isLeader; + const isLeader = useProfileStore((state) => state.isLeader);As per coding guidelines
Focus on: Zustand state management correctnessandAvoid unnecessary re-renders.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/meal-plan/meal-plan-create-page.tsx` at line 28, The component directly calls useProfileStore().isLeader which subscribes to the whole store and can cause unnecessary re-renders; change this to use a selector form such as const isLeader = useProfileStore(state => state.isLeader) so the component only subscribes to the isLeader slice (locate the usage of useProfileStore and the isLeader reference in meal-plan-create-page and replace the direct property access with a selector callback).src/pages/meal-plan/meal-plan-page.tsx (1)
40-46:⚠️ Potential issue | 🟠 MajorReact 컴포넌트 내
getState()직접 호출로 Zustand 구독 메커니즘이 작동하지 않습니다Line 40에서
useFamilyStore.getState()를 사용하면 스냅샷만 반환되고 구독이 이루어지지 않습니다.familyRoomId가 변경되어도 컴포넌트가 리렌더링되지 않아 Line 46의useGetTodayMealPlan훅이 이전 값을 계속 사용할 수 있습니다.🔧 제안 수정
- const { familyRoomId } = useFamilyStore.getState(); + const familyRoomId = useFamilyStore((state) => state.familyRoomId);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/meal-plan/meal-plan-page.tsx` around lines 40 - 46, You're using useFamilyStore.getState() which returns a snapshot and does not subscribe, so replace that call with the subscribing selector form (call useFamilyStore(state => state.familyRoomId)) so the component re-renders when familyRoomId changes and useGetTodayMealPlan receives the updated id; remove the getState() usage, pass the subscribed familyRoomId into useGetTodayMealPlan, and optionally guard useGetTodayMealPlan calls for undefined/nullable familyRoomId in the component.
🤖 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/mobile-layout.tsx`:
- Around line 27-30: The three JSX <button> elements in mobile-layout.tsx (the
one with onClick={() => navigate("/")} and the other two buttons around lines
referenced) lack an explicit type and can unintentionally submit surrounding
forms; update each button element to include type="button" to prevent default
form submit behavior while preserving their onClick handlers.
- Line 23: The background <img> using the Background asset in the mobile layout
is decorative and should be hidden from assistive tech; update the <img> element
that renders Background (in src/layouts/mobile-layout.tsx) to include alt="" and
aria-hidden="true" so screen readers ignore it (locate the img that currently
has src={Background} and replace its attributes accordingly).
- Around line 10-12: Rename the constant footerPaths to FOOTER_PATHS and move
its declaration out of the component scope to avoid repeated creation; update
any usages (e.g., the showFooter computation that uses
footerPaths.includes(location.pathname)) to reference FOOTER_PATHS instead,
ensuring the constant follows UPPER_SNAKE_CASE and is declared at module level.
In `@src/pages/meal-plan/meal-plan-create-page.tsx`:
- Around line 307-310: The three plain <button> elements (the one with
onClick={onClick} shown in the diff and the other buttons around the same area)
are missing an explicit type, causing accessibility/lint errors; update each
button element to include type="button" so they don't default to type="submit"
(locate the button with onClick={onClick} and the other sibling buttons in the
meal-plan-create-page component and add type="button" to each).
- Around line 280-288: The current finally block always calls
navigate(`/meal-plan?tab=...`) even when the API call failed, causing navigation
on error; modify the control flow in the function containing the
try/catch/finally (the block that calls the API and uses weekParam and navigate)
so that navigation is performed only on success—move the navigate(...) calls out
of finally and into the try after the successful API response (or set a success
flag and check it before navigating); ensure the catch shows the alert("주간 식단 확정
실패" + error) and returns/keeps the user on the current page so they can retry,
while keeping weekParam and navigate references unchanged.
- Around line 257-262: The component currently calls toast.error during render
when sessionStorage.getItem("mealPlan") is falsy; change the early return to
explicitly "return null" and move the toast.error call into a useEffect that
runs when the local "response" (or session value) changes and is falsy (and only
when not isLoading), so the side effect is not executed during render (modify
the component where response is read and add a useEffect tied to
response/isLoading to show the toast instead of invoking toast.error inline).
In `@src/pages/meal-plan/meal-plan-edit-page.tsx`:
- Around line 261-269: The infinite-scroll trigger (useInView ref and inView
effect) should be limited to the Wishlist tab to avoid unnecessary fetchNextPage
calls from the Recommend tab; update the component so the ref from useInView is
only attached/rendered when the active tab is the Wishlist, and keep the
existing useEffect that watches inView, hasNextPage, isFetching, and
fetchNextPage but it will only run when the ref exists; locate the useInView
invocation and the effect (symbols: useInView, ref, inView, useEffect,
hasNextPage, isFetching, fetchNextPage) and conditionally render/attach ref
based on the selected tab (e.g., only when tab === 'wishlist').
- Line 253: The code uses useFamilyStore.getState().familyRoomId (and a similar
call in the parent component) which is non-reactive; change both usages to
subscribe to the Zustand store by using the hook selector form (e.g.,
useFamilyStore(state => state.familyRoomId)) so the component re-renders when
familyRoomId changes, and remove or replace any other direct getState() reads in
these components with selector-based hooks.
- Around line 330-355: The three <button> elements shown (the toggle button that
calls setIsOpen and the two tab buttons that call setTab with "common" and
"recommend") are missing explicit type attributes; add type="button" to each of
these buttons (the Chevron toggle button and both tab buttons) to prevent the
default "submit" behavior inside forms and avoid unintended form submission.
- Around line 294-301: The current handleButton calls navigate() before the
async onSubmit completes, causing navigation prior to API completion; update
handleButton to await the submission by making it async and calling await
onSubmit() (or ensure the onSubmit prop is typed as returning Promise<void>),
then perform navigate(...) after the await; alternatively move navigation
responsibility to the parent so handleButton only calls onSubmit; reference the
handleButton function and the onSubmit prop and ensure navigate and weekParam
logic remains the same but executes only after the awaited onSubmit resolves.
- Line 24: 타입 별칭 mealPlanResponse이 소문자 카멜케이스로 되어 있으니 PascalCase로 변경하세요: type
MealPlanResponse = Record<string, SlotItem[]>; 그리고 이 타입을 참조하는 모든 위치(예: 현재 파일의
사용처인 식별자 참조(사용된 곳은 현재 파일의 line 33 근처))를 MealPlanResponse로 업데이트해 타입 정의와 사용이 일치하도록
만드세요.
In `@src/pages/meal-plan/meal-plan-page.tsx`:
- Around line 325-331: Rename the non-camelCase function handlebutton to
handleButton and update all its references/exports/usages accordingly; also scan
the related block around the other occurrence noted (lines 396-405) and rename
any other functions or variables that use snake/lowercase-without-camel (e.g.,
change xyzFunctionName) to camelCase, updating all callers and tests to maintain
consistency with the project's naming convention.
- Around line 285-304: The map callbacks for futureDays and pastDays sometimes
return nothing causing a lint error; change each usage to first filter out days
with empty meals (e.g., futureDays.filter(d => d.meals.length > 0)) and then map
the filtered array to render the JSX, keeping the existing render logic that
uses CalendarChipS and DateMenuList (preserve isSelect values: true for
futureDays, false for pastDays) so every map callback always returns an element.
- Around line 373-405: Add explicit type="button" to all standalone buttons in
this modal to prevent accidental form submission: the close button that invokes
onClick, each star-rating button that calls setScore, and the two preference
buttons that call handlebutton(true/false). Locate the button elements in the
modal JSX (the one using onClick={onClick}, the star map that calls
setScore(idx), and the preference buttons that call handlebutton) and add
type="button" to each.
- Around line 236-237: The weekly header calculation using listHeaderDate
currently concatenates substrings and adds 6 to the day string (date + "~" +
date[5] + date[6] + "." + (Number(date[8] + date[9]) + 6)), which yields invalid
days at month/ year boundaries; change it to parse the input date into a Date
object (or use an existing date util), add 6 days via date arithmetic (e.g.,
create Date from date, setDate(getDate() + 6)), then format the resulting month
and day with zero-padding as needed and build listHeaderDate from the original
date string and the correctly computed end date; update code paths that
reference listHeaderDate accordingly.
- Around line 340-357: Move the score validation out of the mutate onError path
and perform it at the start of handleSubmitReview so you never call mutate({
recipeId, score, isFavorite, type }) with an invalid score; if score is falsy,
call toast.error("별을 클릭해주세요") and return before mutate. Change the onError
handler signature from (e: any) to (e: AxiosError<{ message?: string }>) and use
e.response?.data?.message ?? "요청에 실패했습니다" when calling toast.error to provide a
fallback message; keep existing calls to setIsOpen/onClick as they are but
ensure onClick() remains only in the error branch after logging the error.
---
Outside diff comments:
In `@src/pages/meal-plan/meal-plan-create-page.tsx`:
- Around line 41-42: Rename the lowercase constants to UPPER_SNAKE_CASE and
update all their usages: change regenerate -> REGENERATE, week -> WEEK (or
WEEK_CONST if more descriptive), and dayKor -> DAY_KOR (or DAY_KOREAN) wherever
referenced in meal-plan-create-page component; ensure you update
imports/exports, any JSX/handler references, and tests so identifiers match
exactly and run a quick build/grep to catch remaining occurrences.
- Around line 44-57: The call to setStep("result") is happening too early and
can leave the UI in the result state if validation fails (e.g., when
familyRoomId is null); move the setStep("result") invocation to after successful
validation and/or after the meal-plan creation completes (i.e., after you check
familyRoomId and after the try block succeeds). Specifically, keep the body
construction (CreateMealPlan), setIsLoading(true), and the familyRoomId null
check (familyRoomId == null) before changing step, and only call
setStep("result") once creation succeeds (inside the try after the API/create
call and before setIsLoading(false) in the success path).
- Around line 71-76: Change both catch clauses in meal-plan-create-page.tsx from
catch(error: any) to catch(error: unknown) and narrow the type before accessing
response/data; implement a small type guard (e.g., isAxiosError or
isErrorWithResponse) to check that error is an object with response?.data?.code
and response?.data?.message, then handle the "MEAL_PLAN_409" branch and fallback
message using the safely extracted fields; ensure a final fallback toast.error
for unknown errors so no any casts are used when reading error.response.data.
- Line 28: The component directly calls useProfileStore().isLeader which
subscribes to the whole store and can cause unnecessary re-renders; change this
to use a selector form such as const isLeader = useProfileStore(state =>
state.isLeader) so the component only subscribes to the isLeader slice (locate
the usage of useProfileStore and the isLeader reference in meal-plan-create-page
and replace the direct property access with a selector callback).
In `@src/pages/meal-plan/meal-plan-page.tsx`:
- Around line 40-46: You're using useFamilyStore.getState() which returns a
snapshot and does not subscribe, so replace that call with the subscribing
selector form (call useFamilyStore(state => state.familyRoomId)) so the
component re-renders when familyRoomId changes and useGetTodayMealPlan receives
the updated id; remove the getState() usage, pass the subscribed familyRoomId
into useGetTodayMealPlan, and optionally guard useGetTodayMealPlan calls for
undefined/nullable familyRoomId in the component.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 9c3bed44-ef73-440c-a88e-bdaa40012ed6
📒 Files selected for processing (16)
src/components/chip/CalendarChip/CalendarChipM.tsxsrc/components/chip/CalendarChip/CalendarChipS.tsxsrc/components/chip/MenuChip.tsxsrc/components/common/Footer.tsxsrc/components/meal-plan/BottomSheet.tsxsrc/components/meal-plan/ChooseWeek.tsxsrc/components/meal-plan/ListHeader.tsxsrc/components/meal-plan/MealPlanResult.tsxsrc/components/meal-plan/MemberMealPlanView.tsxsrc/components/meal-plan/ReviewModal.tsxsrc/components/meal-plan/TodayMeal.tsxsrc/components/meal-plan/WeekMeal.tsxsrc/layouts/mobile-layout.tsxsrc/pages/meal-plan/meal-plan-create-page.tsxsrc/pages/meal-plan/meal-plan-edit-page.tsxsrc/pages/meal-plan/meal-plan-page.tsx
💤 Files with no reviewable changes (9)
- src/components/meal-plan/WeekMeal.tsx
- src/components/meal-plan/ReviewModal.tsx
- src/components/meal-plan/ChooseWeek.tsx
- src/components/meal-plan/MemberMealPlanView.tsx
- src/components/meal-plan/TodayMeal.tsx
- src/components/common/Footer.tsx
- src/components/meal-plan/ListHeader.tsx
- src/components/meal-plan/MealPlanResult.tsx
- src/components/meal-plan/BottomSheet.tsx
| const footerPaths = ["/", "/meal-plan"]; | ||
|
|
||
| const showFooter = footerPaths.includes(location.pathname); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
상수 네이밍을 가이드에 맞게 UPPER_SNAKE_CASE로 맞춰주세요.
footerPaths는 상수 성격이므로 FOOTER_PATHS로 변경하는 게 규칙에 맞고, 컴포넌트 바깥으로 올리면 불필요한 재생성도 줄일 수 있습니다.
♻️ Proposed refactor
+const FOOTER_PATHS = ["/", "/meal-plan"];
+
export default function MobileLayout() {
const location = useLocation();
- const footerPaths = ["/", "/meal-plan"];
-
- const showFooter = footerPaths.includes(location.pathname);
+ const showFooter = FOOTER_PATHS.includes(location.pathname);As per coding guidelines, Constants must be UPPER_SNAKE_CASE.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/layouts/mobile-layout.tsx` around lines 10 - 12, Rename the constant
footerPaths to FOOTER_PATHS and move its declaration out of the component scope
to avoid repeated creation; update any usages (e.g., the showFooter computation
that uses footerPaths.includes(location.pathname)) to reference FOOTER_PATHS
instead, ensuring the constant follows UPPER_SNAKE_CASE and is declared at
module level.
| {showFooter && ( | ||
| <div className="fixed bottom-0 left-1/2 -translate-x-1/2 w-full max-w-[400px] text-[10px] font-medium z-10 text-center"> | ||
| <div className="relative"> | ||
| <img src={Background} className="w-full" alt="푸터 배경" /> |
There was a problem hiding this comment.
장식용 이미지는 스크린리더에서 제외해주세요.
Line 23의 배경 이미지는 정보 전달용이 아니므로 alt=""와 aria-hidden="true"가 적절합니다.
♿ Proposed fix
- <img src={Background} className="w-full" alt="푸터 배경" />
+ <img src={Background} className="w-full" alt="" aria-hidden="true" />📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <img src={Background} className="w-full" alt="푸터 배경" /> | |
| <img src={Background} className="w-full" alt="" aria-hidden="true" /> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/layouts/mobile-layout.tsx` at line 23, The background <img> using the
Background asset in the mobile layout is decorative and should be hidden from
assistive tech; update the <img> element that renders Background (in
src/layouts/mobile-layout.tsx) to include alt="" and aria-hidden="true" so
screen readers ignore it (locate the img that currently has src={Background} and
replace its attributes accordingly).
| <button | ||
| className="flex flex-col cursor-pointer gap-2" | ||
| onClick={() => navigate("/")} | ||
| > |
There was a problem hiding this comment.
button 기본 submit 동작을 명시적으로 차단해주세요.
Line 27, Line 37, Line 47의 버튼 모두 type이 없어, 폼 컨텍스트에서 의도치 않은 submit이 발생할 수 있습니다(정적 분석 에러와 동일).
🔧 Proposed fix
<button
+ type="button"
className="flex flex-col cursor-pointer gap-2"
onClick={() => navigate("/")}
>
@@
<button
+ type="button"
className="flex flex-col items-center cursor-pointer gap-2"
onClick={() => navigate("/meal-plan")}
>
@@
<button
+ type="button"
className="
absolute left-1/2 -translate-x-1/2 bottom-[clamp(32px,calc(56/400*100vw),56px)]
flex flex-col justify-center items-center gap-1 Also applies to: 37-40, 47-55
🧰 Tools
🪛 Biome (2.4.4)
[error] 27-30: Provide an explicit type prop for the button element.
(lint/a11y/useButtonType)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/layouts/mobile-layout.tsx` around lines 27 - 30, The three JSX <button>
elements in mobile-layout.tsx (the one with onClick={() => navigate("/")} and
the other two buttons around lines referenced) lack an explicit type and can
unintentionally submit surrounding forms; update each button element to include
type="button" to prevent default form submit behavior while preserving their
onClick handlers.
| const response = sessionStorage.getItem("mealPlan"); | ||
| if (isLoading) return null; | ||
| if (!response) { | ||
| toast.error("올바른 접근이 아닙니다. 식단 생성부터 해주세요!"); | ||
| return; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
fd "meal-plan-create-page.tsx" --type fRepository: Urisik-serv/URISIK_FRONTEND
Length of output: 116
🏁 Script executed:
cat -n src/pages/meal-plan/meal-plan-create-page.tsx | head -280 | tail -40Repository: Urisik-serv/URISIK_FRONTEND
Length of output: 1237
🏁 Script executed:
wc -l src/pages/meal-plan/meal-plan-create-page.tsxRepository: Urisik-serv/URISIK_FRONTEND
Length of output: 120
🏁 Script executed:
cat -n src/pages/meal-plan/meal-plan-create-page.tsx | head -100Repository: Urisik-serv/URISIK_FRONTEND
Length of output: 4362
🏁 Script executed:
cat -n src/pages/meal-plan/meal-plan-create-page.tsx | sed -n '248,300p'Repository: Urisik-serv/URISIK_FRONTEND
Length of output: 1754
🏁 Script executed:
cat -n src/pages/meal-plan/meal-plan-create-page.tsx | sed -n '248,320p'Repository: Urisik-serv/URISIK_FRONTEND
Length of output: 2738
🏁 Script executed:
rg "sessionStorage.getItem|sessionStorage.setItem" src/pages/meal-plan/meal-plan-create-page.tsx -nRepository: Urisik-serv/URISIK_FRONTEND
Length of output: 195
🏁 Script executed:
cat -n src/pages/meal-plan/meal-plan-create-page.tsx | sed -n '43,82p'Repository: Urisik-serv/URISIK_FRONTEND
Length of output: 1507
렌더링 중 부수효과를 실행하고 있습니다.
Line 260의 toast.error는 렌더링 중 부수효과라 Strict Mode에서 중복 실행될 수 있습니다. Line 261의 return을 return null로 명시하고, 알림은 useEffect로 분리하세요.
수정 예시
- const response = sessionStorage.getItem("mealPlan");
- if (isLoading) return null;
- if (!response) {
- toast.error("올바른 접근이 아닙니다. 식단 생성부터 해주세요!");
- return;
- }
+ const response = sessionStorage.getItem("mealPlan");
+ if (isLoading) return null;
+ if (!response) return null;
+
+ useEffect(() => {
+ if (!response) {
+ toast.error("올바른 접근이 아닙니다. 식단 생성부터 해주세요!");
+ navigate("/meal-plan/create");
+ }
+ }, [response, navigate]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/meal-plan/meal-plan-create-page.tsx` around lines 257 - 262, The
component currently calls toast.error during render when
sessionStorage.getItem("mealPlan") is falsy; change the early return to
explicitly "return null" and move the toast.error call into a useEffect that
runs when the local "response" (or session value) changes and is falsy (and only
when not isLoading), so the side effect is not executed during render (modify
the component where response is read and add a useEffect tied to
response/isLoading to show the toast instead of invoking toast.error inline).
| } catch (error) { | ||
| alert("주간 식단 확정 실패" + error); | ||
| } finally { | ||
| if (weekParam === "THIS") { | ||
| navigate(`/meal-plan?tab=THIS`); | ||
| } else { | ||
| navigate(`/meal-plan?tab=NEXT`); | ||
| } | ||
| } |
There was a problem hiding this comment.
확정 실패 시에도 성공 경로로 이동합니다.
Line 283~287이 finally에 있어 API 실패여도 탭 화면으로 이동합니다. 성공 시에만 이동하고, 실패 시 현재 화면 유지 후 재시도 가능하게 처리하는 게 안전합니다.
수정 예시
const handleClick = async () => {
try {
if (familyRoomId == null) {
toast.error("familyRoomId 없음");
return;
}
await postConfirmMealPlan({
familyRoomId: familyRoomId,
mealPlanId: mealPlanId,
});
+ if (weekParam === "THIS") {
+ navigate(`/meal-plan?tab=THIS`);
+ } else {
+ navigate(`/meal-plan?tab=NEXT`);
+ }
} catch (error) {
- alert("주간 식단 확정 실패" + error);
- } finally {
- if (weekParam === "THIS") {
- navigate(`/meal-plan?tab=THIS`);
- } else {
- navigate(`/meal-plan?tab=NEXT`);
- }
+ toast.error("주간 식단 확정에 실패했어요. 잠시 후 다시 시도해주세요.");
}
};🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/meal-plan/meal-plan-create-page.tsx` around lines 280 - 288, The
current finally block always calls navigate(`/meal-plan?tab=...`) even when the
API call failed, causing navigation on error; modify the control flow in the
function containing the try/catch/finally (the block that calls the API and uses
weekParam and navigate) so that navigation is performed only on success—move the
navigate(...) calls out of finally and into the try after the successful API
response (or set a success flag and check it before navigating); ensure the
catch shows the alert("주간 식단 확정 실패" + error) and returns/keeps the user on the
current page so they can retry, while keeping weekParam and navigate references
unchanged.
| const listHeaderDate = | ||
| date + "~" + date[5] + date[6] + "." + (Number(date[8] + date[9]) + 6); |
There was a problem hiding this comment.
주간 헤더 날짜 계산이 월말/연말에서 잘못됩니다
Line 237은 문자열 일자에 +6을 하는 방식이라 03.36 같은 잘못된 날짜를 만들 수 있습니다.
🔧 제안 수정
- const listHeaderDate =
- date + "~" + date[5] + date[6] + "." + (Number(date[8] + date[9]) + 6);
+ const startDate = new Date(date);
+ const endDate = new Date(startDate);
+ endDate.setDate(startDate.getDate() + 6);
+ const endMonth = String(endDate.getMonth() + 1).padStart(2, "0");
+ const endDay = String(endDate.getDate()).padStart(2, "0");
+ const listHeaderDate = `${date}~${endMonth}.${endDay}`;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const listHeaderDate = | |
| date + "~" + date[5] + date[6] + "." + (Number(date[8] + date[9]) + 6); | |
| const startDate = new Date(date); | |
| const endDate = new Date(startDate); | |
| endDate.setDate(startDate.getDate() + 6); | |
| const endMonth = String(endDate.getMonth() + 1).padStart(2, "0"); | |
| const endDay = String(endDate.getDate()).padStart(2, "0"); | |
| const listHeaderDate = `${date}~${endMonth}.${endDay}`; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/meal-plan/meal-plan-page.tsx` around lines 236 - 237, The weekly
header calculation using listHeaderDate currently concatenates substrings and
adds 6 to the day string (date + "~" + date[5] + date[6] + "." + (Number(date[8]
+ date[9]) + 6)), which yields invalid days at month/ year boundaries; change it
to parse the input date into a Date object (or use an existing date util), add 6
days via date arithmetic (e.g., create Date from date, setDate(getDate() + 6)),
then format the resulting month and day with zero-padding as needed and build
listHeaderDate from the original date string and the correctly computed end
date; update code paths that reference listHeaderDate accordingly.
| {futureDays.map((day) => { | ||
| if (day.meals.length > 0) { | ||
| return ( | ||
| <div key={day.dayKor} className="flex gap-3"> | ||
| <CalendarChipS text={day.dayKor} type="primary" /> | ||
| <DateMenuList isSelect={true} data={day.meals} /> | ||
| </div> | ||
| ); | ||
| } | ||
| })} | ||
| {pastDays.map((day) => { | ||
| if (day.meals.length > 0) { | ||
| return ( | ||
| <div key={day.dayKor} className="flex gap-3"> | ||
| <CalendarChipS text={day.dayKor} type="gray" /> | ||
| <DateMenuList isSelect={false} data={day.meals} /> | ||
| </div> | ||
| ); | ||
| } | ||
| })} |
There was a problem hiding this comment.
map() 콜백이 일부 분기에서 값을 반환하지 않아 lint 에러가 발생합니다
Line 285, Line 295의 map 콜백은 조건 불일치 시 반환값이 없습니다. filter + map으로 분리해 명시적으로 반환해 주세요.
🔧 제안 수정
- {futureDays.map((day) => {
- if (day.meals.length > 0) {
- return (
- <div key={day.dayKor} className="flex gap-3">
- <CalendarChipS text={day.dayKor} type="primary" />
- <DateMenuList isSelect={true} data={day.meals} />
- </div>
- );
- }
- })}
- {pastDays.map((day) => {
- if (day.meals.length > 0) {
- return (
- <div key={day.dayKor} className="flex gap-3">
- <CalendarChipS text={day.dayKor} type="gray" />
- <DateMenuList isSelect={false} data={day.meals} />
- </div>
- );
- }
- })}
+ {futureDays
+ .filter((day) => day.meals.length > 0)
+ .map((day) => (
+ <div key={day.dayKor} className="flex gap-3">
+ <CalendarChipS text={day.dayKor} type="primary" />
+ <DateMenuList isSelect={true} data={day.meals} />
+ </div>
+ ))}
+ {pastDays
+ .filter((day) => day.meals.length > 0)
+ .map((day) => (
+ <div key={day.dayKor} className="flex gap-3">
+ <CalendarChipS text={day.dayKor} type="gray" />
+ <DateMenuList isSelect={false} data={day.meals} />
+ </div>
+ ))}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {futureDays.map((day) => { | |
| if (day.meals.length > 0) { | |
| return ( | |
| <div key={day.dayKor} className="flex gap-3"> | |
| <CalendarChipS text={day.dayKor} type="primary" /> | |
| <DateMenuList isSelect={true} data={day.meals} /> | |
| </div> | |
| ); | |
| } | |
| })} | |
| {pastDays.map((day) => { | |
| if (day.meals.length > 0) { | |
| return ( | |
| <div key={day.dayKor} className="flex gap-3"> | |
| <CalendarChipS text={day.dayKor} type="gray" /> | |
| <DateMenuList isSelect={false} data={day.meals} /> | |
| </div> | |
| ); | |
| } | |
| })} | |
| {futureDays | |
| .filter((day) => day.meals.length > 0) | |
| .map((day) => ( | |
| <div key={day.dayKor} className="flex gap-3"> | |
| <CalendarChipS text={day.dayKor} type="primary" /> | |
| <DateMenuList isSelect={true} data={day.meals} /> | |
| </div> | |
| ))} | |
| {pastDays | |
| .filter((day) => day.meals.length > 0) | |
| .map((day) => ( | |
| <div key={day.dayKor} className="flex gap-3"> | |
| <CalendarChipS text={day.dayKor} type="gray" /> | |
| <DateMenuList isSelect={false} data={day.meals} /> | |
| </div> | |
| ))} |
🧰 Tools
🪛 Biome (2.4.4)
[error] 285-285: This callback passed to map() iterable method should always return a value.
(lint/suspicious/useIterableCallbackReturn)
[error] 295-295: This callback passed to map() iterable method should always return a value.
(lint/suspicious/useIterableCallbackReturn)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/meal-plan/meal-plan-page.tsx` around lines 285 - 304, The map
callbacks for futureDays and pastDays sometimes return nothing causing a lint
error; change each usage to first filter out days with empty meals (e.g.,
futureDays.filter(d => d.meals.length > 0)) and then map the filtered array to
render the JSX, keeping the existing render logic that uses CalendarChipS and
DateMenuList (preserve isSelect values: true for futureDays, false for pastDays)
so every map callback always returns an element.
| const handlebutton = (select: Preference) => { | ||
| if (select == preference) { | ||
| setPreference(null); | ||
| } else { | ||
| setPreference(select); | ||
| } | ||
| }; |
There was a problem hiding this comment.
함수명 handlebutton을 camelCase(handleButton)로 맞춰주세요
Line 325의 함수명은 팀 네이밍 규칙과 일관성이 떨어집니다.
🔧 제안 수정
- const handlebutton = (select: Preference) => {
+ const handleButton = (select: Preference) => {
@@
- onClick={() => handlebutton(false)}
+ onClick={() => handleButton(false)}
@@
- onClick={() => handlebutton(true)}
+ onClick={() => handleButton(true)}As per coding guidelines Variables and functions must use camelCase.
Also applies to: 396-405
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/meal-plan/meal-plan-page.tsx` around lines 325 - 331, Rename the
non-camelCase function handlebutton to handleButton and update all its
references/exports/usages accordingly; also scan the related block around the
other occurrence noted (lines 396-405) and rename any other functions or
variables that use snake/lowercase-without-camel (e.g., change xyzFunctionName)
to camelCase, updating all callers and tests to maintain consistency with the
project's naming convention.
| const handleSubmitReview = ({ | ||
| recipeId, | ||
| score, | ||
| isFavorite, | ||
| }: createReview) => { | ||
| mutate( | ||
| { recipeId, score, isFavorite, type }, | ||
| { | ||
| onSuccess: () => { | ||
| setIsOpen(true); | ||
| }, | ||
| onError: (e: any) => { | ||
| if (!score) { | ||
| toast.error("별을 클릭해주세요"); | ||
| return; | ||
| } | ||
| toast.error(e.response?.data?.message); | ||
| onClick(); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n src/pages/meal-plan/meal-plan-page.tsx | sed -n '330,365p'Repository: Urisik-serv/URISIK_FRONTEND
Length of output: 1023
🏁 Script executed:
# Check for any type usage in error handlers
rg -n 'onError.*any' src/pages/meal-plan/meal-plan-page.tsx -A 5Repository: Urisik-serv/URISIK_FRONTEND
Length of output: 269
🏁 Script executed:
# Check for AxiosError import in the file
rg -n 'import.*AxiosError|from.*axios' src/pages/meal-plan/meal-plan-page.tsxRepository: Urisik-serv/URISIK_FRONTEND
Length of output: 53
🏁 Script executed:
# Check the createReview type definition
rg -n 'createReview|interface.*Review|type.*Review' src/pages/meal-plan/meal-plan-page.tsx -B 2 -A 3Repository: Urisik-serv/URISIK_FRONTEND
Length of output: 1199
🏁 Script executed:
find src -name "*use-post-review*" -type fRepository: Urisik-serv/URISIK_FRONTEND
Length of output: 109
🏁 Script executed:
# Check the usePostReview hook definition
cat -n src/hooks/mutations/use-post-review.ts 2>/dev/null || cat -n src/hooks/mutations/use-post-review.tsxRepository: Urisik-serv/URISIK_FRONTEND
Length of output: 1243
🏁 Script executed:
# Check for other onError handlers with 'any' type in the file
rg -n 'onError.*:.*any' src/pages/meal-plan/meal-plan-page.tsxRepository: Urisik-serv/URISIK_FRONTEND
Length of output: 105
🏁 Script executed:
# Check what types are imported from axios or other API libraries
rg -n 'import.*from.*axios|import.*from.*api' src/pages/meal-plan/meal-plan-page.tsxRepository: Urisik-serv/URISIK_FRONTEND
Length of output: 53
🏁 Script executed:
# Check the postReview API function to understand error type
rg -n 'export.*postReview|function postReview' src/api/review.ts -A 10Repository: Urisik-serv/URISIK_FRONTEND
Length of output: 357
🏁 Script executed:
# Check axios configuration or API setup in the project
find src -name "*api*" -o -name "*axios*" | head -20Repository: Urisik-serv/URISIK_FRONTEND
Length of output: 115
🏁 Script executed:
# Check for TypeScript types related to API errors
rg -n 'AxiosError|Error.*type|interface.*Error' src/types/ -B 1 -A 2Repository: Urisik-serv/URISIK_FRONTEND
Length of output: 53
🏁 Script executed:
# Check axios configuration
cat -n src/api/axios/axios.tsRepository: Urisik-serv/URISIK_FRONTEND
Length of output: 1024
🏁 Script executed:
# Check what error types are used across the codebase
rg -n 'import.*AxiosError|from.*axios' src/ -A 2 | head -30Repository: Urisik-serv/URISIK_FRONTEND
Length of output: 1229
🏁 Script executed:
# Check the review types file
cat -n src/types/review.tsRepository: Urisik-serv/URISIK_FRONTEND
Length of output: 606
리뷰 제출 전 검증을 먼저 수행하고 에러 타입을 명시해야 합니다
345번 라인에서 mutate() 호출 시 score 검증이 선행되지 않아 유효하지 않은 요청이 전송되고 있으며, 351번 라인의 any 타입 사용으로 인한 타입 안정성 부재가 확인됩니다.
score검증을mutate()호출 전에 이동- 에러 핸들러의 타입을
AxiosError<{ message?: string }>로 명시 - 폴백 메시지 추가 권장
🔧 제안 수정
+import type { AxiosError } from "axios";
import type { createReview } from "../../types/review";
const handleSubmitReview = ({
recipeId,
score,
isFavorite,
}: createReview) => {
+ if (!score) {
+ toast.error("별을 클릭해주세요");
+ return;
+ }
+
mutate(
{ recipeId, score, isFavorite, type },
{
onSuccess: () => {
setIsOpen(true);
},
- onError: (e: any) => {
- if (!score) {
- toast.error("별을 클릭해주세요");
- return;
- }
- toast.error(e.response?.data?.message);
+ onError: (e: AxiosError<{ message?: string }>) => {
+ toast.error(e.response?.data?.message ?? "리뷰 등록에 실패했어요");
onClick();
},
},
);
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const handleSubmitReview = ({ | |
| recipeId, | |
| score, | |
| isFavorite, | |
| }: createReview) => { | |
| mutate( | |
| { recipeId, score, isFavorite, type }, | |
| { | |
| onSuccess: () => { | |
| setIsOpen(true); | |
| }, | |
| onError: (e: any) => { | |
| if (!score) { | |
| toast.error("별을 클릭해주세요"); | |
| return; | |
| } | |
| toast.error(e.response?.data?.message); | |
| onClick(); | |
| const handleSubmitReview = ({ | |
| recipeId, | |
| score, | |
| isFavorite, | |
| }: createReview) => { | |
| if (!score) { | |
| toast.error("별을 클릭해주세요"); | |
| return; | |
| } | |
| mutate( | |
| { recipeId, score, isFavorite, type }, | |
| { | |
| onSuccess: () => { | |
| setIsOpen(true); | |
| }, | |
| onError: (e: AxiosError<{ message?: string }>) => { | |
| toast.error(e.response?.data?.message ?? "리뷰 등록에 실패했어요"); | |
| onClick(); | |
| }, | |
| }, | |
| ); | |
| }; |
🧰 Tools
🪛 ESLint
[error] 351-351: Unexpected any. Specify a different type.
(@typescript-eslint/no-explicit-any)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/meal-plan/meal-plan-page.tsx` around lines 340 - 357, Move the
score validation out of the mutate onError path and perform it at the start of
handleSubmitReview so you never call mutate({ recipeId, score, isFavorite, type
}) with an invalid score; if score is falsy, call toast.error("별을 클릭해주세요") and
return before mutate. Change the onError handler signature from (e: any) to (e:
AxiosError<{ message?: string }>) and use e.response?.data?.message ?? "요청에
실패했습니다" when calling toast.error to provide a fallback message; keep existing
calls to setIsOpen/onClick as they are but ensure onClick() remains only in the
error branch after logging the error.
| <button | ||
| className="pt-[10px] cursor-pointer flex justify-center items-center" | ||
| onClick={onClick} | ||
| > | ||
| <img src={X} alt="닫기버튼" className="size-6" /> | ||
| </button> | ||
| <div className="font-semibold"> | ||
| <p className="text-[20px] pb-6">오늘의 메뉴는 어떠셨나요</p> | ||
| <div className="pb-4 flex justify-center"> | ||
| {star.map((idx) => ( | ||
| <button | ||
| className="size-[58px] flex justify-center items-center cursor-pointer" | ||
| key={idx} | ||
| onClick={() => setScore(idx)} | ||
| > | ||
| <img | ||
| src={idx <= score ? SelectedStar : UnselectedStar} | ||
| alt="별점" | ||
| /> | ||
| </button> | ||
| ))} | ||
| </div> | ||
| <div className="flex justify-center gap-[10px]"> | ||
| <button | ||
| className={`px-[10px] py-2 rounded-lg cursor-pointer ${preference === false ? "text-white bg-primary-700" : "text-black bg-[#F0F0F0]"}`} | ||
| onClick={() => handlebutton(false)} | ||
| > | ||
| 내 취향은 아니에요 | ||
| </button> | ||
| <button | ||
| className={`px-[10px] py-2 rounded-lg cursor-pointer ${preference === true ? "text-white bg-primary-700" : "text-black bg-[#F0F0F0]"}`} | ||
| onClick={() => handlebutton(true)} | ||
| > |
There was a problem hiding this comment.
모달 내 버튼들에 type="button"이 누락되어 있습니다
Line 373, Line 383, Line 396, Line 402의 버튼은 폼 컨텍스트에서 의도치 않은 submit을 유발할 수 있습니다.
🔧 제안 수정
<button
+ type="button"
className="pt-[10px] cursor-pointer flex justify-center items-center"
onClick={onClick}
>
@@
<button
+ type="button"
className="size-[58px] flex justify-center items-center cursor-pointer"
key={idx}
onClick={() => setScore(idx)}
>
@@
<button
+ type="button"
className={`px-[10px] py-2 rounded-lg cursor-pointer ${preference === false ? "text-white bg-primary-700" : "text-black bg-[`#F0F0F0`]"}`}
onClick={() => handlebutton(false)}
>
@@
<button
+ type="button"
className={`px-[10px] py-2 rounded-lg cursor-pointer ${preference === true ? "text-white bg-primary-700" : "text-black bg-[`#F0F0F0`]"}`}
onClick={() => handlebutton(true)}
>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <button | |
| className="pt-[10px] cursor-pointer flex justify-center items-center" | |
| onClick={onClick} | |
| > | |
| <img src={X} alt="닫기버튼" className="size-6" /> | |
| </button> | |
| <div className="font-semibold"> | |
| <p className="text-[20px] pb-6">오늘의 메뉴는 어떠셨나요</p> | |
| <div className="pb-4 flex justify-center"> | |
| {star.map((idx) => ( | |
| <button | |
| className="size-[58px] flex justify-center items-center cursor-pointer" | |
| key={idx} | |
| onClick={() => setScore(idx)} | |
| > | |
| <img | |
| src={idx <= score ? SelectedStar : UnselectedStar} | |
| alt="별점" | |
| /> | |
| </button> | |
| ))} | |
| </div> | |
| <div className="flex justify-center gap-[10px]"> | |
| <button | |
| className={`px-[10px] py-2 rounded-lg cursor-pointer ${preference === false ? "text-white bg-primary-700" : "text-black bg-[#F0F0F0]"}`} | |
| onClick={() => handlebutton(false)} | |
| > | |
| 내 취향은 아니에요 | |
| </button> | |
| <button | |
| className={`px-[10px] py-2 rounded-lg cursor-pointer ${preference === true ? "text-white bg-primary-700" : "text-black bg-[#F0F0F0]"}`} | |
| onClick={() => handlebutton(true)} | |
| > | |
| <button | |
| type="button" | |
| className="pt-[10px] cursor-pointer flex justify-center items-center" | |
| onClick={onClick} | |
| > | |
| <img src={X} alt="닫기버튼" className="size-6" /> | |
| </button> | |
| <div className="font-semibold"> | |
| <p className="text-[20px] pb-6">오늘의 메뉴는 어떠셨나요</p> | |
| <div className="pb-4 flex justify-center"> | |
| {star.map((idx) => ( | |
| <button | |
| type="button" | |
| className="size-[58px] flex justify-center items-center cursor-pointer" | |
| key={idx} | |
| onClick={() => setScore(idx)} | |
| > | |
| <img | |
| src={idx <= score ? SelectedStar : UnselectedStar} | |
| alt="별점" | |
| /> | |
| </button> | |
| ))} | |
| </div> | |
| <div className="flex justify-center gap-[10px]"> | |
| <button | |
| type="button" | |
| className={`px-[10px] py-2 rounded-lg cursor-pointer ${preference === false ? "text-white bg-primary-700" : "text-black bg-[`#F0F0F0`]"}`} | |
| onClick={() => handlebutton(false)} | |
| > | |
| 내 취향은 아니에요 | |
| </button> | |
| <button | |
| type="button" | |
| className={`px-[10px] py-2 rounded-lg cursor-pointer ${preference === true ? "text-white bg-primary-700" : "text-black bg-[`#F0F0F0`]"}`} | |
| onClick={() => handlebutton(true)} | |
| > |
🧰 Tools
🪛 Biome (2.4.4)
[error] 373-376: Provide an explicit type prop for the button element.
(lint/a11y/useButtonType)
[error] 383-387: Provide an explicit type prop for the button element.
(lint/a11y/useButtonType)
[error] 396-399: Provide an explicit type prop for the button element.
(lint/a11y/useButtonType)
[error] 402-405: Provide an explicit type prop for the button element.
(lint/a11y/useButtonType)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/meal-plan/meal-plan-page.tsx` around lines 373 - 405, Add explicit
type="button" to all standalone buttons in this modal to prevent accidental form
submission: the close button that invokes onClick, each star-rating button that
calls setScore, and the two preference buttons that call
handlebutton(true/false). Locate the button elements in the modal JSX (the one
using onClick={onClick}, the star map that calls setScore(idx), and the
preference buttons that call handlebutton) and add type="button" to each.
| const { month, weekKor } = getWeekOfMonth(new Date(date)); | ||
|
|
||
| const dayKorMap: Record<string, string> = { | ||
| MONDAY: "월", | ||
| TUESDAY: "화", | ||
| WEDNESDAY: "수", | ||
| THURSDAY: "목", | ||
| FRIDAY: "금", | ||
| SATURDAY: "토", | ||
| SUNDAY: "일", | ||
| }; | ||
| const dayNames = [ | ||
| "MONDAY", | ||
| "TUESDAY", | ||
| "WEDNESDAY", | ||
| "THURSDAY", | ||
| "FRIDAY", | ||
| "SATURDAY", | ||
| "SUNDAY", | ||
| ]; | ||
| const todayIdx = new Date().getDay(); |
There was a problem hiding this comment.
저희 utils/changeAdditionalProp.ts 파일 보시면 day 배열이 존재하는데, 한번 확인해보고 통합해도 좋을 것 같아요.
확인해보니 constants/date-record.ts에도 월~일까지의 Record가 있습니다. date는 여러곳에서 사용되니까 한 번 확인해보시고 통일하면 좋을 것 같아요!
| type ChooseWeekProps = { | ||
| mealTime: "점심" | "저녁"; | ||
| onChangeSelected: (slots: SlotRequest[]) => void; | ||
| }; | ||
|
|
||
| const week: { label: string; value: DayOfWeek }[] = [ | ||
| { label: "월", value: "MONDAY" }, | ||
| { label: "화", value: "TUESDAY" }, | ||
| { label: "수", value: "WEDNESDAY" }, | ||
| { label: "목", value: "THURSDAY" }, | ||
| { label: "금", value: "FRIDAY" }, | ||
| { label: "토", value: "SATURDAY" }, | ||
| { label: "일", value: "SUNDAY" }, | ||
| ]; |
There was a problem hiding this comment.
위의 리뷰와 동일하게 같이 선언하거나 모아둬도 괜찮을 것 같아요!
| const handleButton = () => { | ||
| setIsOpen(true); | ||
| }; |
There was a problem hiding this comment.
handleClick과 handleButton 둘의 차이가 모호한 것 같아요. 핸들러 명만 봤을 때는 둘이 어떤 차이를 가지는지 와닿지 않습니다! 그리고 setIsOpen(true)의 기능만 한다면 굳이 핸들러를 만들지 않고 {()=>setIsOpen(ture)} 이런 식으로 전달해 주는게 더 나아보여요!
| const [isOpen, setIsOpen] = useState(false); | ||
| const [isModalOpen, setIsModalOpen] = useState(false); | ||
| const [selected, setSelected] = useState<{ |
There was a problem hiding this comment.
네이밍이 헷갈립니다..! tab, isOpen, selected가 어떤 걸 의미하는 건지 잘 모르겠어요..
|
PR 218 머지 -> PR 221 내 충돌 해결 후 머지-> 새 이슈를 파서 리펙토링 진행 계획이었는데 |
PR 제목
[REFACTOR] 컴포넌트 구조 정리
PR을 한 이유
그동안 불필요하게 컴포넌트를 분리해두어 파일 이동이 잦았던 문제가 있었습니다.
따라서 가독성을 높이기 위해 불필요하게 분리된 컴포넌트들을 정리했습니다.
#️⃣연관된 이슈
📝작업 내용
컴포넌트 정리
->
meal-plan-page.tsx안에서 로컬 함수로 변경->
meal-plan-page.tsx에 내부 인라인 처리->
meal-plan-create-page.tsx안에서 로컬 함수로 변경->
meal-plan-create-page.tsx안에서 로컬 함수로 변경->
mobile-layout.tsx에 내부 인라인 처리그 외 컴포넌트
스크린샷 (선택)
💬리뷰 요구사항(선택)
혹시 이 외에도 식단 과 관련해서 정리하면 좋을 것 같은 컴포넌트가 있다면 알려주세요!
코드 내부의 로직 리팩토링은 이슈를 새로 파서 하려고 합니다.
코드 내에서 Button 컴포넌트를 많이 사용하여 충돌 가능성이 있어 #218 머지 후에 PR 머지할 예정입니다!
Summary by CodeRabbit
릴리스 노트