Skip to content

Conversation

@oriNuguri25
Copy link
Collaborator

@oriNuguri25 oriNuguri25 commented Sep 1, 2025

작업 내용

문제점 및 어려움

해결 방안

공유 사항

Summary by CodeRabbit

  • New Features

    • Swipe to dismiss new-order toasts; tap to open order details.
    • Swipe-to-delete for menu items with a confirmation step.
  • Improvements

    • New-order toast redesigned as a compact card with header, totals, avatar and close button.
    • Admin Booth and related pages constrained to a max width for better readability.
    • Account page responsive tweaks for narrow viewports.
    • Login page updated with a new illustration.
  • Style

    • Added toast card styling, typography utilities, and toast entry animation.
  • Chores

    • Added Sentry monitoring and sourcemap support.

@coderabbitai
Copy link

coderabbitai bot commented Sep 1, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds swipe/dismiss interactions and compact toast UI for new orders; introduces a reusable SwipeableRow for swipe-to-delete menu items with confirm flow; applies max-width layout constraints across AdminBooth pages; updates CloseButton API and toast/global CSS; integrates Sentry into nowait-user and adds many runtime package mappings (.pnp.cjs).

Changes

Cohort / File(s) Summary of edits
New-order toast & swipe dismiss
apps/nowait-admin/src/components/NewOrderToast.tsx
Rebuilt toast as compact card, wrapped each toast with a SwipeToDismiss (right-swipe to dismiss), added CloseButton, global audio element id new-order-audio, data-driven menu summary/total, entry keyframes animation, pointer-events handling, and tap navigation using useNavigate().
Swipeable rows for menu items
apps/nowait-admin/src/pages/AdminBooth/components/Swipe/SwipeableRow.tsx, apps/nowait-admin/src/pages/AdminBooth/components/MenuSection.tsx
New SwipeableRow component (forwardRef) enabling left-swipe reveal of delete; MenuSection now wraps rows with SwipeableRow, disables swipe in editMode, changes delete to modal-confirm → delete mutation → local state removal.
CloseButton API & usage
apps/nowait-admin/src/components/closeButton.tsx
onClick typed as MouseEventHandler<HTMLButtonElement>, added width/height props with defaults, added type="button" and pointer-down propagation suppression, dynamic img sizing.
Global styles for toast & typography
apps/nowait-admin/src/global.css
Added .toast-card, toast text utility classes (.text-13-semibold, .text-18-semibold, .text-13-medium, .text-15-semibold) and @keyframes l3.
AdminBooth layout width / responsiveness
apps/nowait-admin/src/pages/AdminBooth/AdminBooth.tsx, apps/nowait-admin/src/pages/AdminBooth/components/BoothSection.tsx, apps/nowait-admin/src/pages/AdminBooth/components/NoticeEditor.tsx, apps/nowait-admin/src/pages/AdminBooth/components/OperatingTimeSelector.tsx, apps/nowait-admin/src/pages/AdminBooth/components/AccountPage.tsx
Applied max-w-[614px] constraints to wrappers; AccountPage adds isSmall responsive flag and adjusts banner, image, and input widths for narrow viewports.
User login & Sentry integration (nowait-user)
apps/nowait-user/src/pages/login/LoginPage.tsx, apps/nowait-user/package.json, apps/nowait-user/src/main.tsx, apps/nowait-user/src/utils/initSentry.ts, apps/nowait-user/vite.config.ts
Replaced login placeholder with LoginImage_4 SVG in fixed-size container; added @sentry/react and @sentry/vite-plugin deps; added initSentry() and called it at app startup; registered Sentry Vite plugin and enabled build sourcemap.
Runtime map / dependency graph
.pnp.cjs
Expanded Yarn PnP RAW_RUNTIME_STATE with many new package/runtime entries (Babel, Sentry internals, virtual/unplugged packages, transitive deps).
Minor cleanup
apps/nowait-admin/src/layout/AdminLayout.tsx
Removed a single inline comment; no functional change.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant T as NewOrderToast
  participant S as SwipeToDismiss
  participant R as Router

  rect rgb(230,245,255)
    note right of T: toast renders compact, summary computed
    U->>T: Tap toast
    T->>R: navigate("/admin/orders/{storeId}")
  end

  rect rgb(255,240,230)
    U->>S: Swipe right on toast
    S-->>T: threshold exceeded → dismiss (animate out)
    U->>T: Click CloseButton
    T-->>T: explicit dismiss
  end
Loading
sequenceDiagram
  autonumber
  participant U as User
  participant SW as SwipeableRow
  participant MR as MenuSection
  participant M as ConfirmModal
  participant API as deleteMenu()
  participant ST as LocalState

  U->>SW: Swipe left on row
  SW-->>SW: reveal "삭제" (open)
  U->>SW: Tap "삭제"
  SW->>MR: onDeleteClick()
  MR->>M: open confirm modal
  alt Confirmed
    M->>API: deleteMenu(menuId)
    API-->>MR: success
    MR->>ST: remove item from menus (local state)
  else Cancelled
    M-->>MR: close modal, no change
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • Develop #416 — Directly touches NewOrderToast, closeButton.tsx, global CSS and swipe components; high overlap in UI and interaction changes.
  • Develop #399 — Also modifies NewOrderToast audio handling and may conflict on audio element/source.
  • Develop #328 — Modifies AccountPage area; related to responsive/layout adjustments introduced here.

