Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds a new Items page and client UI for per-customer-type item inspection/management; relocates and simplifies ItemDialog, removes legacy payment-item-table, adds team search/table components, enables row-click on DataTable, updates sidebar navigation, and changes admin quantity-change payload. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant ItemsPage as PageClient
participant Selector as CustomerSelector
participant ItemHook as useItemForCustomer
participant Admin as adminApp
participant UI as AdjustItemQuantityDialog
participant Toast as Toasts
User->>ItemsPage: Open Items page
ItemsPage->>Selector: Render type & selector UI
User->>Selector: Select type + customer
Selector-->>ItemsPage: Selected customer context
ItemsPage->>ItemHook: Fetch items for selected customer
ItemHook-->>ItemsPage: Item data (Suspense)
alt User adjusts quantity
User->>UI: Open Adjust dialog
UI->>Admin: createItemQuantityChange({... , allow_negative: true})
Admin-->>UI: Success / KnownError
opt Success
UI->>ItemHook: Refresh item data
UI->>Toast: Show success
end
opt KnownError
UI->>Toast: Show specific error
end
end
sequenceDiagram
autonumber
participant TeamTable as TeamTable
participant DataTable as DataTable
participant Router as NextRouter
TeamTable->>DataTable: render(with onRowClick)
User->>DataTable: click row
DataTable-->>TeamTable: onRowClick(team)
TeamTable->>Router: navigate to /projects/{projectId}/teams/{teamId}
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
🧰 Additional context used📓 Path-based instructions (3){apps/dashboard,apps/dev-launchpad,packages/stack-ui,packages/react}/**/*.{tsx,jsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/*.{ts,tsx,js,jsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
{apps/dashboard,apps/dev-launchpad,packages/stack-ui,packages/react}/**/*.{tsx,jsx,css}📄 CodeRabbit inference engine (AGENTS.md)
Files:
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
🔇 Additional comments (2)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Greptile Overview
Summary
This PR implements a comprehensive dashboard feature for managing item quantities across different customer types (users, teams, and custom customers). The changes introduce a new dedicated Items page in the dashboard navigation, refactor the ItemDialog component for better reusability, and enhance data table functionality with row click navigation.The core functionality centers around a new /items page that allows administrators to select customers (users, teams, or custom types) and manage their associated item quantities. The implementation includes customer search capabilities, item quantity adjustment dialogs, and comprehensive error handling. The changes follow established patterns in the codebase by using the page/page-client structure and integrating with existing UI components.
Key architectural changes include moving the ItemDialog component from local directories to a shared @/components/payments/ location for better reusability, adding row click functionality to data tables for improved navigation, and creating a new TeamSearchTable component for customer selection workflows. The backend integration includes enabling negative quantity changes through the allow_negative: true parameter, which supports business scenarios like refunds and inventory corrections.
Important Files Changed
Changed Files
| Filename | Score | Overview |
|---|---|---|
| apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page-client.tsx | 4/5 | New comprehensive dashboard page for managing item quantities with customer selection and quantity adjustment functionality |
| apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page.tsx | 5/5 | Standard Next.js page wrapper for the new Item Quantities feature |
| apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx | 5/5 | Adds Items navigation menu item to the sidebar under Payments section |
| apps/dashboard/src/components/payments/item-dialog.tsx | 4/5 | Complete rewrite from FormDialog to custom Dialog with manual state management and callback-based API |
| apps/dashboard/src/components/data-table/team-search-table.tsx | 4/5 | New simplified team search table component for customer selection workflows |
| apps/dashboard/src/components/data-table/team-table.tsx | 5/5 | Adds row click navigation functionality to team table for better UX |
| packages/stack-ui/src/components/data-table/data-table.tsx | 4/5 | Adds optional onRowClick callback prop with portal prevention logic |
| apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsx | 5/5 | Updates import path for ItemDialog to shared components directory |
| apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsx | 5/5 | Updates import path for ItemDialog to shared components directory |
| packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts | 4/5 | Enables negative quantity changes by adding allow_negative parameter |
| apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/item-dialog.tsx | 4/5 | Removed file - functionality consolidated into shared components |
| apps/dashboard/src/components/data-table/payment-item-table.tsx | 4/5 | Removed file - replaced with comprehensive page-based approach |
Confidence score: 4/5
- This PR introduces significant new functionality with proper error handling and follows established architectural patterns
- Score reflects the complexity of the new item management system and some concerns about the ItemDialog rewrite removing form validation frameworks
- Pay close attention to the ItemDialog component rewrite and the new items page-client.tsx for comprehensive testing
Sequence Diagram
sequenceDiagram
participant User
participant PageClient as "PageClient Component"
participant CustomerSelector as "Customer Selector"
participant ItemTable as "Item Table"
participant AdjustDialog as "Adjust Quantity Dialog"
participant AdminApp as "Admin App"
participant API as "Backend API"
User->>PageClient: "Load items page"
PageClient->>AdminApp: "useProject() & useConfig()"
AdminApp-->>PageClient: "Return project config and payment items"
User->>PageClient: "Select customer type (user/team/custom)"
PageClient->>PageClient: "setCustomerType()"
PageClient->>PageClient: "Filter items by customer type"
User->>CustomerSelector: "Click 'Select customer' button"
CustomerSelector->>CustomerSelector: "Open ActionDialog"
alt Customer type is user
CustomerSelector->>AdminApp: "Load TeamMemberSearchTable"
User->>CustomerSelector: "Select user"
else Customer type is team
CustomerSelector->>AdminApp: "Load TeamSearchTable"
User->>CustomerSelector: "Select team"
else Customer type is custom
User->>CustomerSelector: "Enter custom customer ID"
end
CustomerSelector->>PageClient: "setSelectedCustomer()"
PageClient->>ItemTable: "Render table with customer selection"
ItemTable->>AdminApp: "useItem() for each item"
AdminApp-->>ItemTable: "Return item quantities"
User->>ItemTable: "Click 'Adjust' button for item"
ItemTable->>AdjustDialog: "Open AdjustItemQuantityDialog"
AdjustDialog->>AdjustDialog: "Display quantity adjustment form"
User->>AdjustDialog: "Enter quantity change and description"
User->>AdjustDialog: "Click 'Apply change'"
AdjustDialog->>AdminApp: "createItemQuantityChange()"
AdminApp->>API: "POST item quantity update"
API-->>AdminApp: "Return success/error"
alt Success
AdminApp->>AdminApp: "refreshItem()"
AdminApp-->>AdjustDialog: "Success response"
AdjustDialog->>AdjustDialog: "Show success toast"
AdjustDialog->>AdjustDialog: "Close dialog"
ItemTable->>ItemTable: "Refresh item quantities"
else Error
AdminApp-->>AdjustDialog: "Error response"
AdjustDialog->>AdjustDialog: "Show error toast"
AdjustDialog->>AdjustDialog: "Keep dialog open"
end
12 files reviewed, 2 comments
| await onSave({ | ||
| id: itemId.trim(), | ||
| displayName: displayName.trim(), | ||
| customerType | ||
| }); | ||
|
|
||
| handleClose(); |
There was a problem hiding this comment.
logic: Missing error handling around the onSave call - if it throws, the dialog won't close and user gets no feedback
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/dashboard/src/components/payments/item-dialog.tsx
Line: 55:61
Comment:
**logic:** Missing error handling around the onSave call - if it throws, the dialog won't close and user gets no feedback
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Review by RecurseML
🔍 Review performed on 017b43f..aeb2508
✨ No bugs found, your code is sparkling clean
✅ Files analyzed, no issues (12)
• apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page-client.tsx
• apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page.tsx
• apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/item-dialog.tsx
• apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsx
• apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsx
• apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
• apps/dashboard/src/components/data-table/payment-item-table.tsx
• apps/dashboard/src/components/data-table/team-search-table.tsx
• apps/dashboard/src/components/data-table/team-table.tsx
• apps/dashboard/src/components/payments/item-dialog.tsx
• packages/stack-ui/src/components/data-table/data-table.tsx
• packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts
| displayName: yup.string().optional().label("Display Name"), | ||
| customerType: yup.string().oneOf(["user", "team", "custom"]).defined().label("Customer Type"), | ||
| }); | ||
| await onSave({ |
There was a problem hiding this comment.
Consider wrapping the onSave call in a try/catch block in validateAndSave so that if saving fails, the dialog can display an error instead of closing unexpectedly.
| defaultColumnFilters={[]} | ||
| defaultSorting={[{ id: 'createdAt', desc: true }]} | ||
| onRowClick={(row) => { | ||
| router.push(`/projects/${encodeURIComponent(stackAdminApp.projectId)}/teams/${encodeURIComponent(row.id)}`); |
There was a problem hiding this comment.
When building navigation URLs, consider using a tagged template literal (e.g., urlString``) as per best practices for URL fragments instead of manually concatenating with encodeURIComponent.
| router.push(`/projects/${encodeURIComponent(stackAdminApp.projectId)}/teams/${encodeURIComponent(row.id)}`); | |
| router.push(urlString`/projects/${stackAdminApp.projectId}/teams/${row.id}`); |
This comment was generated because it violated a code review rule: mrule_pmzJAgHDlFZgwIwD.
| 'use client'; | ||
| import { useAdminApp } from "@/app/(main)/(protected)/projects/[projectId]/use-admin-app"; |
| "use client"; | ||
|
|
||
| import { cn } from "@/lib/utils"; |
There was a problem hiding this comment.
moved to components/payments/item-dialog
| delta: options.quantity, | ||
| expires_at: options.expiresAt, | ||
| description: options.description, | ||
| allow_negative: true, |
There was a problem hiding this comment.
The admin app now always passes allow_negative: true for item quantity changes, but the items page still includes error handling for ItemQuantityInsufficientAmount errors, which will never occur when negative quantities are allowed.
View Details
📝 Patch Details
diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page-client.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page-client.tsx
index d700f66e..9bf6c4dd 100644
--- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page-client.tsx
+++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page-client.tsx
@@ -504,14 +504,7 @@ function handleItemQuantityError(error: unknown) {
});
return;
}
- if (error instanceof KnownErrors.ItemQuantityInsufficientAmount) {
- toast({
- title: "Quantity too low",
- description: "This change would reduce the quantity below zero.",
- variant: "destructive",
- });
- return;
- }
+
toast({ title: "Unable to update quantity", variant: "destructive" });
}
Analysis
Dead error handling for ItemQuantityInsufficientAmount in admin items page
What fails: The handleItemQuantityError() function in apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page-client.tsx contains error handling for KnownErrors.ItemQuantityInsufficientAmount that can never be triggered.
How to reproduce: The admin app's createItemQuantityChange() method (line 581 in packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts) always passes allow_negative: true to the API. When this parameter is true, the backend API never throws ItemQuantityInsufficientAmount errors, making the error handling code unreachable.
Result: Dead code in error handler (lines 507-514) that displays "This change would reduce the quantity below zero" message.
Expected: Remove the unreachable error handling code since the admin app explicitly allows negative quantities by design.
There was a problem hiding this comment.
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 (1)
packages/stack-ui/src/components/data-table/data-table.tsx (1)
74-83: Guard row clicks from bubbling controls.
Any button/link inside the row (e.g., action menus) now firesonRowClick, so trying to open those controls immediately navigates away. Add a guard to ignore events originating from interactive descendants before calling the handler.Apply this diff:
- onClick={(ev) => { - // only trigger onRowClick if the element is a direct descendant; don't trigger for portals - if (ev.target instanceof Node && ev.currentTarget.contains(ev.target)) { - props.onRowClick?.(row.original); - } - }} + onClick={(ev) => { + if (!(ev.target instanceof HTMLElement)) { + return; + } + if (!ev.currentTarget.contains(ev.target)) { + return; + } + if (ev.target.closest('button, a, input, textarea, select, [data-no-row-click]')) { + return; + } + props.onRowClick?.(row.original); + }}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page-client.tsx(1 hunks)apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page.tsx(1 hunks)apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/item-dialog.tsx(0 hunks)apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsx(1 hunks)apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsx(1 hunks)apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx(2 hunks)apps/dashboard/src/components/data-table/payment-item-table.tsx(0 hunks)apps/dashboard/src/components/data-table/team-search-table.tsx(1 hunks)apps/dashboard/src/components/data-table/team-table.tsx(1 hunks)apps/dashboard/src/components/payments/item-dialog.tsx(1 hunks)packages/stack-ui/src/components/data-table/data-table.tsx(2 hunks)packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts(1 hunks)
💤 Files with no reviewable changes (2)
- apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/item-dialog.tsx
- apps/dashboard/src/components/data-table/payment-item-table.tsx
🧰 Additional context used
📓 Path-based instructions (4)
{apps/dashboard,apps/dev-launchpad,packages/stack-ui,packages/react}/**/*.{tsx,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
For blocking alerts and errors in UI, do not use toast notifications; use alerts instead
Files:
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsxapps/dashboard/src/components/data-table/team-table.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page.tsxapps/dashboard/src/components/payments/item-dialog.tsxpackages/stack-ui/src/components/data-table/data-table.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsxapps/dashboard/src/components/data-table/team-search-table.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Prefer ES6 Map over Record when representing key–value collections
Files:
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsxapps/dashboard/src/components/data-table/team-table.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page.tsxpackages/template/src/lib/stack-app/apps/implementations/admin-app-impl.tsapps/dashboard/src/components/payments/item-dialog.tsxpackages/stack-ui/src/components/data-table/data-table.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsxapps/dashboard/src/components/data-table/team-search-table.tsx
{apps/dashboard,apps/dev-launchpad,packages/stack-ui,packages/react}/**/*.{tsx,jsx,css}
📄 CodeRabbit inference engine (AGENTS.md)
Keep hover/click animations snappy; avoid pre-transition delays on hover and apply transitions after the action (e.g., fade-out on hover end)
Files:
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsxapps/dashboard/src/components/data-table/team-table.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page.tsxapps/dashboard/src/components/payments/item-dialog.tsxpackages/stack-ui/src/components/data-table/data-table.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsxapps/dashboard/src/components/data-table/team-search-table.tsx
packages/template/**
📄 CodeRabbit inference engine (AGENTS.md)
When modifying the SDK copies, make changes in packages/template (source of truth)
Files:
packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts
🧬 Code graph analysis (5)
apps/dashboard/src/components/data-table/team-table.tsx (3)
apps/dashboard/src/components/router.tsx (1)
useRouter(15-33)apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (1)
useAdminApp(27-34)packages/stack-ui/src/components/data-table/data-table.tsx (1)
DataTable(124-162)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page.tsx (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page-client.tsx (1)
PageClient(46-142)
apps/dashboard/src/components/payments/item-dialog.tsx (3)
packages/stack-ui/src/components/simple-tooltip.tsx (1)
SimpleTooltip(5-46)packages/stack-ui/src/components/ui/input.tsx (1)
Input(10-41)apps/dashboard/src/lib/utils.tsx (1)
cn(7-9)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page-client.tsx (5)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (1)
useAdminApp(27-34)apps/dashboard/src/components/payments/item-dialog.tsx (1)
ItemDialog(20-188)apps/dashboard/src/components/data-table/team-member-search-table.tsx (1)
TeamMemberSearchTable(9-68)apps/dashboard/src/components/data-table/team-search-table.tsx (1)
TeamSearchTable(25-71)apps/dashboard/src/components/form-dialog.tsx (1)
SmartFormDialog(11-51)
apps/dashboard/src/components/data-table/team-search-table.tsx (5)
packages/stack-ui/src/components/data-table/toolbar-items.tsx (1)
SearchToolbarItem(4-13)apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (1)
useAdminApp(27-34)packages/stack-ui/src/components/data-table/column-header.tsx (1)
DataTableColumnHeader(22-51)packages/stack-ui/src/components/data-table/cells.tsx (1)
TextCell(7-43)packages/stack-ui/src/components/data-table/data-table.tsx (1)
DataTable(124-162)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: sync
- GitHub Check: Vercel Agent Review
- GitHub Check: lint_and_build (latest)
- GitHub Check: build (22.x)
- GitHub Check: docker
- GitHub Check: restart-dev-and-test
- GitHub Check: all-good
- GitHub Check: build (22.x)
- GitHub Check: docker
- GitHub Check: setup-tests
- GitHub Check: Security Check
🔇 Additional comments (2)
packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts (1)
570-582: Negative adjustments enabled appropriately.Setting
allow_negative: trueensures admin quantity decrements work with the new flows. Looks good.apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (1)
38-263: Navigation entry looks good.The new Items route hooks cleanly into the payments section and icon import; no issues spotted.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page-client.tsx
Show resolved
Hide resolved
| const [itemId, setItemId] = useState(editingItem?.id || ""); | ||
| const [displayName, setDisplayName] = useState(editingItem?.displayName || ""); | ||
| const [customerType, setCustomerType] = useState<'user' | 'team' | 'custom'>(forceCustomerType || editingItem?.customerType || 'user'); | ||
| const [errors, setErrors] = useState<Record<string, string>>({}); | ||
|
|
||
| const validateAndSave = async () => { | ||
| const newErrors: Record<string, string> = {}; | ||
|
|
||
| // Validate item ID | ||
| if (!itemId.trim()) { | ||
| newErrors.itemId = "Item ID is required"; | ||
| } else if (!/^[a-z0-9-]+$/.test(itemId)) { | ||
| newErrors.itemId = "Item ID must contain only lowercase letters, numbers, and hyphens"; | ||
| } else if (!editingItem && existingItemIds.includes(itemId)) { | ||
| newErrors.itemId = "This item ID already exists"; | ||
| } | ||
|
|
||
| // Validate display name | ||
| if (!displayName.trim()) { | ||
| newErrors.displayName = "Display name is required"; | ||
| } | ||
|
|
||
| if (Object.keys(newErrors).length > 0) { | ||
| setErrors(newErrors); | ||
| return; | ||
| } | ||
| ) | ||
|
|
||
| export function ItemDialog({ open, onOpenChange, project, mode, initial }: Props) { | ||
| const itemSchema = yup.object({ | ||
| itemId: userSpecifiedIdSchema("itemId").defined().label("Item ID"), | ||
| displayName: yup.string().optional().label("Display Name"), | ||
| customerType: yup.string().oneOf(["user", "team", "custom"]).defined().label("Customer Type"), | ||
| }); | ||
| await onSave({ | ||
| id: itemId.trim(), | ||
| displayName: displayName.trim(), | ||
| customerType | ||
| }); | ||
|
|
||
| handleClose(); | ||
| }; | ||
|
|
||
| useEffect(() => { | ||
| if (forceCustomerType || editingItem?.customerType) { | ||
| setCustomerType(forceCustomerType || editingItem?.customerType || 'user'); | ||
| } | ||
| }, [forceCustomerType, editingItem]); | ||
|
|
||
| const handleClose = () => { | ||
| if (!editingItem) { | ||
| setItemId(""); | ||
| setDisplayName(""); | ||
| setCustomerType('user'); | ||
| } | ||
| setErrors({}); | ||
| onOpenChange(false); | ||
| }; |
There was a problem hiding this comment.
Sync dialog state with editingItem.
Because the local state is initialised only once, switching into edit mode leaves the inputs blank (or stuck with whatever was typed during the previous create flow). Likewise, after editing an item and closing, reopening in “create” mode still shows the last edited values. The dialog never reacts to editingItem/forceCustomerType changes, so the form can’t reliably load the record you’re trying to edit. Please wire the state to those props before shipping.
Suggested fix:
@@
- const [itemId, setItemId] = useState(editingItem?.id || "");
- const [displayName, setDisplayName] = useState(editingItem?.displayName || "");
- const [customerType, setCustomerType] = useState<'user' | 'team' | 'custom'>(forceCustomerType || editingItem?.customerType || 'user');
- const [errors, setErrors] = useState<Record<string, string>>({});
+ const [itemId, setItemId] = useState(editingItem?.id || "");
+ const [displayName, setDisplayName] = useState(editingItem?.displayName || "");
+ const [customerType, setCustomerType] = useState<'user' | 'team' | 'custom'>(forceCustomerType || editingItem?.customerType || 'user');
+ const [errors, setErrors] = useState<Record<string, string>>({});
+
+ useEffect(() => {
+ if (editingItem) {
+ setItemId(editingItem.id);
+ setDisplayName(editingItem.displayName);
+ } else {
+ setItemId("");
+ setDisplayName("");
+ }
+ setErrors({});
+ }, [editingItem]);
+
+ useEffect(() => {
+ setCustomerType(forceCustomerType ?? editingItem?.customerType ?? 'user');
+ }, [editingItem, forceCustomerType]);
@@
- useEffect(() => {
- if (forceCustomerType || editingItem?.customerType) {
- setCustomerType(forceCustomerType || editingItem?.customerType || 'user');
- }
- }, [forceCustomerType, editingItem]);
+ 📝 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 [itemId, setItemId] = useState(editingItem?.id || ""); | |
| const [displayName, setDisplayName] = useState(editingItem?.displayName || ""); | |
| const [customerType, setCustomerType] = useState<'user' | 'team' | 'custom'>(forceCustomerType || editingItem?.customerType || 'user'); | |
| const [errors, setErrors] = useState<Record<string, string>>({}); | |
| const validateAndSave = async () => { | |
| const newErrors: Record<string, string> = {}; | |
| // Validate item ID | |
| if (!itemId.trim()) { | |
| newErrors.itemId = "Item ID is required"; | |
| } else if (!/^[a-z0-9-]+$/.test(itemId)) { | |
| newErrors.itemId = "Item ID must contain only lowercase letters, numbers, and hyphens"; | |
| } else if (!editingItem && existingItemIds.includes(itemId)) { | |
| newErrors.itemId = "This item ID already exists"; | |
| } | |
| // Validate display name | |
| if (!displayName.trim()) { | |
| newErrors.displayName = "Display name is required"; | |
| } | |
| if (Object.keys(newErrors).length > 0) { | |
| setErrors(newErrors); | |
| return; | |
| } | |
| ) | |
| export function ItemDialog({ open, onOpenChange, project, mode, initial }: Props) { | |
| const itemSchema = yup.object({ | |
| itemId: userSpecifiedIdSchema("itemId").defined().label("Item ID"), | |
| displayName: yup.string().optional().label("Display Name"), | |
| customerType: yup.string().oneOf(["user", "team", "custom"]).defined().label("Customer Type"), | |
| }); | |
| await onSave({ | |
| id: itemId.trim(), | |
| displayName: displayName.trim(), | |
| customerType | |
| }); | |
| handleClose(); | |
| }; | |
| useEffect(() => { | |
| if (forceCustomerType || editingItem?.customerType) { | |
| setCustomerType(forceCustomerType || editingItem?.customerType || 'user'); | |
| } | |
| }, [forceCustomerType, editingItem]); | |
| const handleClose = () => { | |
| if (!editingItem) { | |
| setItemId(""); | |
| setDisplayName(""); | |
| setCustomerType('user'); | |
| } | |
| setErrors({}); | |
| onOpenChange(false); | |
| }; | |
| const [itemId, setItemId] = useState(editingItem?.id || ""); | |
| const [displayName, setDisplayName] = useState(editingItem?.displayName || ""); | |
| const [customerType, setCustomerType] = useState<'user' | 'team' | 'custom'>(forceCustomerType || editingItem?.customerType || 'user'); | |
| const [errors, setErrors] = useState<Record<string, string>>({}); | |
| // Sync itemId, displayName, and errors when editingItem changes | |
| useEffect(() => { | |
| if (editingItem) { | |
| setItemId(editingItem.id); | |
| setDisplayName(editingItem.displayName); | |
| } else { | |
| setItemId(""); | |
| setDisplayName(""); | |
| } | |
| setErrors({}); | |
| }, [editingItem]); | |
| // Sync customerType when editingItem or forceCustomerType changes | |
| useEffect(() => { | |
| setCustomerType(forceCustomerType ?? editingItem?.customerType ?? 'user'); | |
| }, [editingItem, forceCustomerType]); | |
| const validateAndSave = async () => { | |
| const newErrors: Record<string, string> = {}; | |
| // Validate item ID | |
| if (!itemId.trim()) { | |
| newErrors.itemId = "Item ID is required"; | |
| } else if (!/^[a-z0-9-]+$/.test(itemId)) { | |
| newErrors.itemId = "Item ID must contain only lowercase letters, numbers, and hyphens"; | |
| } else if (!editingItem && existingItemIds.includes(itemId)) { | |
| newErrors.itemId = "This item ID already exists"; | |
| } | |
| // Validate display name | |
| if (!displayName.trim()) { | |
| newErrors.displayName = "Display name is required"; | |
| } | |
| if (Object.keys(newErrors).length > 0) { | |
| setErrors(newErrors); | |
| return; | |
| } | |
| await onSave({ | |
| id: itemId.trim(), | |
| displayName: displayName.trim(), | |
| customerType | |
| }); | |
| handleClose(); | |
| }; | |
| const handleClose = () => { | |
| if (!editingItem) { | |
| setItemId(""); | |
| setDisplayName(""); | |
| setCustomerType('user'); | |
| } | |
| setErrors({}); | |
| onOpenChange(false); | |
| }; |
🤖 Prompt for AI Agents
In apps/dashboard/src/components/payments/item-dialog.tsx around lines 28 to 78,
the local form state is only set on initial render so the dialog doesn’t update
when editingItem or forceCustomerType change; add a useEffect that watches
[editingItem, forceCustomerType] and sets itemId to editingItem?.id || "" (or ""
for create), displayName to editingItem?.displayName || "", and customerType to
forceCustomerType || editingItem?.customerType || 'user' so the form loads the
correct values when switching between create/edit and resets to defaults when
closing; keep existing handleClose behavior to clear state on create close.
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/e2e/tests/js/payments.test.ts(2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.test.{ts,tsx,js}
📄 CodeRabbit inference engine (AGENTS.md)
In tests, prefer .toMatchInlineSnapshot where possible; refer to snapshot-serializer.ts for snapshot formatting and handling of non-deterministic values
Files:
apps/e2e/tests/js/payments.test.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Prefer ES6 Map over Record when representing key–value collections
Files:
apps/e2e/tests/js/payments.test.ts
🧬 Code graph analysis (1)
apps/e2e/tests/js/payments.test.ts (1)
apps/e2e/tests/js/js-helpers.ts (1)
createApp(41-86)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: Vercel Agent Review
- GitHub Check: docker
- GitHub Check: build (22.x)
- GitHub Check: lint_and_build (latest)
- GitHub Check: restart-dev-and-test
- GitHub Check: all-good
- GitHub Check: setup-tests
- GitHub Check: docker
- GitHub Check: build (22.x)
- GitHub Check: Security Check
🔇 Additional comments (1)
apps/e2e/tests/js/payments.test.ts (1)
168-168: LGTM!The addition of
serverAppto the destructuring aligns with the updatedcreateApphelper function signature and is necessary for the server-side API usage below.
| // Try to decrease by 1 (should fail with KnownErrors.ItemQuantityInsufficientAmount) | ||
| await expect(adminApp.createItemQuantityChange({ teamId: team.id, itemId, quantity: -1 })) | ||
| .rejects.toThrow(); | ||
| const item = await serverApp.getItem({ teamId: team.id, itemId }); | ||
| const success = await item.tryDecreaseQuantity(1); | ||
| expect(success).toBe(false); |
There was a problem hiding this comment.
Update the outdated comment.
The comment on line 199 mentions that the operation "should fail with KnownErrors.ItemQuantityInsufficientAmount", but the new implementation uses tryDecreaseQuantity() which returns a boolean (false on failure) instead of throwing an exception. The test logic correctly expects false, but the comment no longer reflects the actual behavior.
Apply this diff to update the comment:
- // Try to decrease by 1 (should fail with KnownErrors.ItemQuantityInsufficientAmount)
+ // Try to decrease by 1 (should return false as quantity cannot go below zero)Additionally, the implementation change from exception-based to boolean-based error handling is good—it provides a cleaner API for attempting operations that may fail due to business constraints.
📝 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 to decrease by 1 (should fail with KnownErrors.ItemQuantityInsufficientAmount) | |
| await expect(adminApp.createItemQuantityChange({ teamId: team.id, itemId, quantity: -1 })) | |
| .rejects.toThrow(); | |
| const item = await serverApp.getItem({ teamId: team.id, itemId }); | |
| const success = await item.tryDecreaseQuantity(1); | |
| expect(success).toBe(false); | |
| // Try to decrease by 1 (should return false as quantity cannot go below zero) | |
| const item = await serverApp.getItem({ teamId: team.id, itemId }); | |
| const success = await item.tryDecreaseQuantity(1); | |
| expect(success).toBe(false); |
🤖 Prompt for AI Agents
In apps/e2e/tests/js/payments.test.ts around lines 199-202, the inline comment
is outdated — it still says the decrease "should fail with
KnownErrors.ItemQuantityInsufficientAmount" even though tryDecreaseQuantity()
now returns a boolean; update the comment to state that tryDecreaseQuantity(1)
should return false when quantity is insufficient (i.e., the method returns a
boolean indicating success/failure rather than throwing an exception), leaving
the test assertion expect(success).toBe(false) unchanged.
| const handleSaveItem = async (item: { id: string, displayName: string, customerType: 'user' | 'team' | 'custom' }) => { | ||
| await project.updateConfig({ [`payments.items.${item.id}`]: { displayName: item.displayName, customerType: item.customerType } }); | ||
| setShowItemDialog(false); | ||
| }; |
There was a problem hiding this comment.
The handleSaveItem function doesn't provide user feedback via toast notifications, unlike similar functions in other parts of the codebase, leading to inconsistent user experience.
View Details
📝 Patch Details
diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page-client.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page-client.tsx
index d700f66e..f88be3da 100644
--- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page-client.tsx
+++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page-client.tsx
@@ -77,6 +77,7 @@ export default function PageClient() {
const handleSaveItem = async (item: { id: string, displayName: string, customerType: 'user' | 'team' | 'custom' }) => {
await project.updateConfig({ [`payments.items.${item.id}`]: { displayName: item.displayName, customerType: item.customerType } });
setShowItemDialog(false);
+ toast({ title: "Item created" });
};
return (
Analysis
Missing toast notification in handleSaveItem function creates inconsistent UX
What fails: The handleSaveItem function in apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page-client.tsx successfully creates/updates items but provides no user feedback, unlike identical functions in other files.
How to reproduce:
- Navigate to a project's Items page
- Click "Create User Item" (or Team/Custom Item)
- Fill in item details and save
- Observe no success feedback is shown to user
Result: Users get no confirmation that their item was created successfully, creating poor UX
Expected: Should show toast notification like similar functions in page-client-catalogs-view.tsx line 1544 and page-client-list-view.tsx line 753: toast({ title: "Item created" })
https://www.loom.com/share/cd979cfbd6e64dc9827bc50f1bb62085?sid=bf5bc9b1-5a7a-4c0f-864f-52ab8f3c648c
High-level PR Summary
This PR adds a new Items page to the dashboard that allows admins to inspect and adjust item quantities for different customer types (users, teams, or custom customers). The new page includes functionality to select customers, view their current item quantities, and create quantity adjustments with optional expiration dates and descriptions. The existing
ItemDialogcomponent was refactored and moved to a shared location, and the old payment item table was removed. Additionally, navigation for teams was improved with clickable rows, and a new team search table component was added to support customer selection.⏱️ Estimated Review Time: 30-90 minutes
💡 Review Order Suggestion
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page.tsxapps/dashboard/src/components/payments/item-dialog.tsxapps/dashboard/src/components/data-table/team-search-table.tsxapps/dashboard/src/components/data-table/team-table.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page-client.tsxpackages/stack-ui/src/components/data-table/data-table.tsxpackages/template/src/lib/stack-app/apps/implementations/admin-app-impl.tsapps/dashboard/src/components/data-table/payment-item-table.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/item-dialog.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsxImportant
Adds a new Items page to the dashboard for managing item quantities, refactors components, and updates navigation and tests.
Itemspage inpage-client.tsxfor managing item quantities by customer type (User, Team, Custom).CustomerSelectorandItemTablecomponents inpage-client.tsx.Itemsinsidebar-layout.tsx.ItemDialogtoitem-dialog.tsxwith clearer validation and optional enforced customer type.team-table.tsx.data-table.tsx.payments.test.tsto test new item quantity functionalities.payment-item-table.tsxanditem-dialog.tsxfrompayments/productsdirectory.team-search-table.tsxandteam-table.tsx.This description was created by
for f5165f5. You can customize this summary. It will automatically update as commits are pushed.
Summary by CodeRabbit
New Features
Refactor
Tests