Skip to content

feat(console): 점주 멀티매장 URL 모델(/{storeId}) 전환 및 매장 스위처 도입#80

Merged
kisn3089 merged 3 commits into
mainfrom
feat/multi-store-url
Jul 11, 2026
Merged

feat(console): 점주 멀티매장 URL 모델(/{storeId}) 전환 및 매장 스위처 도입#80
kisn3089 merged 3 commits into
mainfrom
feat/multi-store-url

Conversation

@kisn3089

@kisn3089 kisn3089 commented Jul 11, 2026

Copy link
Copy Markdown
Owner

설명 (Description)

점주 콘솔을 멀티매장 URL 모델(/{storeId}) 로 전환하고, 이를 뒷받침하는 매장 스위처·사이드바·마지막 접속 매장 저장 방식을 정리합니다. 이후 테이블 관련 작업(tableNumber String 전환, 테이블 생성 기능)의 선행 기반입니다.

1. 멀티매장 URL 모델 전환 (feat/console)

  • 점주 콘솔 라우트를 /{storeId} 기반으로 전환하고 매장 스위처 도입

2. 마지막 접속 매장 저장 방식 변경 (refactor/db)

  • 마지막 접속 매장 저장을 localStorage쿠키로 이관하여 SSR에서 접근 가능하도록 변경

3. 사이드바·버튼 인터랙션 (feat/ui)

  • 사이드바 기본 폭 확장
  • TouchEventButton에 이벤트 전파 제어(swallowEvent) 옵션 추가

변경 유형 (Type of Change)

  • ✨ 새로운 기능 (New feature)
  • 🎨 코드 리팩토링 (Code refactoring)
  • 🎨 프론트엔드 UI/UX (Frontend UI/UX)

영향 범위 (Scope)

  • 프론트엔드 UI (Frontend UI)
  • 데이터베이스 (Database)

테스트 (Testing)

  • 수동 테스트 (Manual testing)
  • 프론트엔드 테스트 (Frontend testing)

테스트 단계 (Test Steps)

  1. 콘솔 로그인 후 매장 스위처로 매장 전환 → URL이 /{storeId}/...로 갱신되는지 확인
  2. 페이지 새로고침 시 마지막 접속 매장 쿠키가 유지되어 동일 매장으로 진입하는지 확인
  3. 사이드바 폭 및 버튼 터치 인터랙션 확인

체크리스트 (Checklist)

  • 코드가 프로젝트의 코딩 규범을 따름
  • 자체 코드 리뷰를 완료함
  • 변경사항이 새로운 경고를 발생시키지 않음

데이터베이스 마이그레이션 (Database Migration)

  • 데이터베이스 마이그레이션 불필요

배포 참고사항 (Deployment Notes)

  • 환경변수 추가/변경 없음
  • 마지막 접속 매장 저장이 쿠키로 변경됨 (기존 localStorage 값은 무시됨)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • 새로운 기능

    • 매장별 주문 현황 보드와 실시간 주문 상태 업데이트를 제공합니다.
    • 테이블별 주문 상세 조회, 주문 추가·수정·삭제 및 결제를 지원합니다.
    • 메뉴와 테이블 관리 화면 및 추가 버튼을 제공합니다.
    • 주문 접수, 전체 주문 수락, 테이블 QR 코드 표시·복사를 지원합니다.
    • 여러 매장 간 전환과 최근 방문 매장 자동 진입을 지원합니다.
  • 개선 사항

    • 사이드바 탐색 구조와 화면 여백을 개선하고, 주문·매장 메뉴 접근성을 높였습니다.
    • 주문 및 결제 처리 중 로딩·오류 안내를 명확히 표시합니다.

kisn3089 and others added 3 commits July 4, 2026 18:46
- COOKIE_TABLE에 LAST_ACCESSED_STORE_ID 추가하여 서버에서도 읽을 수 있도록 함
- localStorage.const 상수 및 index export 제거

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- SIDEBAR_WIDTH 8rem→12rem: 사이드바 헤더 매장명 노출 대응
- TouchEventButton에 swallowEvent 옵션 추가하여 stopPropagation 여부를 선택 가능하도록 함

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
한 점주가 여러 매장을 관리할 수 있도록 매장 스코프를 URL로 통일한다.

- 라우트 재구성: orders·dashboard·tables·menus를 /{storeId}/* 하위로 이동
- 루트(/) 진입 리졸버 StoreEntryRedirect(RSC): 매장 목록 조회 후 마지막 접속 매장(쿠키) 또는 첫 매장의 /{storeId}/orders로 서버 리다이렉트
- [storeId]/layout: /stores/v1/{storeId} 조회로 매장 소유·유효성 검증, PersistLastStore로 마지막 접속 매장 쿠키 기록
- 사이드바 헤더 StoreSwitcher: 현재 매장명 노출, 매장 2개 이상 시 드롭다운 전환
- sidebarList→sidebarGroup 재구성 및 SidebarItemGroups가 useParams로 /{storeId}/{segment} 링크 생성
- next.config의 / → /stores 리다이렉트 제거, 로그인·로고 이동 경로를 /로 정리
- OrderNoticeDaemon·useStoreOrderSyncDaemon을 useParams storeId 기반으로 단순화(localStorage fallback 제거)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 11, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
ssurack-console Ready Ready Preview, Comment Jul 11, 2026 11:10am
ssurack-order Ready Ready Preview, Comment Jul 11, 2026 11:10am

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Changes

이번 변경은 Console의 매장 진입·사이드바 구조를 개편하고, 주문 보드와 주문 상세·추가 주문·결제 UI를 새로 구성합니다. React Query 캐시 동기화, 실시간 주문 이벤트 처리, 마지막 매장 쿠키 저장도 포함됩니다.

Console 주문 관리

Layer / File(s) Summary
매장 진입과 사이드바 네비게이션
apps/console/src/app/(navigator)/(sidebar)/..., apps/console/src/shared/config/*, packages/db/constants/*
쿠키 기반 매장 진입, 매장 선택기, 사이드바 그룹 및 매장 스코프 라우팅을 추가했습니다.
라우트 레이아웃과 설정 페이지
apps/console/src/app/(navigator)/(sidebar)/[storeId]/dashboard/*, .../menus/*, .../tables/*
대시보드·메뉴·테이블 페이지와 공통 레이아웃을 추가했습니다.
주문 보드 데이터와 동기화
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/layout.tsx, components/*, utils/*
주문 보드 프리패치, 캐시 설정, 실시간 무효화와 테이블별 주문 목록을 연결했습니다.
테이블 주문 카드와 상태 변경
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/*
주문 상태 변경, 전체 수락, QR 코드, 세션 만료 및 상세 링크 UI를 구현했습니다.
주문 상세와 추가 주문
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/*
주문 상세 Context·Provider·테이블 편집, 메뉴 선택·옵션 편집·주문 생성·결제 흐름을 추가했습니다.
공통 UI 계약
packages/ui/src/components/*
터치 이벤트 전파 제어 옵션과 사이드바 기본 폭을 변경했습니다.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • kisn3089/ssurak#26: 주문 항목 수정·삭제 API와 useOrderItem 연동이 관련됩니다.
  • kisn3089/ssurak#73: 주문 상세 행 선택 상태와 Context 기반 편집 흐름이 직접 연결됩니다.
  • kisn3089/ssurak#77: 결제 및 추가 주문 다이얼로그 UI가 동일 영역과 연관됩니다.

Suggested labels: feat, console, packages, ui

Poem

매장 길 따라 쿠키가 빛나고
주문 카드는 차례로 깨어납니다.
메뉴는 담기고 수량은 춤추며
QR은 작은 문을 열어줍니다.
캐시는 흐르고, 결제는 반짝—
Console에 새 하루가 시작됩니다.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed PR의 핵심 변경인 멀티매장 URL 전환과 매장 스위처 도입을 정확하고 간결하게 요약합니다.
Description check ✅ Passed 핵심 섹션(설명, 유형, 범위, 테스트, 체크리스트, 배포/마이그레이션)을 대부분 채워 템플릿 요구를 충족합니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/multi-store-url

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.

@coderabbitai coderabbitai Bot added console feat New feature or request packages ui labels Jul 11, 2026
@kisn3089 kisn3089 self-assigned this Jul 11, 2026
@kisn3089 kisn3089 merged commit 1726c9f into main Jul 11, 2026
4 checks passed
@kisn3089 kisn3089 deleted the feat/multi-store-url branch July 11, 2026 11:17

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (6)
apps/console/src/lib/realtime/useStoreOrderSyncDaemon.ts (1)

21-47: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

storeId 타입을 string으로 좁힌 후 잔존한 dead code guard를 제거해 주세요.

storeId parameter 타입이 string | undefined에서 string으로 변경되었으므로, Line 47의 if (!storeId) return; guard는 더 이상 도달할 수 없는 분기입니다. 혼란을 방지하기 위해 제거를 권장합니다.

♻️ 제안: 불필요한 guard 제거
   useEffect(() => {
-    if (!storeId) return;
-
     const socket = getRealtimeSocket();
     subscribeAdmin(storeId);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/lib/realtime/useStoreOrderSyncDaemon.ts` around lines 21 -
47, Remove the unreachable `if (!storeId) return;` guard from the second
`useEffect` in the store order sync daemon, since `storeId` is typed as
`string`; leave the remaining effect logic unchanged.
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsx (1)

19-75: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

결제 로직을 custom hook으로 추출하는 것을 권장합니다.

PaymentDialogControlbar에 상태 관리(isError), 비동기 결제 로직(payment, tryPayment), abort 처리가 혼재되어 있습니다. coding guidelines에 따라 복잡한 로직은 custom hook으로 분리하는 것을 권장합니다.

As per coding guidelines, apps/{order,console}/src/**/*.{ts,tsx}: "suggest custom hooks for complex logic when appropriate".