Suggested reviewers

  • dgKim1
  • hwangdae

Poem

A twitch of whiskers, a nimble hop,
I swipe the toast — the orders stop.
Menus slide and little deletes sing,
I hum a tune for the Sentry ring.
Hop, hop, hooray — the UI’s neat, carrot-coded and sweet. 🥕


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9e88892 and dbebe85.

⛔ Files ignored due to path filters (51)
  • .yarn/cache/@babel-core-npm-7.28.3-fb967e901c-e6b3eb830c.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@babel-generator-npm-7.28.3-1529434ded-0ff58bcf04.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@babel-helper-module-transforms-npm-7.28.3-7b69ec189a-549be62515.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@babel-helpers-npm-7.28.3-8e4849da45-03a8f94135.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@babel-parser-npm-7.28.3-8acaa30019-1f41eb8262.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@babel-traverse-npm-7.28.3-7786c501c7-26e95b29a4.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@esbuild-darwin-arm64-npm-0.25.8-351c587628-10c0.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@jridgewell-sourcemap-codec-npm-1.5.5-5189d9fc79-f9e538f302.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@parcel-watcher-darwin-arm64-npm-2.5.1-12be747bca-10c0.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@parcel-watcher-win32-x64-npm-2.5.1-6e3012ad80-10c0.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@rollup-rollup-darwin-arm64-npm-4.46.2-78a61e53eb-10c0.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@rollup-rollup-win32-x64-msvc-npm-4.46.2-66021925a7-10c0.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@sentry-babel-plugin-component-annotate-npm-4.2.0-3138745407-595a46e943.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@sentry-browser-npm-10.8.0-25cba9320a-33e434c5b5.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@sentry-bundler-plugin-core-npm-4.2.0-a6284fa009-99ccf7d060.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@sentry-cli-darwin-npm-2.53.0-c906d44a58-10c0.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@sentry-cli-npm-2.53.0-8a1ab4d955-ea6acf44d3.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@sentry-core-npm-10.8.0-429363af5e-7c25eab30b.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@sentry-internal-browser-utils-npm-10.8.0-ba0daf4d42-21bd70e778.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@sentry-internal-feedback-npm-10.8.0-0652980227-c72c201022.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@sentry-internal-replay-canvas-npm-10.8.0-607410ecaf-4b5e06d04f.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@sentry-internal-replay-npm-10.8.0-071491bd98-d3ffeaa64d.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@sentry-react-npm-10.8.0-024496c300-f40c476da8.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@sentry-vite-plugin-npm-4.2.0-a5d6d1e52c-7452c1b75d.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@tailwindcss-oxide-darwin-arm64-npm-4.1.11-9385878a36-10c0.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/@tailwindcss-oxide-win32-x64-msvc-npm-4.1.11-3a4bbe0b3d-10c0.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/agent-base-npm-6.0.2-428f325a93-dc4f757e40.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/anymatch-npm-3.1.3-bc81d103b1-57b06ae984.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/binary-extensions-npm-2.3.0-bd3f20d865-75a59cafc1.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/chokidar-npm-3.6.0-3c413a828f-8361dcd013.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/fsevents-patch-6b67494872-10c0.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/glob-npm-9.3.5-2f602083f0-2f6c2b9ee0.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/https-proxy-agent-npm-5.0.1-42d65f358e-6dd639f034.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/is-binary-path-npm-2.1.0-e61d46f557-a16eaee59a.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/lightningcss-darwin-arm64-npm-1.30.1-4f54e7a15b-10c0.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/lightningcss-win32-x64-msvc-npm-1.30.1-2e0d6b2fcc-10c0.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/magic-string-npm-0.30.8-0378572eee-51a1f06f67.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/minimatch-npm-8.0.4-bf57f0e98a-a0a394c356.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/minipass-npm-4.2.8-f05abfe254-4ea76b030d.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/node-fetch-npm-2.7.0-587d57004e-b55786b602.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/normalize-path-npm-3.0.0-658ba7d77f-e008c8142b.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/progress-npm-2.0.3-d1f87e2ac6-1697e07cb1.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/readdirp-npm-3.6.0-f950cc74ab-6fa848cf63.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/tr46-npm-0.0.3-de53018915-047cb209a6.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/turbo-darwin-arm64-npm-2.5.6-9d889b055e-10c0.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/unplugin-npm-1.0.1-70bc9bb0e2-7d59b5a28a.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/webidl-conversions-npm-3.0.1-60310f6a2b-5612d5f3e5.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/webpack-sources-npm-3.3.3-62a2b4959b-ab732f6933.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/webpack-virtual-modules-npm-0.5.0-314fd879d0-0742e069cd.zip is excluded by !**/.yarn/**, !**/*.zip
  • .yarn/cache/whatwg-url-npm-5.0.0-374fb45e60-1588bed84d.zip is excluded by !**/.yarn/**, !**/*.zip
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (5)
  • .pnp.cjs (27 hunks)
  • apps/nowait-user/package.json (1 hunks)
  • apps/nowait-user/src/main.tsx (1 hunks)
  • apps/nowait-user/src/utils/initSentry.ts (1 hunks)
  • apps/nowait-user/vite.config.ts (2 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch develop

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot requested a review from dgKim1 September 1, 2025 23:59
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: 2

Caution

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

⚠️ Outside diff range comments (8)
apps/nowait-admin/src/pages/AdminBooth/components/NoticeEditor.tsx (1)

114-121: Fix event-listener leak on the editor "update" handler

You add a new listener on every mount but don’t remove it, causing duplicate updates and memory growth.

 useEffect(() => {
   if (!editor) return;
-  editor.on("update", () => {
-    setNotice(editor.getHTML());
-  });
+  const handler = () => setNotice(editor.getHTML());
+  editor.on("update", handler);
+  return () => editor.off("update", handler);
 }, [editor, setNotice]);
apps/nowait-admin/src/pages/AdminBooth/components/BoothSection.tsx (1)

103-123: Avoid object URL leaks in Preview props

URL.createObjectURL is called during render; repeated renders will leak URLs. Generate URLs in a memo/effect and revoke them on cleanup (or move this responsibility into PreviewModal).

Example inside PreviewModal (preferred centralization):

// derive URL for a possibly-File image
const useObjectUrl = (fileOrUrl?: File | string | null) => {
  const [url, setUrl] = useState<string | null>(null);
  useEffect(() => {
    if (!fileOrUrl) return setUrl(null);
    if (fileOrUrl instanceof File) {
      const u = URL.createObjectURL(fileOrUrl);
      setUrl(u);
      return () => URL.revokeObjectURL(u);
    }
    setUrl(fileOrUrl);
  }, [fileOrUrl]);
  return url;
};

Then pass raw File or URL from BoothSection; let PreviewModal resolve and clean up.

apps/nowait-admin/src/pages/AdminBooth/components/AccountPage.tsx (3)

230-247: Unblock: setSaving(false) is never reset on API errors

Both create and update error paths leave saving stuck true, disabling the Save button until reload.

Apply:

       createPayment(payload, {
         onSuccess: () => {
           console.log("결제 정보가 생성되었습니다.");
           setSaving(false);
         },
-        onError: () => console.log("결제 정보 생성 실패"),
+        onError: () => {
+          console.log("결제 정보 생성 실패");
+          setSaving(false);
+        },
       });
       updatePayment(payload, {
         onSuccess: () => {
           console.log("결제 정보가 수정되었습니다.", payload);
           refetch();
           setSaving(false);
         },
-        onError: () => console.log("결제 정보 수정 실패"),
+        onError: () => {
+          console.log("결제 정보 수정 실패");
+          setSaving(false);
+        },
       });

270-282: Guard against undefined/empty accountNumber to avoid runtime crash

res.accountNumber.split(" ") will throw if accountNumber is undefined or "". Also, use robust splitting.

-    const accountInfo = res.accountNumber.split(" ");
+    const rawAccount = (res.accountNumber ?? "").trim();
+    const accountInfo = rawAccount.length ? rawAccount.split(/\s+/) : [];

451-456: Use text input for account numbers (leading zeros, long values, no spinners)

type="number" drops leading zeros and allows scientific notation. Use text + numeric keyboard.

-          <input
-            type="number"
+          <input
+            type="text"
+            inputMode="numeric"
+            pattern="[0-9\- ]*"
             placeholder="계좌번호"
             value={accountNumber}
             onChange={(e) => setAccountNumber(e.target.value)}
             className="w-1/2 border border-[#dddddd] bg-black-5 text-14-regular text-black-90 rounded-xl p-4"
           />
apps/nowait-admin/src/pages/AdminBooth/components/MenuSection.tsx (3)

294-301: react-beautiful-dnd integration is broken by duplicate ref/props.

provided.innerRef and draggableProps must be attached to the same DOM node. Currently you attach them to both rowContent’s inner div and to SwipeableRow (via ref/contentProps), which can break drag and cause warnings.

Apply:

-                      const rowContent = (
-                        <div
-                          className="flex justify-between items-center py-4"
-                          ref={provided.innerRef}
-                          {...provided.draggableProps}
-                          style={lockedStyle}
-                        >
+                      const rowContent = (
+                        <div className="flex justify-between items-center py-4">
                           ...
                         </div>
                       );
                       return (
                         <SwipeableRow
-                          ref={provided.innerRef}
+                          ref={provided.innerRef}
                           disabled={editMode}
                           onDelete={() => {
                             // 즉시 삭제
                             setSelectedMenu(menu);
                             deleteMenu(menu.id, {
                               onSuccess: () => {
                                 setMenus((prev) =>
                                   prev.filter((m) => m.id !== menu.id)
                                 );
                               },
                             });
                           }}
                           contentProps={{
-                            ...provided.draggableProps,
-                            style: lockedStyle,
+                            ...provided.draggableProps,
+                            style: lockedStyle,
+                            className: "flex justify-between items-center py-4",
                           }}
                         >
                           {rowContent}
                         </SwipeableRow>
                       );

Also applies to: 340-361


254-258: Fix Tailwind class typo and add alt for icon.

“itens-center” should be “items-center”. Also add alt for the decorative icon to satisfy a11y.

-          <button
-            className="flex itens-center text-14-semibold px-[10px] py-[7.5px] bg-black-5 text-black-70 rounded-[8px]"
+          <button
+            className="flex items-center text-14-semibold px-[10px] py-[7.5px] bg-black-5 text-black-70 rounded-[8px]"
             onClick={() => setIsAddModalOpen(true)}
           >
-            메뉴 추가 <img src={addIcon} />
+            메뉴 추가 <img src={addIcon} alt="" aria-hidden="true" />
           </button>

173-196: Sold-out toggle rolls UI state the wrong way on error.

You only flip state on success; on error you also flip, leaving the UI incorrect. Either do optimistic update with rollback, or keep current pattern and don’t touch state on error.

Minimal fix:

       onError: () => {
-        // 3) 실패 시 롤백
-        setMenus((prev) => {
-          const next = [...prev];
-          next[index] = { ...next[index], soldOut: !next[index].soldOut };
-          return next;
-        });
+        console.log("품절 토글 실패");
       },

If you prefer optimistic UX, flip before calling soldOut and keep the rollback block (then remove the onSuccess flip).

🧹 Nitpick comments (24)
apps/nowait-admin/src/pages/AdminBooth/components/OperatingTimeSelector.tsx (1)

64-64: Center the width-capped container for consistent layout

Add w-full and mx-auto so the section stays centered while capped at 614px (mirrors other sections).

-    <div className="mb-[50px] max-w-[614px]">
+    <div className="mb-[50px] w-full max-w-[614px] mx-auto">
apps/nowait-admin/src/pages/AdminBooth/components/NoticeEditor.tsx (2)

131-131: Center the editor card like other capped sections

Add mx-auto so the 614px-capped card is horizontally centered.

-    <div className="w-full bg-white border border-[#DDDDDD] rounded-xl max-w-[614px]">
+    <div className="w-full bg-white border border-[#DDDDDD] rounded-xl max-w-[614px] mx-auto">

46-53: Remove debug log from toolbar effect

console.log(editorChanged) is noisy in production; the effect already re-renders via the transaction listener.

 useEffect(() => {
   if (!editor) return;
-  console.log(editorChanged);
 
   const update = () => setEditorChanged((prev) => prev + 1);
   editor.on("transaction", update);
   return () => editor.off("transaction", update);
 }, [editor]);
apps/nowait-admin/src/pages/AdminBooth/components/BoothSection.tsx (3)

76-76: Center all 614px-capped sections for visual consistency

Add w-full and mx-auto to each capped wrapper so they’re centered across viewports.

-      <div className="flex flex-col items-center pb-[50px] max-w-[614px]">
+      <div className="flex flex-col items-center pb-[50px] w-full max-w-[614px] mx-auto">
-      <div className="flex flex-col mb-[50px] relative max-w-[614px]">
+      <div className="flex flex-col mb-[50px] relative w-full max-w-[614px] mx-auto">
-      <div className="flex flex-col mb-[50px] max-w-[614px]">
+      <div className="flex flex-col mb-[50px] w-full max-w-[614px] mx-auto">

Also applies to: 174-174, 204-204


223-236: Constrain uploads to images at the input level

Add accept to prevent non-image files from selection.

-                <input
-                  type="file"
+                <input
+                  type="file"
+                  accept="image/*"
                   className="hidden"

76-76: Reduce repetition of the magic width "614px"

Consider a shared token (Tailwind theme: extend maxWidth.booth = "614px" → use max-w-booth) to avoid hardcoding across components.

apps/nowait-user/src/pages/login/LoginPage.tsx (1)

87-89: A11y: mark decorative SVG as hidden or provide a title

If purely decorative, hide it from AT. If meaningful, provide an accessible name.

-          <LoginImage_4 />
+          <LoginImage_4 aria-hidden="true" focusable="false" />

Alternatively:

<LoginImage_4 role="img" aria-label="축제 맵 일러스트" />
apps/nowait-admin/src/global.css (2)

74-80: Tame backdrop blur cost and ensure visible frosted look

Blur(200px) is costly on mobile GPUs and the frosted effect needs a semi-transparent background to be noticeable. Reduce blur and add a translucent background; include a fallback for browsers without backdrop-filter.

 .toast-card {
-  box-shadow: 0px 4px 25px rgba(0, 0, 0, 0.1);
-  backdrop-filter: blur(200px);
-  -webkit-backdrop-filter: blur(200px);
-  border-radius: 10px;
+  box-shadow: 0px 4px 25px rgba(0, 0, 0, 0.1);
+  backdrop-filter: blur(20px);
+  -webkit-backdrop-filter: blur(20px);
+  background: rgba(255, 255, 255, 0.75);
+  border-radius: 10px;
 }

Optional fallback:

@supports not (backdrop-filter: blur(1px)) {
  .toast-card { background: rgba(255, 255, 255, 0.95); }
}

90-113: Align text utility colors with theme tokens

Hardcoded colors (#fff, #666666, #222222) can drift from the design system. Prefer CSS variables or Tailwind theme colors (e.g., var(--black-70)) for consistency and theming.

apps/nowait-admin/src/pages/AdminBooth/components/AccountPage.tsx (5)

342-342: Fix non-existent utility and ensure inner content can shrink

w-overflow-scroll isn't a standard Tailwind class. Use overflow utilities and min-w-0 to prevent flex children from overflowing.

-                <div className={`flex flex-col w-[79%] w-overflow-scroll`}>
+                <div className="flex flex-col min-w-0 w-[79%] overflow-x-auto">

180-183: Save disabled state should reflect error presence (and avoid unused dep)

Currently hasError is in deps but not in the expression.

-  const saveDisabled = useMemo(() => {
-    return saving || (sameUrls && sameAccount) || !paymentFilled;
-  }, [saving, sameUrls, sameAccount, paymentFilled, hasError]);
+  const saveDisabled = useMemo(() => {
+    return saving || (sameUrls && sameAccount) || !paymentFilled || hasError;
+  }, [saving, sameUrls, sameAccount, paymentFilled, hasError]);

298-306: Make the clickable banner accessible (keyboard + semantics)

Clickable <img> lacks keyboard access. Wrap with a button or add role/tabIndex handlers.

-        <div className="h-full min-h-[60px]">
-          <img
-            src={banner}
-            alt="배너"
-            className={`object-fill min-h-[60px]`}
-            onClick={() => navigate("guides")}
-          />
-        </div>
+        <div className="h-full min-h-[60px]">
+          <button
+            type="button"
+            aria-label="가이드 보기"
+            onClick={() => navigate("guides")}
+            className="w-full"
+          >
+            <img
+              src={banner}
+              alt="배너"
+              className="object-fill min-h-[60px] w-full"
+            />
+          </button>
+        </div>

40-40: Avoid JS breakpoint for one-off margin; prefer CSS to reduce hydration risk

isSmall is only used for banner margins. Use a Tailwind max-width breakpoint instead; then remove isSmall.

-  const isSmall = width < 415;

And update the banner container (Line 298):

-      <div className={`h-full ${isSmall ? "my-4" : "my-10"}`}>
+      <div className="h-full my-10 max-[414px]:my-4">

170-170: Remove noisy console logs in production

console.log(curAccount, "즉각 반영", sameAccount); can leak data and clutter logs. Guard by env or remove.

apps/nowait-admin/src/components/closeButton.tsx (1)

15-20: Improve a11y: label the button itself

Add an explicit accessible name on the button; relying on inner img alt is fragile.

-    <button
+    <button
       type="button"
       onPointerDown={(e) => e.stopPropagation()}
       onClick={onClick}
-      className={`flex items-center justify-center h-6 w-6`}
+      aria-label="닫기"
+      title="닫기"
+      className={`flex items-center justify-center h-6 w-6`}
     >
apps/nowait-admin/src/pages/AdminBooth/components/Swipe/SwipeableRow.tsx (5)

104-125: Do not override consumer event handlers from contentProps

Your handlers replace contentProps handlers. Safely call both to preserve consumer behavior.

-          onTouchStart={onTouchStart}
+          onTouchStart={(e) => {
+            contentProps?.onTouchStart?.(e);
+            onTouchStart(e);
+          }}
-          onTouchMove={onTouchMove}
+          onTouchMove={(e) => {
+            contentProps?.onTouchMove?.(e);
+            onTouchMove(e);
+          }}
-          onTouchEnd={onTouchEnd}
+          onTouchEnd={(e) => {
+            contentProps?.onTouchEnd?.(e);
+            onTouchEnd();
+          }}
-          onMouseDown={onMouseDown}
+          onMouseDown={(e) => {
+            contentProps?.onMouseDown?.(e);
+            onMouseDown(e);
+          }}
-          onMouseMove={onMouseMove}
+          onMouseMove={(e) => {
+            contentProps?.onMouseMove?.(e);
+            onMouseMove(e);
+          }}
-          onMouseUp={onMouseUp}
+          onMouseUp={(e) => {
+            contentProps?.onMouseUp?.(e);
+            onMouseUp();
+          }}

(Repeat pattern for onMouseLeave if needed.)


119-125: Handle touch cancel to reset state

Interrupted gestures (e.g., OS alerts) may leave dragging=true. Add onTouchCancel.

           onTouchEnd={onTouchEnd}
+          onTouchCancel={onTouchEnd}

91-101: Make delete affordance interactive and accessible

The visible “삭제” area isn’t a control. Wrap it in a button to support click/tap/keyboard (while keeping swipe-to-delete).

-        <div
+        <div
           className="absolute top-[16px] right-0 flex items-center justify-end"
           style={{ width: BTN_WIDTH + GAP, height: 70 }}
         >
-          <div
-            className="rounded-[8px] bg-[#FFF0EB] flex items-center justify-center"
-            style={{ width: BTN_WIDTH, height: 70 }}
-          >
-            <span className="text-[#FF4103] text-14-semibold">삭제</span>
-          </div>
+          <button
+            type="button"
+            aria-label="삭제"
+            onClick={onDelete}
+            className="rounded-[8px] bg-[#FFF0EB] flex items-center justify-center"
+            style={{ width: BTN_WIDTH, height: 70 }}
+          >
+            <span className="text-[#FF4103] text-14-semibold" aria-hidden>
+              삭제
+            </span>
+          </button>
         </div>

112-118: Minor perf: hint compositor for smoother swipes

Adding will-change: transform helps GPU promote the layer during drag.

             transition:
               dragging || mouseDragging ? "none" : "transform 200ms ease",
             position: "relative",
             zIndex: 1,
             width: "100%",
             userSelect: "none",
             touchAction: "pan-y",
+            willChange: "transform",

18-21: Consider exposing swipe threshold/width as props

Hard-coded BTN_WIDTH/GAP (70/14) may not suit all rows or locales.

apps/nowait-admin/src/components/NewOrderToast.tsx (3)

98-99: Avoid navigating to an empty store route.

When storeId is missing, navigate to a safe fallback.

- const storeId = localStorage.getItem("storeId") ?? "";
+ const storeId = localStorage.getItem("storeId");
@@
-              onTap={() => navigate(`/admin/orders/${storeId}`)} // ← 클릭 이동은 여기서!
+              onTap={() =>
+                navigate(storeId ? `/admin/orders/${storeId}` : "/admin/orders")
+              } // ← 클릭 이동은 여기서!

Please confirm the fallback route “/admin/orders” exists.

Also applies to: 136-138


140-147: Improve toast accessibility.

Augment role="alert" with aria-live and aria-atomic.

-              <div
-                role="alert"
+              <div
+                role="alert"
+                aria-live="assertive"
+                aria-atomic="true"
                 className={[

181-182: Deduplicate keyframes; move toastIn to global CSS.

Inline keyframes per toast inflate DOM and can cause style thrash. Define once in global.css and drop the inline <style>.

-                <style>{`@keyframes toastIn{to{transform:translateY(0);opacity:1}}`}</style>

Add to global stylesheet (outside this file):

@keyframes toastIn { to { transform: translateY(0); opacity: 1; } }
apps/nowait-admin/src/pages/AdminBooth/AdminBooth.tsx (1)

313-313: LGTM: width constraint added.

max-w-[614px] centers and constrains content nicely.

If 614px is a design token, consider centralizing via Tailwind theme (theme.extend.maxWidth) to avoid magic numbers across files.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2bbf09a and cf313ae.

⛔ Files ignored due to path filters (1)
  • apps/nowait-user/src/assets/login/login_img_4.svg is excluded by !**/*.svg
📒 Files selected for processing (12)
  • apps/nowait-admin/src/components/NewOrderToast.tsx (1 hunks)
  • apps/nowait-admin/src/components/closeButton.tsx (1 hunks)
  • apps/nowait-admin/src/global.css (2 hunks)
  • apps/nowait-admin/src/layout/AdminLayout.tsx (0 hunks)
  • apps/nowait-admin/src/pages/AdminBooth/AdminBooth.tsx (1 hunks)
  • apps/nowait-admin/src/pages/AdminBooth/components/AccountPage.tsx (3 hunks)
  • apps/nowait-admin/src/pages/AdminBooth/components/BoothSection.tsx (3 hunks)
  • apps/nowait-admin/src/pages/AdminBooth/components/MenuSection.tsx (4 hunks)
  • apps/nowait-admin/src/pages/AdminBooth/components/NoticeEditor.tsx (1 hunks)
  • apps/nowait-admin/src/pages/AdminBooth/components/OperatingTimeSelector.tsx (1 hunks)
  • apps/nowait-admin/src/pages/AdminBooth/components/Swipe/SwipeableRow.tsx (1 hunks)
  • apps/nowait-user/src/pages/login/LoginPage.tsx (2 hunks)
💤 Files with no reviewable changes (1)
  • apps/nowait-admin/src/layout/AdminLayout.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
apps/nowait-admin/src/pages/AdminBooth/components/MenuSection.tsx (1)
apps/nowait-admin/src/pages/AdminBooth/components/Swipe/SwipeableRow.tsx (1)
  • SwipeableRow (10-138)
apps/nowait-admin/src/components/NewOrderToast.tsx (1)
apps/nowait-admin/src/hooks/useNewOrderToastStore.tsx (1)
  • useNewOrderToastStore (41-71)
🔇 Additional comments (4)
apps/nowait-user/src/pages/login/LoginPage.tsx (1)

6-6: LGTM: new SVG illustration import

Import pattern with ?react is consistent with existing usage.

apps/nowait-admin/src/pages/AdminBooth/components/AccountPage.tsx (1)

310-310: Confirm layout after removing w-full from section

Dropping className="w-full" may change width in some parents. Please verify no regression on narrow containers.

apps/nowait-admin/src/pages/AdminBooth/components/MenuSection.tsx (2)

239-239: LGTM: constrained list width.

max-w-[614px] aligns with the form layout.


205-213: Confirm sortOrder indexing
The payload currently sends the sortOrder value (0-based) directly. Verify whether the /admin/menus/update-sort endpoint expects 1-based indexes—in which case you’d need to send sortOrder + 1.

Comment on lines +54 to +65
const endDrag = () => {
if (!dragging) return;

const didSwipe = tx >= THRESHOLD;
if (didSwipe) {
onDismiss(); // 스와이프 → 제거
} else if (!moved.current) {
onTap?.(); // 거의 안 움직였으면 → 탭으로 처리
}
setTx(0);
setDragging(false);
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Ghost-click suppression won’t work as intended (tx resets before click).

endDrag resets tx to 0 before the click event fires, so onClickCapture(tx >= THRESHOLD) won’t block the ghost click. Use a ref flag set during swipe-dismiss and clear it on capture.

Apply:

   const moved = React.useRef(false);
+  const suppressClickRef = React.useRef(false);
@@
   const endDrag = () => {
     if (!dragging) return;
 
     const didSwipe = tx >= THRESHOLD;
     if (didSwipe) {
-      onDismiss(); // 스와이프 → 제거
+      suppressClickRef.current = true;
+      onDismiss(); // 스와이프 → 제거
     } else if (!moved.current) {
       onTap?.(); // 거의 안 움직였으면 → 탭으로 처리
     }
     setTx(0);
     setDragging(false);
   };
@@
   // 스와이프 후 발생하는 "유령 클릭" 1회 차단(탭은 통과)
   const onClickCapture = (e: React.MouseEvent) => {
-    if (tx >= THRESHOLD) {
+    if (suppressClickRef.current) {
       e.stopPropagation();
       e.preventDefault();
+      // @ts-ignore
+      (e.nativeEvent as any)?.stopImmediatePropagation?.();
+      suppressClickRef.current = false;
     }
   };

Also applies to: 67-73

🤖 Prompt for AI Agents
In apps/nowait-admin/src/components/NewOrderToast.tsx around lines 54-65 (also
apply same fix to lines 67-73): the problem is resetting tx to 0 inside endDrag
before the click fires so onClickCapture(tx >= THRESHOLD) cannot detect a swipe
and suppress the ghost click. Fix by introducing a ref (e.g., swipedRef) that
you set to true when didSwipe is true inside endDrag, then call onDismiss; do
not rely on tx for click suppression. In your click-capture handler check
swipedRef.current and, if true, prevent the click (clear swipedRef.current =
false in that capture handler so subsequent clicks behave normally). Ensure you
still reset tx and dragging as needed after setting the ref.

)}
</div>
<div className="flex justify-between h-[52px] w-full min-w-[236px] items-center bg-black-5 rounded-xl border border-[#dddddd] pl-4 pr-[10px] py-4">
<div className="flex justify-between h-[52px] w-[474px] min-w-[236px] items-center bg-black-5 rounded-xl border border-[#dddddd] pl-4 pr-[10px] py-4">
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Prevent horizontal overflow on small screens

Hard-coding w-[474px] can overflow on ~360px devices. Let the row grow/shrink responsively.

-              <div className="flex justify-between h-[52px] w-[474px] min-w-[236px] items-center bg-black-5 rounded-xl border border-[#dddddd] pl-4 pr-[10px] py-4">
+              <div className="flex justify-between h-[52px] flex-1 min-w-0 md:max-w-[474px] min-w-[236px] items-center bg-black-5 rounded-xl border border-[#dddddd] pl-4 pr-[10px] py-4">
📝 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
<div className="flex justify-between h-[52px] w-[474px] min-w-[236px] items-center bg-black-5 rounded-xl border border-[#dddddd] pl-4 pr-[10px] py-4">
<div className="flex justify-between h-[52px] flex-1 min-w-0 md:max-w-[474px] min-w-[236px] items-center bg-black-5 rounded-xl border border-[#dddddd] pl-4 pr-[10px] py-4">
🤖 Prompt for AI Agents
In apps/nowait-admin/src/pages/AdminBooth/components/AccountPage.tsx around line
341, the container uses a hard-coded w-[474px] which can cause horizontal
overflow on small (~360px) screens; replace the fixed width with responsive
sizing so the row can shrink: use w-full and max-w-[474px] (and add min-w-0 to
allow flex children to shrink) — e.g. change the class list to include w-full
max-w-[474px] min-w-0 while keeping other utility classes (you can keep or
adjust min-w-[236px] if needed).

dgKim1 and others added 2 commits September 2, 2025 10:57
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

♻️ Duplicate comments (1)
apps/nowait-admin/src/components/NewOrderToast.tsx (1)

25-38: Fix ghost-click suppression and avoid unintended navigation when tapping CloseButton.

  • Ghost-click suppression checks tx, but tx is reset to 0 in endDrag before click fires → suppression fails. Use a ref flag.
  • Tapping the CloseButton (no movement) triggers onTap navigation because tap detection doesn’t exclude interactive targets. Track this and skip onTap.

Apply:

   const startX = React.useRef(0);
   const startY = React.useRef(0);
   const moved = React.useRef(false);
+  const suppressClickRef = React.useRef(false); // blocks one ghost click after swipe-dismiss
+  const ignoreTapRef = React.useRef(false);     // true if pointer started on interactive control

   const [tx, setTx] = React.useState(0);
   const [dragging, setDragging] = React.useState(false);

   const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
     startX.current = e.clientX;
     startY.current = e.clientY;
     moved.current = false;
+    // ignore tap if started on an interactive element
+    const targetEl = e.target as HTMLElement;
+    ignoreTapRef.current = !!targetEl.closest(
+      'button,a,[role="button"],input,select,textarea,[data-interactive],[data-no-tap]'
+    );
     setDragging(true);
     e.currentTarget.setPointerCapture?.(e.pointerId);
   };
@@
   const endDrag = () => {
     if (!dragging) return;

     const didSwipe = tx >= THRESHOLD;
     if (didSwipe) {
-      onDismiss(); // 스와이프 → 제거
+      suppressClickRef.current = true;
+      onDismiss(); // 스와이프 → 제거
     } else if (!moved.current) {
-      onTap?.(); // 거의 안 움직였으면 → 탭으로 처리
+      if (!ignoreTapRef.current) onTap?.(); // 거의 안 움직였으면 → 탭으로 처리(단, 버튼 등은 제외)
     }
     setTx(0);
     setDragging(false);
+    ignoreTapRef.current = false;
   };
@@
   // 스와이프 후 발생하는 "유령 클릭" 1회 차단(탭은 통과)
   const onClickCapture = (e: React.MouseEvent) => {
-    if (tx >= THRESHOLD) {
+    if (suppressClickRef.current) {
       e.stopPropagation();
       e.preventDefault();
+      (e.nativeEvent as any)?.stopImmediatePropagation?.();
+      suppressClickRef.current = false;
     }
   };

Also applies to: 54-65, 67-73

🧹 Nitpick comments (5)
apps/nowait-admin/src/components/NewOrderToast.tsx (5)

121-124: Make total computation resilient to string inputs.

API payloads often carry numbers as strings; coerce defensively to avoid NaN totals.

-                const { quantity = 0, price = 0 } = menuDetails[name] ?? {};
-                return sum + quantity * price;
+                const { quantity = 0, price = 0 } = menuDetails[name] ?? {};
+                const q = Number(quantity) || 0;
+                const p = Number(price) || 0;
+                return sum + q * p;

110-116: Avoid navigating to a trailing-slash route when storeId is empty; prefer meta.storeId fallback.

-          const tableId = t.meta?.tableId as number | undefined;
+          const tableId = t.meta?.tableId as number | undefined;
           const orderName = t.meta?.orderName ?? "신규 주문";
           const menuDetails = (t.meta?.menuDetails ?? {}) as Record<
             string,
             { quantity?: number; price?: number }
           >;
+          const navStoreId = (t.meta?.storeId as string | undefined) ?? storeId;
@@
-              onTap={() => navigate(`/admin/orders/${storeId}`)} // ← 클릭 이동은 여기서!
+              onTap={() =>
+                navigate(navStoreId ? `/admin/orders/${navStoreId}` : `/admin/orders`)
+              } // ← 클릭 이동은 여기서!

Please confirm expected route when no storeId exists.

Also applies to: 137-138


153-160: Add accessible label to CloseButton.

                   <CloseButton
                     width={9.41}
                     height={9.41}
+                    aria-label="알림 닫기"
                     onClick={(e) => {
                       e.stopPropagation();
                       removeToast(t.id);
                     }}
                   />

181-182: Hoist keyframes to CSS; avoid per-toast <style> duplication.

-                <style>{`@keyframes toastIn{to{transform:translateY(0);opacity:1}}`}</style>
+                {/* keyframes moved to global CSS */}

Add to apps/nowait-admin/src/global.css:

@keyframes toastIn { to { transform: translateY(0); opacity: 1 } }

103-107: Ensure single audio element per page.

This component renders a fixed-id audio element. If NewOrderToast mounts in more than one place, IDs will duplicate. Consider lifting the audio tag to a top-level singleton or gating with a feature flag.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between cf313ae and 9e88892.

📒 Files selected for processing (4)
  • apps/nowait-admin/src/components/NewOrderToast.tsx (1 hunks)
  • apps/nowait-admin/src/pages/AdminBooth/components/AccountPage.tsx (3 hunks)
  • apps/nowait-admin/src/pages/AdminBooth/components/MenuSection.tsx (4 hunks)
  • apps/nowait-admin/src/pages/AdminBooth/components/Swipe/SwipeableRow.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/nowait-admin/src/pages/AdminBooth/components/Swipe/SwipeableRow.tsx
  • apps/nowait-admin/src/pages/AdminBooth/components/AccountPage.tsx
  • apps/nowait-admin/src/pages/AdminBooth/components/MenuSection.tsx

@oriNuguri25 oriNuguri25 merged commit bf6a273 into main Sep 2, 2025
1 of 2 checks passed
This was referenced Sep 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants