Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions apps/client/src/shared/apis/axios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ export const postCategory = async (categoryName: string) => {
return response;
};

export const putCategory = async (id: number, categoryName: string) => {
const response = await apiRequest.put(`/api/v1/categories/${id}`, {
categoryName,
});
return response;
};

export const getAcorns = async () => {
const now = formatLocalDateTime(new Date());
const { data } = await apiRequest.get('/api/v1/users/acorns?now=', {
Expand Down
12 changes: 11 additions & 1 deletion apps/client/src/shared/apis/queries.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { useMutation, useQuery, UseQueryResult } from '@tanstack/react-query';
import { getDashboardCategories, postCategory } from '@shared/apis/axios';
import {
getDashboardCategories,
postCategory,
putCategory,
} from '@shared/apis/axios';
import { AxiosError } from 'axios';
import { DashboardCategoriesResponse, AcornsResponse } from '@shared/types/api';
import { getAcorns } from './axios';
Expand All @@ -19,6 +23,12 @@ export const usePostCategory = () => {
mutationFn: (categoryName: string) => postCategory(categoryName),
});
};
export const usePutCategory = () => {
return useMutation({
mutationFn: ({ id, categoryName }: { id: number; categoryName: string }) =>
putCategory(id, categoryName),
});
};

export const useGetArcons = (): UseQueryResult<AcornsResponse, AxiosError> => {
return useQuery({
Expand Down
3 changes: 2 additions & 1 deletion apps/client/src/shared/components/sidebar/PopupPortal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ export default function PopupPortal({
title="카테고리 수정하기"
left="취소"
right="확인"
placeholder={popup.name}
onInputChange={onChange}
defaultValue={popup.name}
onLeftClick={onClose}
onRightClick={() => onEditConfirm?.(popup.id)}
/>
Comment on lines +47 to 51
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

편집 확인 시 값이 비어 전송될 수 있음(defaultValue + onChange만 사용)

defaultValue는 입력 초기값만 채우고, 사용자가 수정하지 않으면 onInputChange가 호출되지 않아 상위 상태가 비어 있을 수 있습니다. 이 상태에서 “확인” 클릭 시 빈 값이 전송될 가능성이 큽니다.

두 가지 중 하나로 막아주세요(권장 1):

  1. 편집 팝업 열릴 때 상위 초깃값을 시딩
-import { createPortal } from 'react-dom';
+import { createPortal } from 'react-dom';
+import { useEffect } from 'react';
@@
 export default function PopupPortal({
   popup,
   onClose,
   onChange,
@@
 }: Props) {
   if (!popup) return null;
 
+  useEffect(() => {
+    if (popup.kind === 'edit') {
+      onChange?.(popup.name);
+    }
+  }, [popup.kind, popup.name]);
  1. 혹은 확인 클릭 시 초깃값을 백업 전달(상위에서 draft 우선 사용)
-            onRightClick={() => onEditConfirm?.(popup.id)}
+            onRightClick={() => onEditConfirm?.(popup.id, popup.name)}

(이 경우 Sidebar의 핸들러가 draft 파라미터를 우선하도록 함께 수정 필요)

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

Suggested change
onInputChange={onChange}
defaultValue={popup.name}
onLeftClick={onClose}
onRightClick={() => onEditConfirm?.(popup.id)}
/>
import { createPortal } from 'react-dom';
import { useEffect } from 'react';
export default function PopupPortal({
popup,
onClose,
onChange,
onEditConfirm,
}: Props) {
if (!popup) return null;
useEffect(() => {
if (popup.kind === 'edit') {
onChange?.(popup.name);
}
}, [popup.kind, popup.name]);
onInputChange={onChange}
defaultValue={popup.name}
onLeftClick={onClose}
onRightClick={() => onEditConfirm?.(popup.id)}
/>
}
🤖 Prompt for AI Agents
In apps/client/src/shared/components/sidebar/PopupPortal.tsx around lines 47 to
51: the component uses defaultValue + onInputChange so if the user doesn't
change the input the parent draft remains empty and an empty value may be
submitted on confirm; fix by seeding the parent state when the edit popup opens
(set the draft in the parent to popup.name) so onEditConfirm always has the
current value, alternatively (less preferred) change confirm handling to pass
popup.name when draft is empty and update the Sidebar handler to prefer draft if
provided.

Expand Down
20 changes: 16 additions & 4 deletions apps/client/src/shared/components/sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
useGetDashboardCategories,
usePostCategory,
useGetArcons,
usePutCategory,
} from '@shared/apis/queries';
import { useState } from 'react';
import { useQueryClient } from '@tanstack/react-query';
Expand All @@ -23,6 +24,7 @@ export function Sidebar() {
const queryClient = useQueryClient();

const { data: categories } = useGetDashboardCategories();
const { mutate: patchCategory } = usePutCategory();
const { mutate: createCategory } = usePostCategory();
const { data, isPending, isError } = useGetArcons();

Expand Down Expand Up @@ -66,6 +68,19 @@ export function Sidebar() {
},
});
};
const handlePatchCategory = (id: number) => {
patchCategory(
{ id, categoryName: newCategoryName },
{
onSuccess: () => {
setNewCategoryName('');
queryClient.invalidateQueries({ queryKey: ['dashboardCategories'] });
close();
},
onError: (error) => console.error('카테고리 수정 실패:', error),
}
);
};
Comment on lines +71 to +83
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

편집 시 입력 변경이 없으면 빈 문자열로 업데이트되는 치명적 버그

편집 팝업에서 사용자가 값을 수정하지 않으면 onChange가 호출되지 않아 newCategoryName이 빈 문자열일 수 있습니다. 이 상태로 PUT하면 카테고리명이 공백으로 바뀝니다. 또한 변경 없음(no-op)일 때는 API 호출을 생략하는 것이 안전합니다.

아래처럼 현재 이름을 안전망으로 사용하고, 공백/변경 없음 케이스를 방지해 주세요:

-  const handlePatchCategory = (id: number) => {
-    patchCategory(
-      { id, categoryName: newCategoryName },
-      {
-        onSuccess: () => {
-          setNewCategoryName('');
-          queryClient.invalidateQueries({ queryKey: ['dashboardCategories'] });
-          close();
-        },
-        onError: (error) => console.error('카테고리 수정 실패:', error),
-      }
-    );
-  };
+  const handlePatchCategory = (id: number) => {
+    const currentName = getCategoryName(id);
+    const trimmed = newCategoryName.trim();
+    const nextName = trimmed || currentName;
+
+    if (!nextName) {
+      console.error('카테고리 이름이 비어 있습니다.');
+      return;
+    }
+    if (nextName === currentName) {
+      // 변경 없음: API 호출 생략
+      close();
+      return;
+    }
+    patchCategory(
+      { id, categoryName: nextName },
+      {
+        onSuccess: () => {
+          setNewCategoryName('');
+          queryClient.invalidateQueries({ queryKey: ['dashboardCategories'] });
+          close();
+        },
+        onError: (error) => console.error('카테고리 수정 실패:', error),
+      }
+    );
+  };

또는 편집 팝업을 열 때 초기 입력값을 state에 주입해 리스크를 더 낮출 수 있습니다(변경 범위: Line 149 인근).

예시:

onEdit={(id, name) => {
  setNewCategoryName(name); // 또는 handleCategoryChange(name)
  openEdit(id, name);
}}
🤖 Prompt for AI Agents
In apps/client/src/shared/components/sidebar/Sidebar.tsx around lines 71 to 83,
the patch handler uses newCategoryName which can be an empty string if the user
didn't change the input, causing accidental blanking of the category; fix by
using the current category name as a safe fallback and short-circuiting no-op
updates: compute const finalName = newCategoryName?.trim() ||
existingCategoryName; if finalName is empty or identical to
existingCategoryName, do not call patchCategory (return early); otherwise call
patchCategory with { id, categoryName: finalName } and keep existing
onSuccess/onError behavior. Also consider setting newCategoryName when opening
the edit popup (around line ~149) so the input is initialized to the current
name.


if (isPending) return <div></div>;
if (isError) return <div></div>;
Expand Down Expand Up @@ -156,10 +171,7 @@ export function Sidebar() {
onClose={close}
onChange={handleCategoryChange}
onCreateConfirm={handleCreateCategory}
onEditConfirm={() => {
// TODO: 수정 API
close();
}}
onEditConfirm={(id) => handlePatchCategory(id)}
onDeleteConfirm={() => {
// TODO: 삭제 API
close();
Expand Down
7 changes: 5 additions & 2 deletions packages/design-system/src/components/popup/Popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ interface BasePopupProps {
errortext?: string;
placeholder?: string;
isError?: boolean;
helperText?: string;
helperText?: string;
inputValue?: string;
defaultValue?: string;
Comment on lines 15 to +16
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아항 팝업키자마자
예전에 지정된 카테고리 이름으로 띄어져야 해서 담은 텍스트인가요??
이걸 기존에 inputValue 속성으로는 활용이 불가한가요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inputValue로 하면 수정이 안되더라고요 그래서 defaultValue로 설정하고 수정가능하게 했습니다-!!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value로 들어가는 값이 정적으로 들어가는 것이라 value로 두게 되면 onChange로 값이 바뀌어도 계속 고정되어 있기 때문에 수정이 불가능해요!
그래서 defaultValue로 넣어주신 것 같네요 👍

onInputChange?: (value: string) => void;
onLeftClick?: () => void;
onRightClick?: () => void;
Expand All @@ -28,6 +29,7 @@ const Popup = ({
errortext,
isError,
inputValue,
defaultValue,
onInputChange,
onLeftClick,
onRightClick,
Expand All @@ -43,9 +45,10 @@ const Popup = ({
isError={isError}
value={inputValue}
onChange={(e) => onInputChange?.(e.target.value)}
defaultValue={defaultValue}
/>
{isError && errortext && (
<p className='mt-[0.5rem] text-error body3-r'>{errortext}</p>
<p className="text-error body3-r mt-[0.5rem]">{errortext}</p>
)}
</div>
)}
Expand Down
Loading