♻️ 제안: usePayment hook 추출
// usePayment.ts
export function usePayment(setOpen: (value: SetStateAction<boolean>) => void) {
  const [isError, setIsError] = useState(false);

  const payment = async (
    signal: AbortSignal,
    e: React.MouseEvent<HTMLButtonElement>
  ) => {
    e.preventDefault();
    const result: HealthCheckResponse = await httpMe.fetchHealthCheck({ signal });
    return result;
  };

  const paymentTransaction = useCancelableAsync<
    [React.MouseEvent<HTMLButtonElement>],
    HealthCheckResponse
  >(payment);

  const tryPayment = async (e: React.MouseEvent<HTMLButtonElement>) => {
    try {
      setIsError(false);
      await paymentTransaction(e);
      setOpen(false);
    } catch {
      setIsError(true);
    }
  };

  return { tryPayment, abort: paymentTransaction.abort, isPending: paymentTransaction.isPending, isError };
}
 export default function PaymentDialogControlbar({
   children,
   setOpen,
 }: PaymentDialogControlbarProps) {
-  const [isError, setIsError] = useState(false);
-
-  const payment = async (...) => { ... };
-  const paymentTransaction = useCancelableAsync(...);
-  const tryPayment = async (...) => { ... };
+  const { tryPayment, abort, isPending, isError } = usePayment(setOpen);

   return (
     <>
-      <AlertDialogCancel onClick={paymentTransaction.abort}>
+      <AlertDialogCancel onClick={abort}>
         취소
       </AlertDialogCancel>
       <AlertDialogAction
         onClick={tryPayment}
         className={isError ? "..." : ""}
-        disabled={paymentTransaction.isPending}
+        disabled={isPending}
       >
         <ActivityRender
-          value={!paymentTransaction.isPending}
+          value={!isPending}
           fallback={<LoadingTransaction />}
         >
           {() => (isError ? "결제 실패, 다시 시도" : children)}
         </ActivityRender>
       </AlertDialogAction>
     </>
   );
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsx
around lines 19 - 75, Extract the payment state and async behavior from
PaymentDialogControlbar into a dedicated usePayment custom hook, including
isError, the health-check payment callback, useCancelableAsync integration,
tryPayment, abort, and isPending. Keep PaymentDialogControlbar focused on
rendering and consume the hook’s returned handlers and state while preserving
the existing success, failure, cancellation, and loading behavior.

Source: Coding guidelines

apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailProvider.tsx (1)

20-131: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

복잡한 로직을 custom hook으로 분리하는 것을 권장합니다.

OrderDetailProvider는 data fetching, mutation 설정, state 관리, derived data 계산, 5개의 action 함수를 포함하고 있습니다. 이 로직을 useOrderDetail(params) 같은 custom hook으로 분리하면 provider가 간결해지고, 비즈니스 로직의 testability가 향상됩니다.

As per coding guidelines, apps/{order,console}/src/**/*.{ts,tsx} 경로의 파일에서는 복잡한 로직을 적절한 custom hook으로 분리할 것을 권장합니다.

♻️ 제안하는 custom hook 분리 구조
+// useOrderDetail.ts
+export function useOrderDetail(params: { storeId: string; tableId: string }) {
+  const { storeId, tableId } = params;
+  const fetchUrl = `/orders/v1/tables/${tableId}/active-session`;
+  const { data: session, isRefetching } =
+    useSuspenseWithAuth<ActiveSessionResponse>(fetchUrl);
+  const { update, remove } = useOrderItem({ storeId, tableId });
+  const [editingItem, setEditingItem] = useState<OrderItemWithOrder | null>(null);
+  // ... derived data, actions ...
+  return { state, actions, meta };
+}
+
+// OrderDetailProvider.tsx
 export function OrderDetailProvider({ params, children }: OrderDetailProviderProps) {
-  const { storeId, tableId } = params;
-  const fetchUrl = `/orders/v1/tables/${tableId}/active-session`;
-  // ... 모든 로직 ...
+  const contextValue = useOrderDetail(params);
   return (
     <OrderDetailContext.Provider value={contextValue}>
       {children}
     </OrderDetailContext.Provider>
   );
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailProvider.tsx
around lines 20 - 131, Extract the data fetching, mutation setup, state, derived
order data, and action functions from OrderDetailProvider into a dedicated
useOrderDetail(params) custom hook. Return the existing context value structure
from the hook, then keep OrderDetailProvider focused on invoking the hook and
passing its result to OrderDetailContext.Provider without changing behavior.

Source: Coding guidelines

apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableHeader.tsx (1)

16-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Grid column 클래스가 두 파일에 중복 정의되어 있습니다.

grid grid-cols-[1.5fr_1fr_1fr] xl:grid-cols-[2fr_1fr_1fr] 클래스가 OrderTableHeader.tsxTableRowOrderTableItem.tsxTableCell에 각각 하드코딩되어 있습니다. 어느 한쪽만 변경되면 헤더와 바디 컬럼 정렬이 깨집니다.

  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableHeader.tsx#L16-L16: 공통 상수를 import하여 사용하도록 변경
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableItem.tsx#L18-L18: 동일한 공통 상수를 import하여 사용하도록 변경
♻️ 제안: 공통 상수 추출

order-table/ 디렉토리에 공통 상수를 추가하고 양쪽에서 import하여 사용:

+// order-table/constants.ts
+export const ORDER_TABLE_GRID_CLASS =
+  "grid grid-cols-[1.5fr_1fr_1fr] xl:grid-cols-[2fr_1fr_1fr]";

OrderTableHeader.tsx:

-          className="grid grid-cols-[1.5fr_1fr_1fr] xl:grid-cols-[2fr_1fr_1fr]"
+          className={ORDER_TABLE_GRID_CLASS}

OrderTableItem.tsx:

-    <TableCell className="grid grid-cols-[1.5fr_1fr_1fr] xl:grid-cols-[2fr_1fr_1fr] cursor-pointer min-h-16 animate-fade-in-up">
+    <TableCell className={`${ORDER_TABLE_GRID_CLASS} cursor-pointer min-h-16 animate-fade-in-up`}>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableHeader.tsx
at line 16, Extract the duplicated grid column class into a shared constant in
the order-table directory, then update OrderTableHeader.tsx:16 and
OrderTableItem.tsx:18 to import and use it instead of hardcoding the class. Keep
both header and body layouts driven by the same constant.
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.ts (1)

2-6: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

@spaceorder/db/types 대신 @spaceorder/db에서 database types를 import해야 합니다. coding guideline에 따르면 database types는 @spaceorder/db 패키지에서 import해야 하지만, 두 파일 모두 @spaceorder/db/types subpath에서 import하고 있습니다. layout.tsx@spaceorder/db에서 OrderBoardByStore를 import하고 있어 일관성이 깨집니다.

  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.ts#L2-L6: ActiveSessionResponse, OrderBoardByStore, PublicTable@spaceorder/db에서 import하도록 변경
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsx#L5-L5: OrderSyncEvent@spaceorder/db에서 import하도록 변경
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.ts
around lines 2 - 6, Update imports in
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.ts
lines 2-6 so ActiveSessionResponse, OrderBoardByStore, and PublicTable come from
`@spaceorder/db` instead of its /types subpath. Also update OrderSyncEvent in
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsx
line 5 to import from `@spaceorder/db`, preserving the existing symbol usage.

Source: Coding guidelines

apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsx (1)

13-25: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

synchronize 함수를 useCallback으로 memoize하는 것을 권장합니다.

synchronize는 렌더링마다 새로운 함수 참조를 생성합니다. useStoreOrderSyncDaemonuseEffect 의존성 배열에 onCreatedAction, onUpdatedAction, onCancelledAction이 포함되어 있어, OrderSyncDaemon이 리렌더링될 때마다 socket listener가 불필요하게 unsubscribe/subscribe됩니다.

board query가 invalidate되어 AwaitOrdersSummary가 리렌더링되는 시점(예: reconnect)에 이 cascade가 발생할 수 있으며, unsubscribe/subscribe 사이에 실시간 이벤트가 유실될 가능성이 있습니다.

♻️ Proposed fix
+import { useCallback } from "react";
+
 export default function OrderSyncDaemon() {
   const params = useParams<{ storeId: string }>();
   const queryClient = useQueryClient();
 
-  const synchronize = (event: OrderSyncEvent) => {
+  const synchronize = useCallback((event: OrderSyncEvent) => {
     queryClient.invalidateQueries({
       queryKey: pathToQueryKey(
         `/orders/v1/tables/${event.tablePublicId}/active-session`
       ),
     });
-  };
+  }, [queryClient]);
 
   useStoreOrderSyncDaemon(params.storeId, {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsx
around lines 13 - 25, OrderSyncDaemon의 synchronize 콜백을 useCallback으로 memoize하고,
내부에서 사용하는 queryClient를 의존성으로 포함하세요. useStoreOrderSyncDaemon에 전달되는
onCreatedAction, onUpdatedAction, onCancelledAction이 리렌더링마다 새로 생성되지 않도록 기존
synchronize 동작과 query key 생성을 유지하세요.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/AddMenuButton.tsx:
- Line 45: AddMenuButton의 가격 표시에서 transCurrencyFormat(price) 뒤에 붙는 “원”을 제거하고, 기존
“변경”/“추가” 문구는 그대로 유지하세요. 통화 기호는 transCurrencyFormat이 제공하는 값을 사용해 다른 사용처와 일관되게
표시합니다.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/category-list/CategoryList.tsx:
- Around line 39-42: Update the button in CategoryList’s menu rendering to
include disabled={!menu.isAvailable}, preserving the existing onClick guard so
unavailable menus cannot be focused or activated via keyboard.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsx:
- Around line 41-50: Remove the unnecessary e.preventDefault() call from the
catch block of tryPayment; keep setIsError(true) and the existing success
behavior unchanged.
- Around line 25-34: Update the payment function so the payment button invokes
the actual payment API endpoint instead of httpMe.fetchHealthCheck; preserve the
AbortSignal and event-prevention behavior, and use the payment API’s response
type. If no payment endpoint exists yet, separate this placeholder from the
payment action and ensure the UI does not present it as a successful payment.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/tableOrderColumns.tsx:
- Around line 68-76: Update the totalPrice column cell logic around editingData
and isSelected so the selected editing path also verifies editingData is truthy
before using editingData.totalPrice. Preserve the row.getValue fallback for
non-selected rows and ensure transCurrencyFormat always receives a number.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderAcceptAllButton.tsx:
- Line 30: Replace the useTransition-based loading in TableOrderAcceptAllButton
with a dedicated state such as isAccepting that remains true for the entire
async acceptance flow, including until Promise.all completes. Use this state to
keep the button disabled and spinner visible, and reset it after the mutations
finish so duplicate clicks cannot start another operation.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderCard.tsx:
- Around line 17-18: Update TableOrderCard to destructure storeId alongside
tableId from useParams, then build the order href/path using that storeId
instead of deriving a prefix with usePathname().split("orders"). Remove the
pathname-splitting dependency while preserving the existing tableId-based
routing.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderItem.tsx:
- Around line 38-41: Handle the rejection from
updateOrderByTableMutation.mutateAsync in onOrderStatusUpdate by attaching catch
handling consistent with TableOrderAcceptAllButton. Preserve the mutation’s
existing onError rollback and isError retry UI behavior while preventing an
unhandled promise rejection.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/TableOrderList.tsx:
- Around line 7-9: Rename the TableBoardProps type to TableOrderListProps and
update all references, including the TableOrderList component’s props
annotation, while preserving the existing sanitizedTable field and behavior.

---

Nitpick comments:
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableHeader.tsx:
- Line 16: Extract the duplicated grid column class into a shared constant in
the order-table directory, then update OrderTableHeader.tsx:16 and
OrderTableItem.tsx:18 to import and use it instead of hardcoding the class. Keep
both header and body layouts driven by the same constant.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailProvider.tsx:
- Around line 20-131: Extract the data fetching, mutation setup, state, derived
order data, and action functions from OrderDetailProvider into a dedicated
useOrderDetail(params) custom hook. Return the existing context value structure
from the hook, then keep OrderDetailProvider focused on invoking the hook and
passing its result to OrderDetailContext.Provider without changing behavior.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsx:
- Around line 19-75: Extract the payment state and async behavior from
PaymentDialogControlbar into a dedicated usePayment custom hook, including
isError, the health-check payment callback, useCancelableAsync integration,
tryPayment, abort, and isPending. Keep PaymentDialogControlbar focused on
rendering and consume the hook’s returned handlers and state while preserving
the existing success, failure, cancellation, and loading behavior.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsx:
- Around line 13-25: OrderSyncDaemon의 synchronize 콜백을 useCallback으로 memoize하고,
내부에서 사용하는 queryClient를 의존성으로 포함하세요. useStoreOrderSyncDaemon에 전달되는
onCreatedAction, onUpdatedAction, onCancelledAction이 리렌더링마다 새로 생성되지 않도록 기존
synchronize 동작과 query key 생성을 유지하세요.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.ts:
- Around line 2-6: Update imports in
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.ts
lines 2-6 so ActiveSessionResponse, OrderBoardByStore, and PublicTable come from
`@spaceorder/db` instead of its /types subpath. Also update OrderSyncEvent in
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsx
line 5 to import from `@spaceorder/db`, preserving the existing symbol usage.

In `@apps/console/src/lib/realtime/useStoreOrderSyncDaemon.ts`:
- Around line 21-47: Remove the unreachable `if (!storeId) return;` guard from
the second `useEffect` in the store order sync daemon, since `storeId` is typed
as `string`; leave the remaining effect logic unchanged.
🪄 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: 150d4d1a-e37c-4b86-865a-801b7a40c885

📥 Commits

Reviewing files that changed from the base of the PR and between 3451b1a and 83156ef.

📒 Files selected for processing (89)
  • apps/console/next.config.ts
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/components/PersistLastStore.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/dashboard/layout.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/dashboard/page.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/layout.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/menus/layout.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/menus/page.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/page.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/EmptyOrderDetail.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/TableOrderDetail.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/TableOrderDetailLayout.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailContent.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailContext.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailControlbar.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailFooter.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailPayment.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailProvider.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailTable.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/CreateOrderDialog.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/CreateOrderProvider.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/AddMenuButton.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/AddMenuDetail.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/OptionTags.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/added-menu-list/AddedMenu.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/added-menu-list/AddedMenuList.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/added-menu-list/CreateOrderButton.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/category-list/CategoryList.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/category-list/CategoryTabList.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/index.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableBody.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableControlbar.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableFrame.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableHeader.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableItem.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableOptions.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableRow.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/index.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/tableOrderColumns.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/AwaitOrdersSummary.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/GridLayout.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/OrdersPageLayout.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-board/TableBoard.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/TableOrderList.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/TableOrderListLayout.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderAcceptAllButton.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderCard.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderContent.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderFooter.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderHeader.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderItem.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderOrderList.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderQrCode.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/index.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/hooks/useSetCacheByStoreBoard.ts
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/layout.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/page.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.ts
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/tables/layout.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/tables/page.tsx
  • apps/console/src/app/(navigator)/(sidebar)/components/HeaderLinkButton.tsx
  • apps/console/src/app/(navigator)/(sidebar)/components/MainLayout.tsx
  • apps/console/src/app/(navigator)/(sidebar)/components/NavSidebar.tsx
  • apps/console/src/app/(navigator)/(sidebar)/components/PageTitle.tsx
  • apps/console/src/app/(navigator)/(sidebar)/components/SidebarFooterLayout.tsx
  • apps/console/src/app/(navigator)/(sidebar)/components/SidebarItemGroups.tsx
  • apps/console/src/app/(navigator)/(sidebar)/components/StoreEntryRedirect.tsx
  • apps/console/src/app/(navigator)/(sidebar)/components/StoreSwitcher.tsx
  • apps/console/src/app/(navigator)/(sidebar)/components/UserName.tsx
  • apps/console/src/app/(navigator)/(sidebar)/layout.tsx
  • apps/console/src/app/(navigator)/(sidebar)/loading.tsx
  • apps/console/src/app/(navigator)/(sidebar)/page.tsx
  • apps/console/src/app/(navigator)/(sidebar)/stores/page.tsx
  • apps/console/src/app/(navigator)/components/UserNameDropDown.tsx
  • apps/console/src/app/(navigator)/layout.tsx
  • apps/console/src/app/(navigator)/signin/components/form-card/FormCard.tsx
  • apps/console/src/app/common/servers/getStores.ts
  • apps/console/src/app/page.tsx
  • apps/console/src/components/realtime/OrderNoticeDaemon.tsx
  • apps/console/src/lib/realtime/useStoreOrderSyncDaemon.ts
  • apps/console/src/shared/config/sidebarGroup.ts
  • apps/console/src/shared/config/sidebarGroup.type.ts
  • apps/console/src/shared/config/sidebarList.ts
  • packages/db/constants/cookieTable.const.ts
  • packages/db/constants/index.ts
  • packages/db/constants/localStorage.const.ts
  • packages/ui/src/components/buttons/TouchEventButton.tsx
  • packages/ui/src/components/layouts/sidebar.tsx
💤 Files with no reviewable changes (6)
  • apps/console/src/shared/config/sidebarList.ts
  • apps/console/src/app/page.tsx
  • packages/db/constants/localStorage.const.ts
  • packages/db/constants/index.ts
  • apps/console/next.config.ts
  • apps/console/src/app/(navigator)/(sidebar)/stores/page.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 9

🧹 Nitpick comments (6)
apps/console/src/lib/realtime/useStoreOrderSyncDaemon.ts (1)

21-47: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

storeId 타입을 string으로 좁힌 후 잔존한 dead code guard를 제거해 주세요.

storeId parameter 타입이 string | undefined에서 string으로 변경되었으므로, Line 47의 if (!storeId) return; guard는 더 이상 도달할 수 없는 분기입니다. 혼란을 방지하기 위해 제거를 권장합니다.

♻️ 제안: 불필요한 guard 제거
   useEffect(() => {
-    if (!storeId) return;
-
     const socket = getRealtimeSocket();
     subscribeAdmin(storeId);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/console/src/lib/realtime/useStoreOrderSyncDaemon.ts` around lines 21 -
47, Remove the unreachable `if (!storeId) return;` guard from the second
`useEffect` in the store order sync daemon, since `storeId` is typed as
`string`; leave the remaining effect logic unchanged.
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsx (1)

19-75: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

결제 로직을 custom hook으로 추출하는 것을 권장합니다.

PaymentDialogControlbar에 상태 관리(isError), 비동기 결제 로직(payment, tryPayment), abort 처리가 혼재되어 있습니다. coding guidelines에 따라 복잡한 로직은 custom hook으로 분리하는 것을 권장합니다.

As per coding guidelines, apps/{order,console}/src/**/*.{ts,tsx}: "suggest custom hooks for complex logic when appropriate".

♻️ 제안: usePayment hook 추출
// usePayment.ts
export function usePayment(setOpen: (value: SetStateAction<boolean>) => void) {
  const [isError, setIsError] = useState(false);

  const payment = async (
    signal: AbortSignal,
    e: React.MouseEvent<HTMLButtonElement>
  ) => {
    e.preventDefault();
    const result: HealthCheckResponse = await httpMe.fetchHealthCheck({ signal });
    return result;
  };

  const paymentTransaction = useCancelableAsync<
    [React.MouseEvent<HTMLButtonElement>],
    HealthCheckResponse
  >(payment);

  const tryPayment = async (e: React.MouseEvent<HTMLButtonElement>) => {
    try {
      setIsError(false);
      await paymentTransaction(e);
      setOpen(false);
    } catch {
      setIsError(true);
    }
  };

  return { tryPayment, abort: paymentTransaction.abort, isPending: paymentTransaction.isPending, isError };
}
 export default function PaymentDialogControlbar({
   children,
   setOpen,
 }: PaymentDialogControlbarProps) {
-  const [isError, setIsError] = useState(false);
-
-  const payment = async (...) => { ... };
-  const paymentTransaction = useCancelableAsync(...);
-  const tryPayment = async (...) => { ... };
+  const { tryPayment, abort, isPending, isError } = usePayment(setOpen);

   return (
     <>
-      <AlertDialogCancel onClick={paymentTransaction.abort}>
+      <AlertDialogCancel onClick={abort}>
         취소
       </AlertDialogCancel>
       <AlertDialogAction
         onClick={tryPayment}
         className={isError ? "..." : ""}
-        disabled={paymentTransaction.isPending}
+        disabled={isPending}
       >
         <ActivityRender
-          value={!paymentTransaction.isPending}
+          value={!isPending}
           fallback={<LoadingTransaction />}
         >
           {() => (isError ? "결제 실패, 다시 시도" : children)}
         </ActivityRender>
       </AlertDialogAction>
     </>
   );
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsx
around lines 19 - 75, Extract the payment state and async behavior from
PaymentDialogControlbar into a dedicated usePayment custom hook, including
isError, the health-check payment callback, useCancelableAsync integration,
tryPayment, abort, and isPending. Keep PaymentDialogControlbar focused on
rendering and consume the hook’s returned handlers and state while preserving
the existing success, failure, cancellation, and loading behavior.

Source: Coding guidelines

apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailProvider.tsx (1)

20-131: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

복잡한 로직을 custom hook으로 분리하는 것을 권장합니다.

OrderDetailProvider는 data fetching, mutation 설정, state 관리, derived data 계산, 5개의 action 함수를 포함하고 있습니다. 이 로직을 useOrderDetail(params) 같은 custom hook으로 분리하면 provider가 간결해지고, 비즈니스 로직의 testability가 향상됩니다.

As per coding guidelines, apps/{order,console}/src/**/*.{ts,tsx} 경로의 파일에서는 복잡한 로직을 적절한 custom hook으로 분리할 것을 권장합니다.

♻️ 제안하는 custom hook 분리 구조
+// useOrderDetail.ts
+export function useOrderDetail(params: { storeId: string; tableId: string }) {
+  const { storeId, tableId } = params;
+  const fetchUrl = `/orders/v1/tables/${tableId}/active-session`;
+  const { data: session, isRefetching } =
+    useSuspenseWithAuth<ActiveSessionResponse>(fetchUrl);
+  const { update, remove } = useOrderItem({ storeId, tableId });
+  const [editingItem, setEditingItem] = useState<OrderItemWithOrder | null>(null);
+  // ... derived data, actions ...
+  return { state, actions, meta };
+}
+
+// OrderDetailProvider.tsx
 export function OrderDetailProvider({ params, children }: OrderDetailProviderProps) {
-  const { storeId, tableId } = params;
-  const fetchUrl = `/orders/v1/tables/${tableId}/active-session`;
-  // ... 모든 로직 ...
+  const contextValue = useOrderDetail(params);
   return (
     <OrderDetailContext.Provider value={contextValue}>
       {children}
     </OrderDetailContext.Provider>
   );
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailProvider.tsx
around lines 20 - 131, Extract the data fetching, mutation setup, state, derived
order data, and action functions from OrderDetailProvider into a dedicated
useOrderDetail(params) custom hook. Return the existing context value structure
from the hook, then keep OrderDetailProvider focused on invoking the hook and
passing its result to OrderDetailContext.Provider without changing behavior.

Source: Coding guidelines

apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableHeader.tsx (1)

16-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Grid column 클래스가 두 파일에 중복 정의되어 있습니다.

grid grid-cols-[1.5fr_1fr_1fr] xl:grid-cols-[2fr_1fr_1fr] 클래스가 OrderTableHeader.tsxTableRowOrderTableItem.tsxTableCell에 각각 하드코딩되어 있습니다. 어느 한쪽만 변경되면 헤더와 바디 컬럼 정렬이 깨집니다.

  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableHeader.tsx#L16-L16: 공통 상수를 import하여 사용하도록 변경
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableItem.tsx#L18-L18: 동일한 공통 상수를 import하여 사용하도록 변경
♻️ 제안: 공통 상수 추출

order-table/ 디렉토리에 공통 상수를 추가하고 양쪽에서 import하여 사용:

+// order-table/constants.ts
+export const ORDER_TABLE_GRID_CLASS =
+  "grid grid-cols-[1.5fr_1fr_1fr] xl:grid-cols-[2fr_1fr_1fr]";

OrderTableHeader.tsx:

-          className="grid grid-cols-[1.5fr_1fr_1fr] xl:grid-cols-[2fr_1fr_1fr]"
+          className={ORDER_TABLE_GRID_CLASS}

OrderTableItem.tsx:

-    <TableCell className="grid grid-cols-[1.5fr_1fr_1fr] xl:grid-cols-[2fr_1fr_1fr] cursor-pointer min-h-16 animate-fade-in-up">
+    <TableCell className={`${ORDER_TABLE_GRID_CLASS} cursor-pointer min-h-16 animate-fade-in-up`}>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableHeader.tsx
at line 16, Extract the duplicated grid column class into a shared constant in
the order-table directory, then update OrderTableHeader.tsx:16 and
OrderTableItem.tsx:18 to import and use it instead of hardcoding the class. Keep
both header and body layouts driven by the same constant.
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.ts (1)

2-6: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

@spaceorder/db/types 대신 @spaceorder/db에서 database types를 import해야 합니다. coding guideline에 따르면 database types는 @spaceorder/db 패키지에서 import해야 하지만, 두 파일 모두 @spaceorder/db/types subpath에서 import하고 있습니다. layout.tsx@spaceorder/db에서 OrderBoardByStore를 import하고 있어 일관성이 깨집니다.

  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.ts#L2-L6: ActiveSessionResponse, OrderBoardByStore, PublicTable@spaceorder/db에서 import하도록 변경
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsx#L5-L5: OrderSyncEvent@spaceorder/db에서 import하도록 변경
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.ts
around lines 2 - 6, Update imports in
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.ts
lines 2-6 so ActiveSessionResponse, OrderBoardByStore, and PublicTable come from
`@spaceorder/db` instead of its /types subpath. Also update OrderSyncEvent in
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsx
line 5 to import from `@spaceorder/db`, preserving the existing symbol usage.

Source: Coding guidelines

apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsx (1)

13-25: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

synchronize 함수를 useCallback으로 memoize하는 것을 권장합니다.

synchronize는 렌더링마다 새로운 함수 참조를 생성합니다. useStoreOrderSyncDaemonuseEffect 의존성 배열에 onCreatedAction, onUpdatedAction, onCancelledAction이 포함되어 있어, OrderSyncDaemon이 리렌더링될 때마다 socket listener가 불필요하게 unsubscribe/subscribe됩니다.

board query가 invalidate되어 AwaitOrdersSummary가 리렌더링되는 시점(예: reconnect)에 이 cascade가 발생할 수 있으며, unsubscribe/subscribe 사이에 실시간 이벤트가 유실될 가능성이 있습니다.

♻️ Proposed fix
+import { useCallback } from "react";
+
 export default function OrderSyncDaemon() {
   const params = useParams<{ storeId: string }>();
   const queryClient = useQueryClient();
 
-  const synchronize = (event: OrderSyncEvent) => {
+  const synchronize = useCallback((event: OrderSyncEvent) => {
     queryClient.invalidateQueries({
       queryKey: pathToQueryKey(
         `/orders/v1/tables/${event.tablePublicId}/active-session`
       ),
     });
-  };
+  }, [queryClient]);
 
   useStoreOrderSyncDaemon(params.storeId, {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsx
around lines 13 - 25, OrderSyncDaemon의 synchronize 콜백을 useCallback으로 memoize하고,
내부에서 사용하는 queryClient를 의존성으로 포함하세요. useStoreOrderSyncDaemon에 전달되는
onCreatedAction, onUpdatedAction, onCancelledAction이 리렌더링마다 새로 생성되지 않도록 기존
synchronize 동작과 query key 생성을 유지하세요.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/AddMenuButton.tsx:
- Line 45: AddMenuButton의 가격 표시에서 transCurrencyFormat(price) 뒤에 붙는 “원”을 제거하고, 기존
“변경”/“추가” 문구는 그대로 유지하세요. 통화 기호는 transCurrencyFormat이 제공하는 값을 사용해 다른 사용처와 일관되게
표시합니다.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/category-list/CategoryList.tsx:
- Around line 39-42: Update the button in CategoryList’s menu rendering to
include disabled={!menu.isAvailable}, preserving the existing onClick guard so
unavailable menus cannot be focused or activated via keyboard.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsx:
- Around line 41-50: Remove the unnecessary e.preventDefault() call from the
catch block of tryPayment; keep setIsError(true) and the existing success
behavior unchanged.
- Around line 25-34: Update the payment function so the payment button invokes
the actual payment API endpoint instead of httpMe.fetchHealthCheck; preserve the
AbortSignal and event-prevention behavior, and use the payment API’s response
type. If no payment endpoint exists yet, separate this placeholder from the
payment action and ensure the UI does not present it as a successful payment.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/tableOrderColumns.tsx:
- Around line 68-76: Update the totalPrice column cell logic around editingData
and isSelected so the selected editing path also verifies editingData is truthy
before using editingData.totalPrice. Preserve the row.getValue fallback for
non-selected rows and ensure transCurrencyFormat always receives a number.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderAcceptAllButton.tsx:
- Line 30: Replace the useTransition-based loading in TableOrderAcceptAllButton
with a dedicated state such as isAccepting that remains true for the entire
async acceptance flow, including until Promise.all completes. Use this state to
keep the button disabled and spinner visible, and reset it after the mutations
finish so duplicate clicks cannot start another operation.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderCard.tsx:
- Around line 17-18: Update TableOrderCard to destructure storeId alongside
tableId from useParams, then build the order href/path using that storeId
instead of deriving a prefix with usePathname().split("orders"). Remove the
pathname-splitting dependency while preserving the existing tableId-based
routing.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderItem.tsx:
- Around line 38-41: Handle the rejection from
updateOrderByTableMutation.mutateAsync in onOrderStatusUpdate by attaching catch
handling consistent with TableOrderAcceptAllButton. Preserve the mutation’s
existing onError rollback and isError retry UI behavior while preventing an
unhandled promise rejection.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/TableOrderList.tsx:
- Around line 7-9: Rename the TableBoardProps type to TableOrderListProps and
update all references, including the TableOrderList component’s props
annotation, while preserving the existing sanitizedTable field and behavior.

---

Nitpick comments:
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableHeader.tsx:
- Line 16: Extract the duplicated grid column class into a shared constant in
the order-table directory, then update OrderTableHeader.tsx:16 and
OrderTableItem.tsx:18 to import and use it instead of hardcoding the class. Keep
both header and body layouts driven by the same constant.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailProvider.tsx:
- Around line 20-131: Extract the data fetching, mutation setup, state, derived
order data, and action functions from OrderDetailProvider into a dedicated
useOrderDetail(params) custom hook. Return the existing context value structure
from the hook, then keep OrderDetailProvider focused on invoking the hook and
passing its result to OrderDetailContext.Provider without changing behavior.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsx:
- Around line 19-75: Extract the payment state and async behavior from
PaymentDialogControlbar into a dedicated usePayment custom hook, including
isError, the health-check payment callback, useCancelableAsync integration,
tryPayment, abort, and isPending. Keep PaymentDialogControlbar focused on
rendering and consume the hook’s returned handlers and state while preserving
the existing success, failure, cancellation, and loading behavior.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsx:
- Around line 13-25: OrderSyncDaemon의 synchronize 콜백을 useCallback으로 memoize하고,
내부에서 사용하는 queryClient를 의존성으로 포함하세요. useStoreOrderSyncDaemon에 전달되는
onCreatedAction, onUpdatedAction, onCancelledAction이 리렌더링마다 새로 생성되지 않도록 기존
synchronize 동작과 query key 생성을 유지하세요.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.ts:
- Around line 2-6: Update imports in
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.ts
lines 2-6 so ActiveSessionResponse, OrderBoardByStore, and PublicTable come from
`@spaceorder/db` instead of its /types subpath. Also update OrderSyncEvent in
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsx
line 5 to import from `@spaceorder/db`, preserving the existing symbol usage.

In `@apps/console/src/lib/realtime/useStoreOrderSyncDaemon.ts`:
- Around line 21-47: Remove the unreachable `if (!storeId) return;` guard from
the second `useEffect` in the store order sync daemon, since `storeId` is typed
as `string`; leave the remaining effect logic unchanged.
🪄 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: 150d4d1a-e37c-4b86-865a-801b7a40c885

📥 Commits

Reviewing files that changed from the base of the PR and between 3451b1a and 83156ef.

📒 Files selected for processing (89)
  • apps/console/next.config.ts
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/components/PersistLastStore.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/dashboard/layout.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/dashboard/page.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/layout.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/menus/layout.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/menus/page.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/page.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/EmptyOrderDetail.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/TableOrderDetail.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/TableOrderDetailLayout.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailContent.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailContext.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailControlbar.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailFooter.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailPayment.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailProvider.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailTable.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/CreateOrderDialog.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/CreateOrderProvider.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/AddMenuButton.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/AddMenuDetail.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/OptionTags.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/added-menu-list/AddedMenu.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/added-menu-list/AddedMenuList.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/added-menu-list/CreateOrderButton.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/category-list/CategoryList.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/category-list/CategoryTabList.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/index.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableBody.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableControlbar.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableFrame.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableHeader.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableItem.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableOptions.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableRow.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/index.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/tableOrderColumns.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/AwaitOrdersSummary.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/GridLayout.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/OrdersPageLayout.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-board/TableBoard.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/TableOrderList.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/TableOrderListLayout.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderAcceptAllButton.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderCard.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderContent.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderFooter.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderHeader.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderItem.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderOrderList.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderQrCode.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/index.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/hooks/useSetCacheByStoreBoard.ts
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/layout.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/page.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.ts
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/tables/layout.tsx
  • apps/console/src/app/(navigator)/(sidebar)/[storeId]/tables/page.tsx
  • apps/console/src/app/(navigator)/(sidebar)/components/HeaderLinkButton.tsx
  • apps/console/src/app/(navigator)/(sidebar)/components/MainLayout.tsx
  • apps/console/src/app/(navigator)/(sidebar)/components/NavSidebar.tsx
  • apps/console/src/app/(navigator)/(sidebar)/components/PageTitle.tsx
  • apps/console/src/app/(navigator)/(sidebar)/components/SidebarFooterLayout.tsx
  • apps/console/src/app/(navigator)/(sidebar)/components/SidebarItemGroups.tsx
  • apps/console/src/app/(navigator)/(sidebar)/components/StoreEntryRedirect.tsx
  • apps/console/src/app/(navigator)/(sidebar)/components/StoreSwitcher.tsx
  • apps/console/src/app/(navigator)/(sidebar)/components/UserName.tsx
  • apps/console/src/app/(navigator)/(sidebar)/layout.tsx
  • apps/console/src/app/(navigator)/(sidebar)/loading.tsx
  • apps/console/src/app/(navigator)/(sidebar)/page.tsx
  • apps/console/src/app/(navigator)/(sidebar)/stores/page.tsx
  • apps/console/src/app/(navigator)/components/UserNameDropDown.tsx
  • apps/console/src/app/(navigator)/layout.tsx
  • apps/console/src/app/(navigator)/signin/components/form-card/FormCard.tsx
  • apps/console/src/app/common/servers/getStores.ts
  • apps/console/src/app/page.tsx
  • apps/console/src/components/realtime/OrderNoticeDaemon.tsx
  • apps/console/src/lib/realtime/useStoreOrderSyncDaemon.ts
  • apps/console/src/shared/config/sidebarGroup.ts
  • apps/console/src/shared/config/sidebarGroup.type.ts
  • apps/console/src/shared/config/sidebarList.ts
  • packages/db/constants/cookieTable.const.ts
  • packages/db/constants/index.ts
  • packages/db/constants/localStorage.const.ts
  • packages/ui/src/components/buttons/TouchEventButton.tsx
  • packages/ui/src/components/layouts/sidebar.tsx
💤 Files with no reviewable changes (6)
  • apps/console/src/shared/config/sidebarList.ts
  • apps/console/src/app/page.tsx
  • packages/db/constants/localStorage.const.ts
  • packages/db/constants/index.ts
  • apps/console/next.config.ts
  • apps/console/src/app/(navigator)/(sidebar)/stores/page.tsx
🛑 Comments failed to post (9)
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/AddMenuButton.tsx (1)

45-45: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

What is the output of new Intl.NumberFormat("ko-KR", { currency: "KRW", style: "currency" }).format(10000) in modern JavaScript engines?

💡 Result:

The output of the command new Intl.NumberFormat("ko-KR", { currency: "KRW", style: "currency" }).format(10000) in modern JavaScript engines is "₩10,000" [1][2][3]. Because the Korean Won (KRW) does not use minor currency units (i.e., it has no fractional subunits like cents), the Intl.NumberFormat API automatically excludes decimal places when formatting for the ko-KR locale [4][1][5]. The resulting string includes the Korean Won symbol (₩) followed by the formatted number with a thousands separator [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n[files]\n'
git ls-files | rg 'transCurrencyFormat|CreateOrderButton|AddMenuButton'

printf '\n[search]\n'
rg -n "transCurrencyFormat|원 -|주문 생성|변경|추가" apps/console/src -g '!**/node_modules/**'

printf '\n[file: transCurrencyFormat implementation]\n'
file=$(rg -n "function transCurrencyFormat|const transCurrencyFormat|export .*transCurrencyFormat" -l apps/console/src packages 2>/dev/null | head -n 1 || true)
if [ -n "${file:-}" ]; then
  echo "$file"
  wc -l "$file"
  sed -n '1,220p' "$file"
fi

printf '\n[file: AddMenuButton.tsx]\n'
sed -n '1,120p' 'apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/AddMenuButton.tsx'

printf '\n[file: CreateOrderButton.tsx]\n'
sed -n '1,120p' 'apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/CreateOrderButton.tsx'

Repository: kisn3089/ssurak

Length of output: 8333


을 제거해 주세요

  • transCurrencyFormat(price)는 이미 ₩10,000처럼 currency symbol을 포함합니다.
  • 여기에 을 붙이면 ₩10,000원으로 단위가 중복됩니다.
  • 다른 사용처와도 동일하게 맞추는 게 좋습니다.
수정 예시
-        {`${transCurrencyFormat(price)}원 - ${isEditing ? "변경" : "추가"}`}
+        {`${transCurrencyFormat(price)} - ${isEditing ? "변경" : "추가"}`}
📝 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.

        {`${transCurrencyFormat(price)} - ${isEditing ? "변경" : "추가"}`}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/AddMenuButton.tsx
at line 45, AddMenuButton의 가격 표시에서 transCurrencyFormat(price) 뒤에 붙는 “원”을 제거하고,
기존 “변경”/“추가” 문구는 그대로 유지하세요. 통화 기호는 transCurrencyFormat이 제공하는 값을 사용해 다른 사용처와 일관되게
표시합니다.
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/category-list/CategoryList.tsx (1)

39-42: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

이용 불가능한 메뉴의 button에 disabled 속성을 추가해 주세요.

menu.isAvailable이 false일 때 Card에 pointer-events-none이 적용되어 마우스 클릭은 차단되지만, 키보드 사용자는 여전히 button에 focus하고 활성화할 수 있습니다. disabled={!menu.isAvailable}을 추가하면 키보드 접근성도 함께 차단됩니다.

♿ 제안하는 접근성 개선
                     <button
                       className="rounded-3xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
-                      onClick={() => menu.isAvailable && selectMenu(menu)}
+                      type="button"
+                      disabled={!menu.isAvailable}
+                      onClick={() => selectMenu(menu)}
                     >
📝 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="rounded-3xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
                      type="button"
                      disabled={!menu.isAvailable}
                      onClick={() => selectMenu(menu)}
                    >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/category-list/CategoryList.tsx
around lines 39 - 42, Update the button in CategoryList’s menu rendering to
include disabled={!menu.isAvailable}, preserving the existing onClick guard so
unavailable menus cannot be focused or activated via keyboard.
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsx (2)

25-34: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: fetchHealthCheck의 정의와 실제 결제 API 존재 여부를 확인합니다.
rg -n "fetchHealthCheck" packages/api/ --type=ts
rg -n "payment\|pay\|checkout" packages/api/ --type=ts -i -l

Repository: kisn3089/ssurak

Length of output: 292


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the API helper and the UI caller.
sed -n '1,120p' packages/api/core/identity/me/httpMe.ts
printf '\n---\n'
sed -n '1,220p' 'apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsx'
printf '\n---\n'

# Find payment/checkout/order-related API helpers in packages/api.
rg -n --hidden --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' \
  "payment|checkout|pay|order" packages/api -i

Repository: kisn3089/ssurak

Length of output: 27953


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 결제 상태를 다루는 실제 session/order API가 있는지 확인합니다.
rg -n --hidden --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' \
  "updateSessionPaymentSchema|PAYMENT_PENDING|updateCustomerSessionPayloadSchema|updateSessionPayloadSchema|tableSession|session.*payment|payment.*session" packages/api apps -i

printf '\n---\n'

# session 관련 HTTP helper가 있는지 확인합니다.
fd -a 'http*session*.ts' packages/api
printf '\n---\n'
fd -a '*session*.ts' packages/api/core

Repository: kisn3089/ssurak

Length of output: 21524


결제 버튼에서는 health check가 아니라 실제 payment API를 호출해 주세요.

  • httpMe.fetchHealthCheck()/ health check용입니다.
  • 현재 구현은 결제 성공처럼 보여도 실제 결제 처리가 되지 않으므로, 결제 endpoint로 교체하거나 placeholder라면 UI/이름을 분리해 주세요.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsx
around lines 25 - 34, Update the payment function so the payment button invokes
the actual payment API endpoint instead of httpMe.fetchHealthCheck; preserve the
AbortSignal and event-prevention behavior, and use the payment API’s response
type. If no payment endpoint exists yet, separate this placeholder from the
payment action and ensure the UI does not present it as a successful payment.

41-50: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

catch 블록의 e.preventDefault()는 dead code입니다.

tryPayment에서 await paymentTransaction(e) 이후에 호출되는 e.preventDefault()는 이미 event가 dispatch된 시점이므로 아무 효과가 없습니다. 성공 시 setOpen(false)가 실행되지 않아 dialog가 열려있는 것은 정상 동작이지만, 이 preventDefault 호출이 그 원인이 아닙니다.

🧹 제안: 불필요한 preventDefault 제거
   const tryPayment = async (e: React.MouseEvent<HTMLButtonElement>) => {
     try {
       setIsError(false);
       await paymentTransaction(e);
       setOpen(false);
     } catch {
-      e.preventDefault();
       setIsError(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.

  const tryPayment = async (e: React.MouseEvent<HTMLButtonElement>) => {
    try {
      setIsError(false);
      await paymentTransaction(e);
      setOpen(false);
    } catch {
      setIsError(true);
    }
  };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsx
around lines 41 - 50, Remove the unnecessary e.preventDefault() call from the
catch block of tryPayment; keep setIsError(true) and the existing success
behavior unchanged.
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/tableOrderColumns.tsx (1)

68-76: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

totalPrice 컬럼에서 editingData 타입 narrowing이 누락되었습니다.

quantity 컬럼(라인 33)에서는 isSelected && meta.editingData를 사용하여 타입을 올바르게 좁히지만, totalPrice 컬럼(라인 71)에서는 isSelected만 확인하여 editingData?.totalPricenumber | undefined가 됩니다. transCurrencyFormatnumber만 받으므로 strict mode에서 type error가 발생합니다.

🐛 제안하는 수정
       const price = isSelected
-        ? editingData?.totalPrice
+        ? editingData?.totalPrice
         : row.getValue<number>("totalPrice");

isSelected 조건에 editingData truthy check를 추가하여 타입을 좁혀 주세요:

-      const isSelected = editingData?.publicId === row.original.publicId;
-      const price = isSelected
-        ? editingData?.totalPrice
+      const isSelected = editingData?.publicId === row.original.publicId;
+      const price = isSelected && editingData
+        ? editingData.totalPrice
         : row.getValue<number>("totalPrice");
📝 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.

    cell: ({ row, table }) => {
      const editingData = table.options.meta?.editingData;
      const isSelected = editingData?.publicId === row.original.publicId;
      const price = isSelected && editingData
        ? editingData.totalPrice
        : row.getValue<number>("totalPrice");
      const formatted = transCurrencyFormat(price);

      return <div className="text-right w-full">{formatted}</div>;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/tableOrderColumns.tsx
around lines 68 - 76, Update the totalPrice column cell logic around editingData
and isSelected so the selected editing path also verifies editingData is truthy
before using editingData.totalPrice. Preserve the row.getValue fallback for
non-selected rows and ensure transCurrencyFormat always receives a number.
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderAcceptAllButton.tsx (1)

30-30: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: React 18.3 useTransition이 async callback을 지원하는지 확인합니다.
# 1. React 버전 확인
cat node_modules/react/package.json | jq '.version'

# 2. useTransition 타입 시그니처 확인
rg -n 'startTransition' node_modules/@types/react/index.d.ts | head -20

# 3. 프로젝트 내 startTransition + async 사용 패턴 확인
rg -nP 'startTransition\(async' --type ts apps/ packages/

Repository: kisn3089/ssurak

Length of output: 546


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 대상 파일과 관련 훅의 구조/해당 구간 확인
ast-grep outline 'apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderAcceptAllButton.tsx' --view expanded
echo '---'
ast-grep outline 'packages/api/hooks/useCancelableAsync.ts' --view expanded

echo '--- FILE: TableOrderAcceptAllButton.tsx (1-220)'
sed -n '1,220p' 'apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderAcceptAllButton.tsx'

echo '--- FILE: useCancelableAsync.ts (1-220)'
sed -n '1,220p' 'packages/api/hooks/useCancelableAsync.ts'

Repository: kisn3089/ssurak

Length of output: 5759


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# React/Next 버전과 관련 파일에서 async transition 사용 맥락 확인
rg -n '"react"|react@|next@|useTransition|startTransition\(async' package.json pnpm-lock.yaml yarn.lock package-lock.json apps packages -g '!**/dist/**' -g '!**/build/**'

echo '--- package.json (top-level)'
sed -n '1,220p' package.json 2>/dev/null || true

echo '--- useCancelableAsync references'
rg -n 'useCancelableAsync\(' apps packages

Repository: kisn3089/ssurak

Length of output: 39225


🌐 Web query:

React startTransition async callback isPending await promise official docs

💡 Result:

In React 19, you can pass an asynchronous function (an "Action") to the startTransition function provided by the useTransition hook, which allows React to automatically track the pending state [1][2]. When you use startTransition with an async callback: 1. The isPending flag from useTransition becomes true immediately when the transition starts [3][1]. 2. It remains true until the asynchronous action completes and all triggered state updates are processed [3][1][2]. 3. You do not need to manually await the promise returned by the Action function inside the component logic for the transition to be tracked; simply passing the async function to startTransition is sufficient [1][2]. Important considerations: - Standalone startTransition vs. useTransition: While the useTransition hook's startTransition function handles async actions as described, the standalone startTransition function (imported from react) behaves differently and does not provide an isPending flag [4][5]. - State updates after await: In some cases, especially when using the standalone startTransition or complex asynchronous flows, state updates occurring after an await in the action might not be automatically included in the transition [1][6][2]. If you notice these updates are not marked as part of the transition, you may need to wrap those specific state updates in an additional startTransition call [1][6][5]. - Async Context: React's current ability to group updates across async boundaries is an implementation that works in many scenarios but may occasionally require explicit wrapping until future platform features like Async Context are fully adopted [3][6]. In summary, for most standard use cases in React 19, passing an async function to the startTransition provided by useTransition is the recommended way to handle pending states without manual loading flags [1][2].

Citations:


🌐 Web query:

site:react.dev startTransition async function isPending promise React 18.3

💡 Result:

In React, startTransition and useTransition are used to mark state updates as non-urgent transitions, allowing them to be interrupted by urgent updates like user input [1][2]. Key differences and behaviors: startTransition: This is a standalone function used when you cannot use the useTransition hook (e.g., outside of a component) [2]. It does not provide an isPending flag to track the transition status [3]. useTransition: This is a hook that returns both a startTransition function and an isPending boolean [4][5]. The isPending flag is automatically set to true when the transition starts and back to false once all transitions triggered by that call—including any updates scheduled synchronously—are completed [4][6]. Handling Async Functions: React's startTransition (and the one returned by useTransition) expects a synchronous function [2]. If you pass an async function to startTransition, React only marks the synchronous state updates scheduled immediately during the initial function call as transitions [4][2]. Any state updates that occur after an await statement (asynchronously) will not be automatically marked as transitions [7][2]. To correctly handle async updates: 1. You should perform the asynchronous work (e.g., fetching data) first [2]. 2. Once the asynchronous work is complete, you then call startTransition to wrap the resulting state updates [7][2]. 3. Alternatively, if you must use an async function inside a transition, you must explicitly wrap any state updates that happen after the await in an additional startTransition call [7]. Note: In newer versions of React (like React 19), async transitions are more natively supported, allowing the isPending state to correctly track the duration of the async request until the final state is rendered [6]. However, in standard React 18.x usage, the synchronous limitation for state updates remains the standard pattern [7][2].

Citations:


startTransition(async () => ...) 대신 별도 loading state를 사용해 주세요.

  • 이 패턴은 React 18.3.1에서 async 작업 전체를 isPending으로 묶어주지 않아서, Promise.all이 끝나기 전에 버튼이 다시 활성화될 수 있습니다.
  • 그 경우 spinner가 먼저 사라지고 중복 클릭으로 mutation이 반복될 수 있습니다.
  • Promise.all 완료까지 isAccepting 같은 useState로 버튼을 잠그거나, async 작업은 transition 밖에서 처리하고 마지막 setStatestartTransition으로 감싸 주세요.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderAcceptAllButton.tsx
at line 30, Replace the useTransition-based loading in TableOrderAcceptAllButton
with a dedicated state such as isAccepting that remains true for the entire
async acceptance flow, including until Promise.all completes. Use this state to
keep the button disabled and spinner visible, and reset it after the mutations
finish so duplicate clicks cannot start another operation.
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderCard.tsx (1)

17-18: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

usePathname().split("orders") 대신 useParamsstoreId를 직접 사용하는 것을 권장합니다.

split("orders")storeId에 "orders"가 부분 문자열로 포함된 경우 잘못된 prefix를 생성합니다. 예: pathname이 /myorders-store/orders/table-1인 경우 split("orders")["/my", "-store/", "/table-1"]pathname = "/my" → href가 /myorders/table-1이 되어 라우팅이 깨집니다.

storeIduseParams에서 직접 가져와 href를 구성하면 더 단순하고 견고해집니다.

♻️ Proposed fix
-import { useParams, usePathname } from "next/navigation";
+import { useParams } from "next/navigation";
-  const { tableId } = useParams<{ tableId: string }>();
-  const [pathname] = usePathname().split("orders");
+  const { storeId, tableId } = useParams<{ storeId: string; tableId: string }>();
-      href={`${pathname}orders/${sanitizedTable.publicId}`}
+      href={`/${storeId}/orders/${sanitizedTable.publicId}`}

Also applies to: 33-33

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderCard.tsx
around lines 17 - 18, Update TableOrderCard to destructure storeId alongside
tableId from useParams, then build the order href/path using that storeId
instead of deriving a prefix with usePathname().split("orders"). Remove the
pathname-splitting dependency while preserving the existing tableId-based
routing.
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderItem.tsx (1)

38-41: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

mutateAsync 호출의 에러 처리가 필요합니다.

onOrderStatusUpdate에서 mutateAsync의 rejection이 처리되지 않아 unhandled promise rejection이 발생합니다. mutation의 onError에서 캐시 롤백은 처리되고 isError로 retry UI가 표시되지만, console에 에러가 출력됩니다.

TableOrderAcceptAllButton에서는 각 mutateAsync.catch()를 적용한 것과 일관성을 맞추는 것도 좋습니다.

🔧 제안하는 수정
   const onOrderStatusUpdate = async (
     e: React.MouseEvent<HTMLButtonElement>
   ) => {
     e.preventDefault();
     const nextStatus = nextStatusMap[order.status];
     if (!nextStatus) {
       return;
     }

-    await updateOrderByTableMutation.mutateAsync({
+    try {
+      await updateOrderByTableMutation.mutateAsync({
         orderId: order.publicId,
         updateOrderPayload: { status: nextStatus },
       });
+    } catch {
+      // mutation의 onError에서 캐시 롤백 및 isError 상태 처리됨
+    }
   };
📝 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.

    try {
      await updateOrderByTableMutation.mutateAsync({
        orderId: order.publicId,
        updateOrderPayload: { status: nextStatus },
      });
    } catch {
      // mutation의 onError에서 캐시 롤백 및 isError 상태 처리됨
    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderItem.tsx
around lines 38 - 41, Handle the rejection from
updateOrderByTableMutation.mutateAsync in onOrderStatusUpdate by attaching catch
handling consistent with TableOrderAcceptAllButton. Preserve the mutation’s
existing onError rollback and isError retry UI behavior while preventing an
unhandled promise rejection.
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/TableOrderList.tsx (1)

7-9: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

TableBoardProps 타입명을 TableOrderListProps로 변경해 주세요.

이 타입은 TableOrderList 컴포넌트의 props를 정의하지만, 이름이 TableBoard를 참조하고 있어 혼동의 소지가 있습니다. As per coding guidelines, type, function, method, or variable names에 clear, descriptive names를 사용해야 합니다.

♻️ 제안하는 수정
-type TableBoardProps = {
+type TableOrderListProps = {
   sanitizedTable: BoardTableWithSession;
 };

-export default function TableOrderList({ sanitizedTable }: TableBoardProps) {
+export default function TableOrderList({ sanitizedTable }: TableOrderListProps) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/TableOrderList.tsx
around lines 7 - 9, Rename the TableBoardProps type to TableOrderListProps and
update all references, including the TableOrderList component’s props
annotation, while preserving the existing sanitizedTable field and behavior.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

console feat New feature or request packages ui

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant