-
Notifications
You must be signed in to change notification settings - Fork 467
Dashboard item quantity changes #936
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
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 inspecting/managing per-customer-type item quantities; refactors and relocates 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 customer type & selector
User->>Selector: Select type + customer (user/team/custom)
Selector-->>ItemsPage: Selected customer context
ItemsPage->>ItemHook: Fetch items for selected customer (type-aware)
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(onRowClick => navigate)
User->>DataTable: click row
DataTable-->>TeamTable: onRowClick(team)
TeamTable->>Router: push(/projects/{projectId}/teams/{teamId})
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unused
"use client"; | ||
|
||
import { cn } from "@/lib/utils"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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 (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.tsx
apps/dashboard/src/components/data-table/team-table.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page.tsx
apps/dashboard/src/components/payments/item-dialog.tsx
packages/stack-ui/src/components/data-table/data-table.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page-client.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsx
apps/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.tsx
apps/dashboard/src/components/data-table/team-table.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page.tsx
packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts
apps/dashboard/src/components/payments/item-dialog.tsx
packages/stack-ui/src/components/data-table/data-table.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page-client.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsx
apps/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.tsx
apps/dashboard/src/components/data-table/team-table.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page.tsx
apps/dashboard/src/components/payments/item-dialog.tsx
packages/stack-ui/src/components/data-table/data-table.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page-client.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsx
apps/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: true
ensures 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
serverApp
to the destructuring aligns with the updatedcreateApp
helper 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, itemI 6D40 d, 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
ItemDialog
component 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.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page.tsx
apps/dashboard/src/components/payments/item-dialog.tsx
apps/dashboard/src/components/data-table/team-search-table.tsx
apps/dashboard/src/components/data-table/team-table.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page-client.tsx
packages/stack-ui/src/components/data-table/data-table.tsx
packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts
apps/dashboard/src/components/data-table/payment-item-table.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
Important
Adds a new Items page to the dashboard for managing item quantities, refactors components, and updates navigation and tests.
Items
page inpage-client.tsx
for managing item quantities by customer type (User, Team, Custom).CustomerSelector
andItemTable
components inpage-client.tsx
.Items
insidebar-layout.tsx
.ItemDialog
toitem-dialog.tsx
with clearer validation and optional enforced customer type.team-table.tsx
.data-table.tsx
.payments.test.ts
to test new item quantity functionalities.payment-item-table.tsx
anditem-dialog.tsx
frompayments/products
directory.team-search-table.tsx
andteam-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