-
Notifications
You must be signed in to change notification settings - Fork 498
dashboard grant product #939
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
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughAdds an API-driven product grant flow and per-customer product listing: backend grant/list endpoints, payments core helpers (grant/getOwnedProducts), Prisma enum API_GRANT migration, shared schemas/types and KnownErrors.ProductAlreadyGranted, dashboard UI for Customers, template/client/server app integrations, and updated e2e tests and snapshots. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Client as Client/Server App
participant API as API /payments/products/{type}/{id}
participant Payments as grantProductToCustomer
participant DB as Prisma
participant Stripe as Stripe
Client->>API: POST grant product (product_id or inline, quantity)
API->>Payments: grantProductToCustomer(options, creationSource=API_GRANT)
Payments->>DB: validate customer & product, check ownership
alt already owned & non-stackable
Payments-->>API: throw KnownErrors.ProductAlreadyGranted
API-->>Client: 400 + { code: PRODUCT_ALREADY_GRANTED, details } + header
else not-owned or stackable
opt subscription price
Payments->>Stripe: create/retrieve subscription
Stripe-->>Payments: subscription details
end
Payments->>DB: create purchase/subscription records
Payments-->>API: { GrantProductResult }
API-->>Client: 200 OK
end
sequenceDiagram
autonumber
participant Client as Client App
participant API as GET /payments/products/{type}/{id}
participant Payments as getOwnedProductsForCustomer
participant DB as Prisma
Client->>API: GET list (cursor, limit)
API->>Payments: getOwnedProductsForCustomer(customerType, customerId)
Payments->>DB: query purchases & subscriptions
Payments-->>API: OwnedProduct[]
API->>API: filter server_only for client auth
API-->>Client: { items, is_paginated, pagination.next_cursor }
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used📓 Path-based instructions (2)packages/template/**📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/*.{ts,tsx,js,jsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
🧬 Code graph analysis (1)packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (4)
⏰ 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)
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 reorganizes the dashboard payment system from an item-centric to customer-centric approach. The main changes include:-
Dashboard Navigation Restructuring: The sidebar navigation is updated to replace the "Items" menu item with "Customers" under the payments section, moving from
/itemsto/payments/customersand changing the icon from Package to UserRound. -
Page Reorganization: The items page is renamed and moved from
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/items/page.tsxtoapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/customers/page.tsx, with the metadata title updated accordingly. -
Enhanced Customer Management: The page-client component is significantly expanded to include product granting functionality. This adds a "Grant Product" button, a comprehensive GrantProductDialog component for product selection, conditional quantity fields for stackable products, and robust error handling.
-
Backend Payment Logic Improvements: The
grantProductToCustomerfunction in the payments module is enhanced to properly handle subscription cancellation when granting conflicting products by setting bothcurrentPeriodEndandcancelAtPeriodEndfields. -
Code Quality Enhancement: A minor but important variable rename from
cachetoitemsCachein the server implementation improves code clarity by distinguishing between different cache types.
This refactoring aligns with a broader shift toward organizing payment-related features under a unified payments section and transitioning from generic item management to customer-focused product management workflows.
Changed Files
| Filename | Score | Overview |
|---|---|---|
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/customers/page.tsx |
5/5 | Renamed from items/page.tsx with updated metadata title from 'Item Quantities' to 'Customers' |
apps/backend/src/lib/payments.tsx |
4/5 | Enhanced subscription cancellation logic in grantProductToCustomer with proper period termination |
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx |
5/5 | Updated navigation from 'Items' to 'Customers' with new route and UserRound icon |
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/customers/page-client.tsx |
4/5 | Major transformation adding product granting functionality with dialog, validation, and error handling |
packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts |
5/5 | Minor variable rename from 'cache' to 'itemsCache' for improved code clarity |
Confidence score: 4/5
- This PR is generally safe to merge with some attention needed for the payment logic changes
- Score reflects solid refactoring with one area requiring careful validation of subscription cancellation behavior
- Pay close attention to the payments.tsx file to ensure subscription cancellation logic works correctly in production
Sequence Diagram
sequenceDiagram
participant User
participant Dashboard as "Dashboard UI"
participant AdminApp as "Admin App"
participant Backend as "Backend API"
participant Database as "Database"
User->>Dashboard: "Click Grant Product button"
Dashboard->>Dashboard: "Open GrantProductDialog"
User->>Dashboard: "Select customer type (user/team/custom)"
Dashboard->>Dashboard: "Update customer selector options"
User->>Dashboard: "Select specific customer"
Dashboard->>AdminApp: "Fetch customer data"
AdminApp->>Backend: "GET customer details"
Backend->>Database: "Query customer info"
Database-->>Backend: "Return customer data"
Backend-->>AdminApp: "Customer details"
AdminApp-->>Dashboard: "Customer object"
User->>Dashboard: "Select product to grant"
Dashboard->>Dashboard: "Show quantity field if stackable"
User->>Dashboard: "Set quantity (if applicable)"
User->>Dashboard: "Click Grant Product"
Dashboard->>AdminApp: "Call grantProduct()"
AdminApp->>Backend: "POST /api/latest/products/grant"
Backend->>Backend: "Validate purchase session"
Backend->>Backend: "Call grantProductToCustomer()"
Backend->>Database: "Create subscription or one-time purchase"
Database-->>Backend: "Purchase record created"
Backend->>Database: "Update item quantities for included items"
Database-->>Backend: "Items updated"
Backend-->>AdminApp: "Grant successful"
AdminApp-->>Dashboard: "Success response"
Dashboard->>Dashboard: "Show success toast"
Dashboard->>AdminApp: "Refresh item quantities"
AdminApp->>Backend: "GET updated item data"
Backend->>Database: "Query current quantities"
Database-->>Backend: "Updated quantities"
Backend-->>AdminApp: "Current item state"
AdminApp-->>Dashboard: "Updated item display"
5 files reviewed, no comments
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 93c5708..42b4100
✨ No bugs found, your code is sparkling clean
✅ Files analyzed, no issues (5)
• apps/backend/src/lib/payments.tsx
• apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/customers/page-client.tsx
• apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/customers/page.tsx
• apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
• packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts
… into product-sdk-functions
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: 3
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)
75-83: Prevent row navigation when clicking row actionsWith the new
onRowClick, any click inside the row (including action buttons/dropdowns) now triggers navigation before the button handler runs. In the updatedTeamTable, clicking “Edit” or the action menu immediately redirects to the team page, making the actions unusable.Skip invoking
onRowClickwhen the event originates from an interactive descendant (button, link, etc.). For example:- 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 (!props.onRowClick || ev.defaultPrevented) return; + const target = ev.target; + if (!(target instanceof HTMLElement)) return; + if (!ev.currentTarget.contains(target)) return; + if (target.closest('button, a, input, textarea, select, [role="button"], [data-no-row-click]')) { + return; + } + props.onRowClick(row.original); + }}
🧹 Nitpick comments (4)
apps/dashboard/src/components/payments/item-dialog.tsx (4)
181-183: Prevent double submits and communicate progress on SaveDisable while saving and show a saving label.
- <Button onClick={validateAndSave}> - {editingItem ? "Save Changes" : "Create Item"} - </Button> + <Button onClick={validateAndSave} disabled={isSaving} aria-busy={isSaving}> + {isSaving ? "Saving..." : (editingItem ? "Save Changes" : "Create Item")} + </Button>
175-176: Surface form-level errors inline (no toasts for blocking errors)Show any onSave failure message above the footer.
</div> + + {errors.form && ( + <Typography type="label" className="text-destructive" role="alert"> + {errors.form} + </Typography> + )} <DialogFooter>As per coding guidelines
70-78: Respect Dialog’s onOpenChange contract; only reset on closePass a handler that propagates open=true and invokes handleClose on close. Improves type-safety and avoids forcing close on open events.
const handleClose = () => { if (!editingItem) { setItemId(""); setDisplayName(""); setCustomerType('user'); } setErrors({}); onOpenChange(false); }; + + const handleDialogOpenChange = (nextOpen: boolean) => { + if (!nextOpen) { + handleClose(); + } else { + onOpenChange(true); + } + };- <Dialog open={open} onOpenChange={handleClose}> + <Dialog open={open} onOpenChange={handleDialogOpenChange}>Also applies to: 81-82
98-121: Add basic a11y: connect inputs to errorsMark invalid fields and link to error text for screen readers.
<Input id="item-id" value={itemId} onChange={(e) => { const nextValue = e.target.value.toLowerCase(); setItemId(nextValue); if (errors.itemId) { setErrors(prev => { const newErrors = { ...prev }; delete newErrors.itemId; return newErrors; }); } }} placeholder="e.g., api-calls" disabled={!!editingItem} className={cn(errors.itemId ? "border-destructive" : "")} + aria-invalid={!!errors.itemId} + aria-describedby={errors.itemId ? "item-id-error" : undefined} /> {errors.itemId && ( - <Typography type="label" className="text-destructive"> + <Typography id="item-id-error" type="label" className="text-destructive" role="alert"> {errors.itemId} </Typography> )}<Input id="display-name" value={displayName} onChange={(e) => { setDisplayName(e.target.value); if (errors.displayName) { setErrors(prev => { const newErrors = { ...prev }; delete newErrors.displayName; return newErrors; }); } }} placeholder="e.g., API Calls" className={cn(errors.displayName ? "border-destructive" : "")} + aria-invalid={!!errors.displayName} + aria-describedby={errors.displayName ? "display-name-error" : undefined} /> {errors.displayName && ( - <Typography type="label" className="text-destructive"> + <Typography id="display-name-error" type="label" className="text-destructive" role="alert"> {errors.displayName} </Typography> )}Also applies to: 130-151
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (37)
apps/backend/prisma/migrations/20251008182311_api_grant_purchase_source/migration.sql(1 hunks)apps/backend/prisma/schema.prisma(1 hunks)apps/backend/src/app/api/latest/internal/payments/test-mode-purchase-session/route.tsx(2 hunks)apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.ts(1 hunks)apps/backend/src/app/api/latest/payments/purchases/create-purchase-url/route.ts(1 hunks)apps/backend/src/app/api/latest/payments/purchases/validate-code/route.ts(3 hunks)apps/backend/src/lib/payments.test.tsx(3 hunks)apps/backend/src/lib/payments.tsx(6 hunks)apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/customers/page-client.tsx(1 hunks)apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/customers/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)apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--purchase-session.test.ts(1 hunks)apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--validate-code.test.ts(5 hunks)apps/e2e/tests/backend/endpoints/api/v1/payments/create-purchase-url.test.ts(1 hunks)apps/e2e/tests/backend/endpoints/api/v1/payments/products.test.ts(1 hunks)apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts(1 hunks)apps/e2e/tests/backend/endpoints/api/v1/payments/validate-code.test.ts(7 hunks)apps/e2e/tests/js/payments.test.ts(3 hunks)packages/stack-shared/src/interface/client-interface.ts(2 hunks)packages/stack-shared/src/interface/crud/products.ts(1 hunks)packages/stack-shared/src/interface/server-interface.ts(2 hunks)packages/stack-shared/src/known-errors.tsx(2 hunks)packages/stack-shared/src/schema-fields.ts(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)packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts(6 hunks)packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts(4 hunks)packages/template/src/lib/stack-app/apps/interfaces/client-app.ts(2 hunks)packages/template/src/lib/stack-app/apps/interfaces/server-app.ts(3 hunks)packages/template/src/lib/stack-app/customers/index.ts(2 hunks)
💤 Files with no reviewable changes (2)
- apps/dashboard/src/components/data-table/payment-item-table.tsx
- apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/item-dialog.tsx
🧰 Additional context used
📓 Path-based instructions (6)
**/*.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/backend/src/lib/payments.test.tsxapps/e2e/tests/backend/endpoints/api/v1/payments/validate-code.test.tsapps/e2e/tests/js/payments.test.tsapps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.tsapps/e2e/tests/backend/endpoints/api/v1/payments/products.test.tsapps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--validate-code.test.tsapps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--purchase-session.test.tsapps/e2e/tests/backend/endpoints/api/v1/payments/create-purchase-url.test.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Prefer ES6 Map over Record when representing key–value collections
Files:
apps/backend/src/lib/payments.test.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsxapps/e2e/tests/backend/endpoints/api/v1/payments/validate-code.test.tspackages/stack-shared/src/interface/crud/products.tspackages/stack-shared/src/schema-fields.tspackages/template/src/lib/stack-app/apps/implementations/admin-app-impl.tsapps/e2e/tests/js/payments.test.tsapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/customers/page.tsxpackages/stack-shared/src/known-errors.tsxapps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.tsapps/dashboard/src/components/data-table/team-search-table.tsxpackages/stack-shared/src/interface/server-interface.tsapps/dashboard/src/components/data-table/team-table.tsxapps/backend/src/app/api/latest/payments/purchases/create-purchase-url/route.tsapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsxapps/e2e/tests/backend/endpoints/api/v1/payments/products.test.tsapps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--validate-code.test.tsapps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--purchase-session.test.tspackages/stack-shared/src/interface/client-interface.tspackages/template/src/lib/stack-app/apps/interfaces/server-app.tsapps/e2e/tests/backend/endpoints/api/v1/payments/create-purchase-url.test.tspackages/template/src/lib/stack-app/customers/index.tspackages/stack-ui/src/components/data-table/data-table.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsxapps/backend/src/app/api/latest/internal/payments/test-mode-purchase-session/route.tsxapps/dashboard/src/components/payments/item-dialog.tsxpackages/template/src/lib/stack-app/apps/interfaces/client-app.tspackages/template/src/lib/stack-app/apps/implementations/server-app-impl.tspackages/template/src/lib/stack-app/apps/implementations/client-app-impl.tsapps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.tsapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/customers/page-client.tsxapps/backend/src/lib/payments.tsxapps/backend/src/app/api/latest/payments/purchases/validate-code/route.ts
{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]/payments/products/page-client-list-view.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/customers/page.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]/payments/products/page-client-catalogs-view.tsxpackages/stack-ui/src/components/data-table/data-table.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsxapps/dashboard/src/components/payments/item-dialog.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/customers/page-client.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]/payments/products/page-client-list-view.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/customers/page.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]/payments/products/page-client-catalogs-view.tsxpackages/stack-ui/src/components/data-table/data-table.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsxapps/dashboard/src/components/payments/item-dialog.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/customers/page-client.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.tspackages/template/src/lib/stack-app/apps/interfaces/server-app.tspackages/template/src/lib/stack-app/customers/index.tspackages/template/src/lib/stack-app/apps/interfaces/client-app.tspackages/template/src/lib/stack-app/apps/implementations/server-app-impl.tspackages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts
apps/backend/src/app/api/latest/**
📄 CodeRabbit inference engine (AGENTS.md)
apps/backend/src/app/api/latest/**: Organize backend API routes by resource under /api/latest (e.g., auth at /api/latest/auth/, users at /api/latest/users/, teams at /api/latest/teams/, oauth providers at /api/latest/oauth-providers/)
Use the custom route handler system in the backend to ensure consistent API responses
Files:
apps/backend/src/app/api/latest/payments/purchases/create-purchase-url/route.tsapps/backend/src/app/api/latest/internal/payments/test-mode-purchase-session/route.tsxapps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.tsapps/backend/src/app/api/latest/payments/purchases/validate-code/route.ts
🧬 Code graph analysis (22)
apps/backend/src/lib/payments.test.tsx (1)
packages/stack-shared/src/known-errors.tsx (2)
KnownErrors(1589-1591)KnownErrors(1593-1716)
packages/stack-shared/src/interface/crud/products.ts (1)
packages/stack-shared/src/schema-fields.ts (6)
yupObject(247-251)yupString(187-190)yupNumber(191-194)inlineProductSchema(593-615)yupArray(213-216)yupBoolean(195-198)
apps/e2e/tests/js/payments.test.ts (1)
apps/e2e/tests/js/js-helpers.ts (1)
createApp(41-86)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/customers/page.tsx (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/customers/page-client.tsx (1)
PageClient(51-188)
packages/stack-shared/src/known-errors.tsx (1)
packages/stack-shared/src/index.ts (1)
KnownError(11-11)
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)
packages/stack-shared/src/interface/server-interface.ts (4)
packages/stack-shared/src/schema-fields.ts (1)
inlineProductSchema(593-615)packages/stack-shared/src/utils/errors.tsx (1)
StackAssertionError(69-85)packages/stack-shared/src/utils/objects.tsx (1)
filterUndefined(373-375)packages/stack-shared/src/utils/urls.tsx (1)
urlString(314-316)
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/backend/src/app/api/latest/payments/purchases/create-purchase-url/route.ts (1)
packages/stack-shared/src/known-errors.tsx (2)
KnownErrors(1589-1591)KnownErrors(1593-1716)
apps/e2e/tests/backend/endpoints/api/v1/payments/products.test.ts (3)
apps/e2e/tests/helpers.ts (1)
it(11-11)apps/e2e/tests/backend/backend-helpers.ts (1)
niceBackendFetch(107-171)packages/stack-shared/src/utils/uuids.tsx (1)
generateUuid(3-8)
packages/stack-shared/src/interface/client-interface.ts (4)
packages/stack-shared/src/interface/crud/products.ts (2)
ListCustomerProductsOptions(22-27)CustomerProductsListResponse(20-20)packages/stack-shared/src/sessions.ts (1)
InternalSession(71-241)packages/stack-shared/src/utils/objects.tsx (1)
filterUndefined(373-375)packages/stack-shared/src/utils/urls.tsx (1)
urlString(314-316)
packages/template/src/lib/stack-app/apps/interfaces/server-app.ts (2)
packages/template/src/lib/stack-app/customers/index.ts (3)
InlineProduct(5-5)CustomerProductsRequestOptions(53-56)CustomerProductsList(44-46)packages/template/src/lib/stack-app/common.ts (1)
AsyncStoreProperty(9-11)
packages/template/src/lib/stack-app/customers/index.ts (1)
packages/template/src/lib/stack-app/common.ts (1)
AsyncStoreProperty(9-11)
apps/backend/src/app/api/latest/internal/payments/test-mode-purchase-session/route.tsx (1)
apps/backend/src/lib/payments.tsx (1)
grantProductToCustomer(550-635)
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)
packages/template/src/lib/stack-app/apps/interfaces/client-app.ts (2)
packages/template/src/lib/stack-app/common.ts (1)
AsyncStoreProperty(9-11)packages/template/src/lib/stack-app/customers/index.ts (2)
CustomerProductsRequestOptions(53-56)CustomerProductsList(44-46)
packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts (3)
packages/template/src/lib/stack-app/apps/implementations/common.ts (1)
createCache(22-27)packages/stack-shared/src/interface/crud/products.ts (1)
CustomerProductsListResponse(20-20)packages/template/src/lib/stack-app/customers/index.ts (2)
Customer(58-83)InlineProduct(5-5)
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (4)
packages/template/src/lib/stack-app/apps/implementations/common.ts (2)
createCacheBySession(29-39)useAsyncCache(146-191)packages/stack-shared/src/interface/crud/products.ts (1)
CustomerProductsListResponse(20-20)packages/template/src/lib/stack-app/customers/index.ts (4)
CustomerProductsList(44-46)Customer(58-83)CustomerProductsListOptions(48-51)CustomerProductsRequestOptions(53-56)packages/stack-shared/src/sessions.ts (1)
InternalSession(71-241)
apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.ts (7)
apps/backend/src/route-handlers/smart-route-handler.tsx (1)
createSmartRouteHandler(209-294)packages/stack-shared/src/schema-fields.ts (8)
yupObject(247-251)clientOrHigherAuthTypeSchema(481-481)adaptSchema(330-330)yupString(187-190)yupNumber(191-194)serverOrHigherAuthTypeSchema(482-482)inlineProductSchema(593-615)yupBoolean(195-198)packages/stack-shared/src/interface/crud/products.ts (1)
customerProductsListResponseSchema(12-18)apps/backend/src/prisma-client.tsx (1)
getPrismaClientForTenancy(64-66)apps/backend/src/lib/payments.tsx (4)
getOwnedProductsForCustomer(646-706)productToInlineProduct(423-436)ensureProductIdOrInlineProduct(21-71)grantProductToCustomer(550-635)packages/stack-shared/src/utils/errors.tsx (1)
StatusError(152-261)packages/stack-shared/src/known-errors.tsx (2)
KnownErrors(1589-1591)KnownErrors(1593-1716)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/customers/page-client.tsx (9)
packages/stack-shared/src/config/schema.ts (1)
CompleteConfig(1074-1074)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/form-fields.tsx (2)
SelectField(229-266)NumberField(305-342)packages/stack-shared/src/utils/promises.tsx (1)
runAsynchronously(343-366)apps/dashboard/src/components/form-dialog.tsx (1)
SmartFormDialog(11-51)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)packages/stack-shared/src/utils/results.tsx (1)
error(36-41)
apps/backend/src/lib/payments.tsx (9)
packages/stack-shared/src/schema-fields.ts (2)
productSchema(569-592)inlineProductSchema(593-615)packages/stack-shared/src/utils/objects.tsx (5)
typedFromEntries(281-283)typedEntries(263-265)filterUndefined(373-375)getOrUndefined(545-548)typedValues(317-319)packages/stack-shared/src/utils/currency-constants.tsx (1)
SUPPORTED_CURRENCIES(9-45)packages/stack-shared/src/known-errors.tsx (2)
KnownErrors(1589-1591)KnownErrors(1593-1716)apps/backend/src/prisma-client.tsx (1)
PrismaClientTransaction(18-18)apps/backend/src/lib/tenancies.tsx (1)
Tenancy(47-47)apps/backend/src/lib/stripe.tsx (1)
getStripeForAccount(25-47)packages/stack-shared/src/utils/strings.tsx (1)
typedToUppercase(30-33)packages/stack-shared/src/utils/dates.tsx (1)
addInterval(197-199)
apps/backend/src/app/api/latest/payments/purchases/validate-code/route.ts (2)
packages/stack-shared/src/schema-fields.ts (1)
inlineProductSchema(593-615)apps/backend/src/lib/payments.tsx (1)
productToInlineProduct(423-436)
🪛 Biome (2.1.2)
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts
[error] 1378-1378: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 1381-1381: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 1384-1384: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
⏰ 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: build (22.x)
- GitHub Check: build (22.x)
- GitHub Check: lint_and_build (latest)
- GitHub Check: docker
- GitHub Check: docker
- GitHub Check: all-good
- GitHub Check: restart-dev-and-test
- GitHub Check: setup-tests
- GitHub Check: Vercel Agent Review
- GitHub Check: Security Check
🔇 Additional comments (14)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (2)
42-42: LGTM!The UserRound icon import is appropriate for the new "Customers" navigation item and follows the existing import pattern.
255-261: LGTM!The new "Customers" navigation item is properly structured and follows the existing pattern. The regex correctly matches the route structure, and the UserRound icon is semantically appropriate for the customers page.
packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts (1)
570-584: Clarify negative quantity allowance. The hardcodedallow_negative: truepermits both positive and negative deltas; since this PR adds an admin “Grant Product” flow (positive only), please confirm negative adjustments are intentional or whether you’d rather restrict this to >0 (or introduce a separate revoke method). Optional: add validation to ensurequantityis finite, non-zero and positive before callingupdateItemQuantity.packages/stack-shared/src/known-errors.tsx (2)
1530-1542: LGTM!The ProductAlreadyGranted error follows the established KnownError pattern correctly, with appropriate status code (400), descriptive message, and structured details containing product_id and customer_id.
1709-1709: LGTM!ProductAlreadyGranted is correctly added to the KnownErrors collection, making it available for use throughout the codebase.
apps/backend/src/lib/payments.test.tsx (3)
2-2: LGTM!Correctly imports KnownErrors from the shared package to use the new structured error types in tests.
846-846: LGTM!Test expectation correctly updated to throw KnownErrors.ProductAlreadyGranted with the appropriate productId and customerId parameters.
1007-1007: LGTM!Test expectation correctly updated to throw KnownErrors.ProductAlreadyGranted with the appropriate productId and customerId parameters.
apps/backend/src/app/api/latest/payments/purchases/create-purchase-url/route.ts (1)
57-57: LGTM!Correctly replaces the generic StatusError with the structured KnownErrors.ProductAlreadyGranted error, providing better error handling and consistent error responses across the API.
apps/backend/prisma/migrations/20251008182311_api_grant_purchase_source/migration.sql (1)
1-2: LGTM!The migration correctly adds the API_GRANT value to the PurchaseCreationSource enum using the standard PostgreSQL ALTER TYPE syntax.
apps/backend/prisma/schema.prisma (1)
759-759: LGTM!The schema correctly includes the API_GRANT value in the PurchaseCreationSource enum, matching the migration applied in 20251008182311_api_grant_purchase_source/migration.sql.
apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts (1)
815-827: LGTM!Test snapshot correctly updated to expect the structured error response with:
- code: "PRODUCT_ALREADY_GRANTED"
- details containing customer_id and product_id
- descriptive error message
- x-stack-known-error header
This aligns with the new KnownErrors.ProductAlreadyGranted error structure.
apps/e2e/tests/backend/endpoints/api/v1/payments/create-purchase-url.test.ts (1)
387-403: LGTM!Test snapshot correctly updated to expect the structured error response with code "PRODUCT_ALREADY_GRANTED", details containing customer_id and product_id, and the x-stack-known-error header. This is consistent with the new KnownErrors handling introduced across the codebase.
apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--purchase-session.test.ts (1)
799-811: LGTM!Test snapshot correctly updated to expect the structured error response with the PRODUCT_ALREADY_GRANTED error code, structured details, and appropriate headers. The changes align with the broader refactoring to use KnownErrors throughout the payments flow.
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts
Show resolved
Hide resolved
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 42b4100..f0d112f
✨ No bugs found, your code is sparkling clean
✅ Files analyzed, no issues (50)
• apps/backend/src/app/api/latest/payments/products/[customer_type]/[customer_id]/route.ts
• apps/backend/src/app/api/latest/payments/purchases/create-purchase-url/route.ts
• apps/backend/src/lib/payments.tsx
• apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsx
• apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
• apps/dashboard/src/components/code-block.tsx
• apps/dev-launchpad/public/index.html
• apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--create-purchase-url.test.ts
• apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--purchase-session.test.ts
• apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--validate-code.test.ts
• apps/e2e/tests/backend/endpoints/api/v1/payments/create-purchase-url.test.ts
• apps/e2e/tests/backend/endpoints/api/v1/payments/products.test.ts
• apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts
• apps/e2e/tests/backend/endpoints/api/v1/payments/validate-code.test.ts
• configs/tsup/js-library.ts
• docs/docs-platform.yml
• docs/public/imgs/mcp.svg
• docs/public/imgs/vscode.svg
• docs/src/components/icons.tsx
• docs/src/components/mdx/button.tsx
• docs/src/components/mdx/info.tsx
• docs/src/mdx-components.tsx
• docs/templates/meta.json
• docs/templates/others/mcp-setup.mdx
• examples/convex/.env.development
• examples/convex/.gitignore
• examples/convex/app/page.tsx
• examples/convex/convex/_generated/api.d.ts
• examples/convex/convex/_generated/api.js
• examples/convex/convex/_generated/server.d.ts
• examples/convex/convex/_generated/server.js
• examples/convex/convex/auth.config.ts
• examples/convex/convex/convex.config.ts
• examples/convex/convex/myFunctions.ts
• examples/convex/globals.css
• examples/convex/package.json
• examples/convex/postcss.config.mjs
• examples/convex/stack/server.tsx
• examples/convex/tailwind.config.js
• packages/js/package.json
• packages/react/package.json
• packages/stack-shared/src/known-errors.tsx
• packages/stack-shared/tsup.config.ts
• packages/stack-ui/src/components/ui/button.tsx
• packages/stack/package.json
• packages/template/package-template.json
• packages/template/package.json
• packages/template/src/integrations/convex.ts
• packages/template/src/integrations/convex/component/README.md
• packages/template/src/integrations/convex/component/convex.config.ts
⏭️ Files skipped (6)
| Locations |
|---|
docs/public/imgs/cursor.svg |
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts |
packages/template/src/lib/stack-app/apps/implementations/common.ts |
packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts |
packages/template/tsup.config.ts |
pnpm-lock.yaml |
https://www.loom.com/share/2767f799df9d48519c737a1d082fc3f4?sid=967802e9-5bfb-438d-96cd-2f6fcbd2f69b
High-level PR Summary
This PR adds a "Grant Product" feature to the dashboard's customer page, allowing administrators to manually grant products to users, teams, or custom customers. The UI has been updated to rename "Items" to "Customers" in the navigation, and the page now includes a dialog for selecting a product and quantity (for stackable products) to grant. Additionally, the backend payment logic has been enhanced to properly set
currentPeriodEndandcancelAtPeriodEndwhen canceling conflicting subscriptions during product grants.⏱️ Estimated Review Time: 15-30 minutes
💡 Review Order Suggestion
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/customers/page.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/customers/page-client.tsxapps/backend/src/lib/payments.tsxpackages/template/src/lib/stack-app/apps/implementations/server-app-impl.tspackages/template/src/lib/stack-app/apps/implementations/server-app-impl.tsImportant
Adds "Grant Product" feature to dashboard, enabling admins to grant products to customers, with new API endpoints, UI updates, and backend logic enhancements.
currentPeriodEndandcancelAtPeriodEndwhen canceling conflicting subscriptions.route.tsfor listing customer products and granting products.grantProductToCustomer()inpayments.tsxto handle product grants.grantProduct(server) andlistProducts(client/server) methods.client-app-impl.tsandserver-app-impl.tsto support new product functionalities.CustomerProductandCustomerProductsListtypes incustomers/index.ts.PRODUCT_ALREADY_GRANTEDerror inknown-errors.tsx.products.test.tsand other test files to cover new product grant scenarios.This description was created by
for f0d112f. You can customize this summary. It will automatically update as commits are pushed.
Summary by CodeRabbit
New Features
Improvements
UI