Skip to content

Comments

Feat: 개발 QA 1차(카테고리 팝업 수정) #113

Merged
jjangminii merged 12 commits intodevelopfrom
fix/#111/sp1-qa-popup
Sep 18, 2025
Merged

Feat: 개발 QA 1차(카테고리 팝업 수정) #113
jjangminii merged 12 commits intodevelopfrom
fix/#111/sp1-qa-popup

Conversation

@jjangminii
Copy link
Collaborator

@jjangminii jjangminii commented Sep 17, 2025

📌 Related Issues

관련된 Issue를 태그해주세요. (e.g. - close #25)

📄 Tasks

  • 수정/추가 input 헬퍼 텍스트 추가
  • 수정/저장/삭제 실패시 에러 토스트
  • 토스트 한번 띄우고 다시 세팅
  • 카테고리 10개 넘어가면 추가 안됨

⭐ PR Point (To Reviewer)

📷 Screenshot

2025-09-18.3.42.05.mov
2025-09-17.4.33.54.mov

Summary by CodeRabbit

  • New Features

    • 사이드바 카테고리 팝업에 입력 검증 추가(공백 제거, 비어 있음 방지, 최대 10자, 중복 검사).
    • 생성/수정/삭제 동작에 대해 자동 닫힘 토스트 알림 도입(동작별 메시지, 닫기 콜백).
  • UI/UX

    • 팝업 입력창에 오류 상태 및 안내 문구 노출.
    • 팝업에서 편집 시 임시 입력 상태 유지 및 동기화.
    • 팝업 열림/닫힘과 토스트 표시 흐름 개선으로 상호작용 안정성 향상.
  • Style

    • 옵션 메뉴 버튼의 내용 정렬을 왼쪽 패딩에서 중앙 정렬로 변경.

@jjangminii jjangminii linked an issue Sep 17, 2025 that may be closed by this pull request
@vercel
Copy link

vercel bot commented Sep 17, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
pinback-client-client Ready Ready Preview Comment Sep 17, 2025 6:41pm

@coderabbitai
Copy link

coderabbitai bot commented Sep 17, 2025

Walkthrough

팝업 입력 검증(공백·길이·중복)과 draft 상태를 추가하고, 생성/수정/삭제 핸들러로 제어 흐름을 분리했습니다. 실패 시 토스트를 표시하도록 PopupPortal과 Sidebar 간에 토스트 상태와 카테고리 목록을 전달하도록 인터페이스가 확장되었습니다.

Changes

Cohort / File(s) Summary
Popup validation & toast integration
apps/client/src/shared/components/sidebar/PopupPortal.tsx
내부 draft 상태 및 useEffect 동기화 추가; 이름 검증(트림, 비어있음, 최대 10자, 중복 검사) 도입; isError/helperText 계산 및 전달; handleInputChange, handleCreate, handleEdit, handleDelete 핸들러 추가; categoryList, isToastOpen, onToastClose, toastKey, toastAction props 추가; 실패 시 AutoDismissToast 렌더링; popup null 안전 처리
Sidebar wiring for categories & toast
apps/client/src/shared/components/sidebar/Sidebar.tsx
toastIsOpen 상태 추가 및 create/patch/delete onError에서 토스트 오픈으로 변경; popup 변경 시 토스트 초기화 useEffect 추가; 팝업 열기 전 토스트 숨김 처리; PopupPortalcategoryListisToastOpen, onToastClose 전달
Style tweak
apps/client/src/shared/components/optionsMenuButton/OptionsMenuButton.tsx
ITEM_STYLE 변경: pl-[0.8rem]justify-center로 버튼 내부 정렬 조정 (시각 정렬 변경, API 불변)

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User as 사용자
  participant Sidebar as Sidebar
  participant Popup as PopupPortal
  participant Validator as Validation
  participant API as Category API
  participant Toast as AutoDismissToast

  User->>Popup: 입력/확인 클릭 (create/edit/delete)
  alt create/edit
    Popup->>Validator: 이름 검증(트림, 길이≤10, 중복제외)
    Validator-->>Popup: 검증 결과
    alt valid
      Popup->>API: mutate(create|edit)
      API-->>Sidebar: onSuccess
      Sidebar-->>User: 팝업 닫음
    else invalid
      Popup->>Toast: show(helperText, actionLabel)
      Toast-->>User: 오류 메시지(자동 닫힘)
    end
  else delete
    Popup->>API: mutate(delete)
    API-->>Sidebar: onSuccess / onError
    alt onError
      Sidebar->>Popup: isToastOpen=true
      Popup->>Toast: show(delete 실패)
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

🛠️ Feature, 정민, frontend

Suggested reviewers

  • jllee000
  • constantly-dev

Poem

토끼는 살금살금, 팝업 옆에서 깡충대네 🐇
이름을 다듬고, 글자 수를 헤아리며,
중복이면 토스트로 "앗!" 하고 알리고,
통과하면 살포시 확인 버튼을 누르지요.
오늘도 깔끔한 카테고리 한 입! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Linked Issues Check ⚠️ Warning 코드 변경은 팝업 입력 유효성 검사, 에러 토스트 추가, 팝업 API 확장 등으로 linked issue #111의 목표(팝업 컴포넌트 QA 반영)를 충족합니다. 그러나 linked issue #25(Progress 컴포넌트 구현)는 본 PR에서 전혀 다루어지지 않아 해당 이슈 요구사항을 만족하지 않습니다. 따라서 제공된 모든 linked issues를 기준으로는 완전한 준수로 보기 어렵습니다. 해결 방안으로 PR에 연결된 이슈 목록에서 #25를 제거하거나 이 PR을 #111 전용으로 명확히 해 주세요. 만약 #25도 함께 처리하려는 의도라면 Progress 컴포넌트 관련 변경을 포함하거나 별도 PR로 분리해 제출해 주세요. PR 설명에 실제로 해결하는 이슈를 명확히 표기하면 혼선을 줄일 수 있습니다.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed PR 제목 "Feat: 개발 QA 1차(정민
Out of Scope Changes Check ✅ Passed 변경 내역은 주로 팝업 관련 로직(입력 검증, create/edit/delete 핸들러, 토스트)과 사이드바 연동으로 linked issue #111의 범위와 일치합니다. OptionsMenuButton의 정렬 스타일 변경은 팝업 버튼 UI와 관련 있어 범위를 벗어난 변경으로 보이지 않습니다. 다만 PopupPortal에 공용 props가 추가된 점은 다른 호출부에 영향이 있으므로 병합 전 호환성 확인이 필요합니다.
Description Check ✅ Passed PR 본문은 템플릿의 Related Issues(- close #111), 작업 목록, 스크린샷 섹션을 포함해 필수 정보가 대부분 충족되어 있습니다. 다만 '⭐ PR Point' 항목이 비어 있어 리뷰어에게 명시적인 검증 포인트나 재현 단계가 부족합니다. 전반적으로 내용은 충분히 구체적이어서 합격 수준입니다.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/#111/sp1-qa-popup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added the fix 버그 수정하라 러브버그 label Sep 17, 2025
@jjangminii jjangminii changed the title Fix/#111/sp1 qa popup Feat: 개발 QA 1차(정민 | 카테고리 팝업 수정) Sep 17, 2025
@github-actions
Copy link

github-actions bot commented Sep 17, 2025

✅ Storybook chromatic 배포 확인:
🐿️ storybook

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (5)
apps/client/src/shared/components/sidebar/PopupPortal.tsx (3)

2-3: draft 초기화용 useEffect 누락 — import 보강 필요

편집 모달이 열릴 때 초깃값 동기화를 위해 useEffect가 필요합니다. 먼저 useEffect import를 추가해 주세요.

적용 diff:

-import { useState } from 'react';
+import { useEffect, useState } from 'react';

33-47: 빈 입력 허용으로 인해 불필요한 요청 가능

현재 검증 함수는 value가 비어 있으면 null을 반환하여 에러가 없다고 판단합니다. 이 상태에서 생성/수정 버튼을 누르면 빈 이름으로 API 호출이 갈 수 있습니다(실패 → 토스트). UX/에러율을 줄이려면 액션 이전에 공백-only를 차단하세요.

적용 diff(검증 메시지는 그대로 두되, 액션 단에서 차단):

-    if (!value) return null;
+    if (!value) return null; // UI 에러 노출은 최소화하되, 액션에서 별도 가드

추가로 아래 핸들러 가드 패치를 함께 적용해 주세요(다음 코멘트 참조).


41-47: 중복 검사 시 좌우 공백 정규화 권장

좌변도 trim()하여 "이름" vs "이름 " 같은 케이스를 동일하게 취급하도록 하는 편이 안전합니다.

적용 diff:

-        category.name === value &&
+        category.name.trim() === value &&
apps/client/src/shared/components/sidebar/Sidebar.tsx (2)

71-73: 생성 실패 전, 공백 입력 사전 차단 권장

빈/공백-only 이름이면 API 호출 전에 빠르게 리턴하고 토스트만 노출하는 편이 UX와 서버 부하에 유리합니다.

적용 diff:

   const handleCreateCategory = () => {
-    createCategory(newCategoryName, {
+    if (!newCategoryName.trim()) {
+      setToastIsOpen(true);
+      return;
+    }
+    createCategory(newCategoryName.trim(), {
       onSuccess: () => {

101-102: 삭제 실패 토스트 재사용성을 위한 헬퍼 함수화 제안

setToastIsOpen(true) 호출이 세 곳에서 반복됩니다. 작은 헬퍼(openErrorToast)로 통일하면 후속 변경(예: 트래킹 추가)이 쉬워집니다.

적용 예:

const openErrorToast = () => setToastIsOpen(true);
/* ... onError: () => openErrorToast() ... */
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b923743 and 67c0da9.

📒 Files selected for processing (2)
  • apps/client/src/shared/components/sidebar/PopupPortal.tsx (6 hunks)
  • apps/client/src/shared/components/sidebar/Sidebar.tsx (5 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-17T09:18:13.818Z
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#102
File: apps/extension/src/components/modalPop/ModalPop.tsx:166-172
Timestamp: 2025-07-17T09:18:13.818Z
Learning: In apps/extension/src/components/modalPop/ModalPop.tsx, the categories array should include "안 읽은 정보" (Unread Information) as the first default category that cannot be deleted. This default category is used consistently across the client-side dashboard and should be protected from deletion in the extension as well.

Applied to files:

  • apps/client/src/shared/components/sidebar/PopupPortal.tsx
  • apps/client/src/shared/components/sidebar/Sidebar.tsx
🧬 Code graph analysis (1)
apps/client/src/shared/components/sidebar/PopupPortal.tsx (2)
packages/design-system/src/components/toast/hooks/uesFadeOut.tsx (1)
  • AutoDismissToast (12-49)
packages/design-system/src/components/toast/Toast.tsx (1)
  • Toast (7-21)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: storybook
🔇 Additional comments (2)
apps/client/src/shared/components/sidebar/Sidebar.tsx (1)

145-157: 기본 카테고리('안 읽은 정보') 삭제 방지 검증 — 재실행 필요

이전 스크립트가 "unrecognized file type: tsx" 오류로 실패했습니다. 아래 수정된 스크립트로 재실행하여 옵션 메뉴에서 기본 카테고리('안 읽은 정보')가 삭제되지 않도록 비활성화(또는 가드)되어 있는지 확인.

#!/bin/bash
# .ts/.tsx 파일에서 기본 카테고리 문자열 및 삭제 핸들러 검색
rg -n -C3 "안 읽은 정보" -g "*.ts" -g "*.tsx" || true
rg -n -C3 -e "openDelete\(" -e "onDelete" -e "deleteCategory" -g "*.ts" -g "*.tsx" || true

대상 파일: apps/client/src/shared/components/sidebar/Sidebar.tsx (라인 145–157) 및 옵션 메뉴 관련 코드 전역 검색.

apps/client/src/shared/components/sidebar/PopupPortal.tsx (1)

64-68: 기본 카테고리("안 읽은 정보") 삭제 방지 로직 확인 필요

리포지토리에서 '안 읽은 정보'·삭제 금지 관련 문자열은 검색되지 않았고, PopupPortal.handleDelete는 onDeleteConfirm?.(popup.id)만 호출합니다 — 상위(OptionsMenuPortal → Sidebar/useCategoryPopups)에서 차단 중인지 확인하거나, 없다면 삭제 가드(예: category.name === '안 읽은 정보' 검사)를 추가하세요. 참고 파일: apps/client/src/shared/components/sidebar/PopupPortal.tsx (handleDelete), apps/client/src/shared/components/sidebar/OptionsMenuPortal.tsx (onDelete 호출), apps/client/src/shared/components/sidebar/Sidebar.tsx (PopupPortal 사용), apps/client/src/shared/apis/axios.ts (deleteCategory API).

Comment on lines 29 to 31
const [draft, setDraft] = useState('');

if (!popup) return null;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

편집 모달에서 입력을 변경하지 않으면 빈 문자열이 전파됨

draft가 기본값 ''에서 시작하고, 편집 시 입력을 바꾸지 않으면 onEditConfirm에 빈 문자열이 전달될 위험이 있습니다(아래 핸들러 코멘트 참조). 모달 오픈 시점에 popup.namedraft와 상위 onChange로 동기화해 주세요.

적용 diff:

 const [draft, setDraft] = useState('');
 
+// 모달 오픈/변경 시 초깃값 동기화
+useEffect(() => {
+  if (!popup) return;
+  if (popup.kind === 'edit') {
+    const v = popup.name?.trim() ?? '';
+    setDraft(v);
+    onChange?.(v);
+  } else if (popup.kind === 'create') {
+    setDraft('');
+    onChange?.('');
+  } else {
+    setDraft('');
+  }
+}, [popup, onChange]);
📝 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
const [draft, setDraft] = useState('');
if (!popup) return null;
const [draft, setDraft] = useState('');
// 모달 오픈/변경 시 초깃값 동기화
useEffect(() => {
if (!popup) return;
if (popup.kind === 'edit') {
const v = popup.name?.trim() ?? '';
setDraft(v);
onChange?.(v);
} else if (popup.kind === 'create') {
setDraft('');
onChange?.('');
} else {
setDraft('');
}
}, [popup, onChange]);
if (!popup) return null;
🤖 Prompt for AI Agents
In apps/client/src/shared/components/sidebar/PopupPortal.tsx around lines 29-31,
draft is initialized to '' and if the user opens the edit modal but doesn't
change the input an empty string can be propagated on confirm; update the
component to synchronize draft with popup.name when the modal opens or when
popup changes (e.g., add a useEffect that sets setDraft(popup.name ?? '')
whenever popup becomes non-null) so the initial input reflects the current name
and onEditConfirm will receive the existing name if the user makes no edits.

Comment on lines 54 to 63
const handleCreate = () => {
if (error) return;
onCreateConfirm?.();
};

const handleEdit = () => {
if (error || popup.kind !== 'edit') return;
onEditConfirm?.(popup.id, draft.trim());
};

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

CREATE/PATCH 시 공백값 차단 및 편집 기본값 폴백

  • 생성: 공백-only면 요청 자체를 막습니다.
  • 수정: 입력 변경이 없으면 popup.name으로 폴백, 최종 값은 상위 onChange로도 반영해 일관성을 유지합니다.

적용 diff:

 const handleCreate = () => {
-    if (error) return;
-    onCreateConfirm?.();
+    const value = draft.trim();
+    if (!value || error) return;
+    onChange?.(value);
+    onCreateConfirm?.();
   };

 const handleEdit = () => {
-    if (error || popup.kind !== 'edit') return;
-    onEditConfirm?.(popup.id, draft.trim());
+    if (popup.kind !== 'edit') return;
+    const value = (draft || popup.name || '').trim();
+    if (!value || error) return;
+    onChange?.(value);
+    onEditConfirm?.(popup.id, value);
   };
📝 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
const handleCreate = () => {
if (error) return;
onCreateConfirm?.();
};
const handleEdit = () => {
if (error || popup.kind !== 'edit') return;
onEditConfirm?.(popup.id, draft.trim());
};
const handleCreate = () => {
const value = draft.trim();
if (!value || error) return;
onChange?.(value);
onCreateConfirm?.();
};
const handleEdit = () => {
if (popup.kind !== 'edit') return;
const value = (draft || popup.name || '').trim();
if (!value || error) return;
onChange?.(value);
onEditConfirm?.(popup.id, value);
};
🤖 Prompt for AI Agents
In apps/client/src/shared/components/sidebar/PopupPortal.tsx around lines 54 to
63, the create and edit handlers must prevent blank-only submissions and ensure
edits fall back to the original name and sync parent state: for handleCreate,
check trimmed draft and return early if it's empty (do not call
onCreateConfirm); for handleEdit, if popup.kind === 'edit' compute finalName =
draft.trim() || popup.name, call onEditConfirm?.(popup.id, finalName) and also
call onChange?.(finalName) so the parent receives the normalized value; keep
existing error checks.

Comment on lines 119 to 129
{isToastOpen && (
<div className="absolute bottom-[23.4rem] left-1/2 -translate-x-1/2">
<AutoDismissToast
duration={1000}
fadeMs={1000}
onClose={onToastClose}
>
<Toast text={`${actionLabel}에 실패했어요.\n다시 시도해주세요`} />
</AutoDismissToast>
</div>
)}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

토스트 재표시가 한 번만 되고 이후 재등장하지 않는 이슈

AutoDismissToast는 내부 타이머 종료 후 onClose를 호출하지만, 부모에서 isToastOpenfalse로 되돌려주지 않으면 다음 오류 때 재마운트가 되지 않아 토스트가 다시 보이지 않습니다. Sidebar에서 onToastClose를 내려 받아 setToastIsOpen(false)를 호출하세요.

적용 예시는 Sidebar 코멘트 참고.

🤖 Prompt for AI Agents
In apps/client/src/shared/components/sidebar/PopupPortal.tsx around lines
119-129 the AutoDismissToast calls onClose when its internal timer finishes but
the parent boolean isToastOpen is never reset, preventing the toast from
remounting on subsequent errors; update the Sidebar (parent) to accept the
onToastClose callback and in that handler call setToastIsOpen(false) (i.e., pass
a function down that flips the toast state to false when invoked) so the toast
can be reopened on future failures.

Comment on lines 85 to 89
onError: (error) => {
console.error('카테고리 수정 실패:', error);
setToastIsOpen(true);
},
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

편집 시 입력 미변경이면 빈 문자열 전송 위험

onEditConfirm에서 전달되는 두 번째 인자를 무시하고, 로컬 상태(newCategoryName)만 사용 중입니다. 편집 모달에서 입력을 바꾸지 않으면 newCategoryName이 빈 문자열이라 빈 이름으로 PATCH가 나갈 수 있습니다. 콜백의 draft를 사용하거나, 최소한 공백-only를 차단하세요.

두 가지 대안 중 택1:

  • 대안 A(권장): draft 사용
-  const handlePatchCategory = (id: number) => {
-    patchCategory(
-      { id, categoryName: newCategoryName },
+  const handlePatchCategory = (id: number, name?: string) => {
+    const next = (name ?? newCategoryName).trim();
+    if (!next) { setToastIsOpen(true); return; }
+    patchCategory(
+      { id, categoryName: next },
       {
  • 대안 B(최소 수정): 모달 오픈 시 newCategoryName을 현재 이름으로 동기화
+ // popup 변경 시 편집 초기값을 동기화
+ useEffect(() => {
+   if (popup?.kind === 'edit') {
+     setNewCategoryName(getCategoryName(popup.id));
+   } else if (popup?.kind === 'create') {
+     setNewCategoryName('');
+   }
+ }, [popup]);

(대안 B 사용 시 useEffect import 필요)

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/client/src/shared/components/sidebar/Sidebar.tsx around lines 85-89, the
edit-confirm handler ignores the provided draft and relies on local
newCategoryName which can be empty, causing an empty-name PATCH; fix by using
the callback's second argument (draft) as the source of truth when sending the
PATCH (preferred), trimming and validating it to block blank-only names before
calling the API, and falling back to the existing name if draft is falsy;
alternatively, if you choose the minimal change, initialize/sync newCategoryName
to the current category name when opening the modal (requires importing
useEffect) and also trim/validate before submitting to prevent whitespace-only
names.

Comment on lines 193 to 194
onEditConfirm={(id) => handlePatchCategory(id)}
onDeleteConfirm={(id) => handleDeleteCategory(id)}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

onEditConfirm 시그니처 활용해 초안 값 직접 전달

위 대안 A를 택했다면, 여기서 draft를 전달해 주세요.

적용 diff:

-        onEditConfirm={(id) => handlePatchCategory(id)}
+        onEditConfirm={(id, draft) => handlePatchCategory(id, 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
onEditConfirm={(id) => handlePatchCategory(id)}
onDeleteConfirm={(id) => handleDeleteCategory(id)}
onEditConfirm={(id, draft) => handlePatchCategory(id, draft)}
onDeleteConfirm={(id) => handleDeleteCategory(id)}
🤖 Prompt for AI Agents
In apps/client/src/shared/components/sidebar/Sidebar.tsx around lines 193-194,
the onEditConfirm prop currently only forwards the id but the review requests
using the onEditConfirm signature to pass the draft as well; update the prop to
forward both parameters to your handler (e.g., call handlePatchCategory with id
and draft or bind the handler so it accepts both id and draft) so the draft
value from the edit UI is passed through to the patch function.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
apps/client/src/shared/components/sidebar/Sidebar.tsx (1)

96-106: 기본 카테고리('안 읽은 정보') 삭제 차단 로직 없음 — UI에서 삭제 호출 차단 필요

레포지토리에서 '삭제 불가' 관련 로직/문구가 검색되지 않았으므로, OptionsMenu/Popup 단계에서 삭제 버튼을 비활성화/비표시하고 handleDeleteCategory에서도 기본 카테고리(id 또는 name 검사)면 삭제 호출을 early-return 하세요.

참고 위치:

  • apps/client/src/shared/components/sidebar/OptionsMenuPortal.tsx: ~35–41 (onDelete 호출)
  • apps/client/src/shared/components/sidebar/Sidebar.tsx: ~94–100 (handleDeleteCategory), ~183–187 (openDelete → onDelete 경로)
  • apps/client/src/shared/apis/queries.ts: ~50–55 (useDeleteCategory mutation)
♻️ Duplicate comments (4)
apps/client/src/shared/components/sidebar/Sidebar.tsx (2)

213-216: AutoDismissToast onClose → 상태 리셋 연결 완료

onToastClose={() => setToastIsOpen(false)}로 재노출 문제 해결되었습니다.


206-216: 편집 확정 시 draft 무시 → 빈 이름 PATCH 위험

PopupPortal이 onEditConfirm(id, draft)로 정상 전달하지만 여기서 draft를 버리고 newCategoryName을 사용해 공백/미변경 시 빈 문자열 PATCH가 발생할 수 있습니다. draft를 수용하고 trim/검증 후 전송하세요.

패치 핸들러 보강:

-  const handlePatchCategory = (id: number) => {
-    patchCategory(
-      { id, categoryName: newCategoryName },
+  const handlePatchCategory = (id: number, name?: string) => {
+    const next = (name ?? newCategoryName).trim();
+    if (!next) { setToastIsOpen(true); return; }
+    patchCategory(
+      { id, categoryName: next },
       {
         onSuccess: () => {
           setNewCategoryName('');
           queryClient.invalidateQueries({ queryKey: ['dashboardCategories'] });
           close();
         },
         onError: () => {
           setToastIsOpen(true);
         },
       }
     );
   };

draft 전달:

-        onEditConfirm={(id) => handlePatchCategory(id)}
+        onEditConfirm={(id, draft) => handlePatchCategory(id, draft)}

Also applies to: 80-94

apps/client/src/shared/components/sidebar/PopupPortal.tsx (2)

37-41: 편집 모달 초기값 동기화 LGTM — 부모 상태도 동기화 권장

초깃값을 draft로 세팅한 점 좋습니다. 부모의 newCategoryName과도 맞추면 불일치가 사라집니다.

   useEffect(() => {
     if (!popup) return;
-    setDraft(popup.kind === 'edit' ? (popup.name ?? '') : '');
+    const init = popup.kind === 'edit' ? (popup.name ?? '') : '';
+    setDraft(init);
+    onChange?.(init);
   }, [popup]);

78-86: 확정 전 부모 onChange에 정규화 값 전달로 상태 일관성 확보

생성/수정 모두 confirm 시 value(trimmed)를 부모에도 반영하면 Sidebar의 내부 상태와 일치합니다(특히 생성 시 후단으로 공백이 포함되지 않도록).

 const handleCreate = () => {
-  if (blocked) return;
-  onCreateConfirm?.();
+  if (blocked) return;
+  onChange?.(value);
+  onCreateConfirm?.();
 };

 const handleEdit = () => {
-  if (blocked || popup.kind !== 'edit') return;
-  onEditConfirm?.(popup.id, value);
+  if (blocked || popup.kind !== 'edit') return;
+  onChange?.(value);
+  onEditConfirm?.(popup.id, value);
 };

Also applies to: 83-86

🧹 Nitpick comments (3)
apps/client/src/shared/components/optionsMenuButton/OptionsMenuButton.tsx (1)

25-27: role="menu" 사용 시 ARIA 시맨틱 보완 필요

현재 컨테이너가 menu 역할이지만 내부 버튼은 menuitem/키보드 내비게이션(방향키, roving tabindex)이 없습니다. 메뉴 시맨틱을 유지하지 않을 거라면 group 으로 낮추는 편이 안전합니다.

적용 예:

-      role="menu"
+      role="group"

혹은 menu 유지 시 각 버튼에 role="menuitem"과 적절한 키보드 핸들링을 추가하세요.

apps/client/src/shared/components/sidebar/PopupPortal.tsx (2)

44-69: 검증 로직 적절 — 공백 트리밍 비교로 중복 판정 강화 제안

기존/신규 이름의 사소한 공백 차이를 동일하게 취급하려면 비교 시 trim()을 적용하세요.

-    !!categoryList?.some(
-      (c) => c.name === value && (popup.kind === 'create' || c.id !== popup.id)
-    );
+    !!categoryList?.some(
+      (c) => c.name.trim() === value && (popup.kind === 'create' || c.id !== popup.id)
+    );

144-155: 토스트 표시 시간이 1초로 다소 짧음

가독/인지 시간을 고려해 2–3초로 늘리는 것을 검토하세요.

-              duration={1000}
+              duration={2000}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 67c0da9 and 5c88464.

📒 Files selected for processing (3)
  • apps/client/src/shared/components/optionsMenuButton/OptionsMenuButton.tsx (1 hunks)
  • apps/client/src/shared/components/sidebar/PopupPortal.tsx (4 hunks)
  • apps/client/src/shared/components/sidebar/Sidebar.tsx (7 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-17T09:18:13.818Z
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#102
File: apps/extension/src/components/modalPop/ModalPop.tsx:166-172
Timestamp: 2025-07-17T09:18:13.818Z
Learning: In apps/extension/src/components/modalPop/ModalPop.tsx, the categories array should include "안 읽은 정보" (Unread Information) as the first default category that cannot be deleted. This default category is used consistently across the client-side dashboard and should be protected from deletion in the extension as well.

Applied to files:

  • apps/client/src/shared/components/sidebar/Sidebar.tsx
  • apps/client/src/shared/components/sidebar/PopupPortal.tsx
🧬 Code graph analysis (2)
apps/client/src/shared/components/sidebar/Sidebar.tsx (1)
apps/client/src/shared/components/sidebar/CreateItem.tsx (1)
  • CreateItem (9-27)
apps/client/src/shared/components/sidebar/PopupPortal.tsx (3)
apps/client/src/shared/hooks/useCategoryPopups.ts (1)
  • PopupState (3-7)
packages/design-system/src/components/toast/hooks/uesFadeOut.tsx (1)
  • AutoDismissToast (12-49)
packages/design-system/src/components/toast/Toast.tsx (1)
  • Toast (7-21)
🔇 Additional comments (4)
apps/client/src/shared/components/optionsMenuButton/OptionsMenuButton.tsx (1)

12-12: 버튼 내용 중앙 정렬 변경 LGTM

디자인 정렬 목적에 부합하며 다른 동작에는 영향 없습니다.

apps/client/src/shared/components/sidebar/Sidebar.tsx (3)

63-66: 팝업 전환 시 토스트 상태 리셋 적절함

팝업 변경마다 토스트를 닫아 재노출이 보장됩니다.


166-171: 팝업 오픈 전 토스트 상태 초기화 LGTM

팝업 열기 전에 토스트를 닫아 잔존 토스트가 새 동작을 가리는 문제를 예방합니다.

Also applies to: 180-187


108-111: 팝업 닫힘 시 토스트도 함께 리셋하는 결합 처리 Good

일관된 종료 동작입니다.

@jjangminii jjangminii merged commit f2303de into develop Sep 18, 2025
8 checks passed
@jjangminii jjangminii changed the title Feat: 개발 QA 1차(정민 | 카테고리 팝업 수정) Feat: 개발 QA 1차(카테고리 팝업 수정) Sep 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix 버그 수정하라 러브버그

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Fix] sp1 QA 팝업 컴포넌트 1차 반영

1 participant