-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Add ability to confirm selected payout #2952
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.
|
|
@coderabbitai full review |
WalkthroughIntroduces optional selectedPayoutId across payout eligibility, confirmation UI, action schema, and cron processing. Adjusts query schemas, routing params, and Prisma where clauses to conditionally filter by a single payout or exclude specified payouts. Updates UI components to ConfirmPayoutsSheet and synchronizes state via router query parameters. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant U as User
participant UI as Dashboard UI (ConfirmPayoutsSheet)
participant A as Action: confirmPayouts
participant Q as Queue (QStash)
participant R as API Route /api/cron/payouts/process
participant S as splitPayouts
participant P as processPayouts
participant DB as Database
U->>UI: Open confirm payouts (optionally selects payout)
UI->>A: submit { selectedPayoutId?, excludedPayoutIds? }
A->>Q: enqueue payload { selectedPayoutId?, excludedPayoutIds? }
Q->>R: POST payload
R->>S: splitPayouts({ selectedPayoutId?, excludedPayoutIds? })
alt selectedPayoutId provided
S->>DB: findMany({ id: selectedPayoutId })
else excludedPayoutIds provided
S->>DB: findMany({ id: { notIn: excludedPayoutIds } })
else
S->>DB: findMany({})
end
R->>P: processPayouts({ selectedPayoutId?, excludedPayoutIds? })
alt selectedPayoutId provided
P->>DB: update/process where { id: selectedPayoutId }
else excludedPayoutIds provided
P->>DB: update/process where { id: { notIn: excludedPayoutIds } }
else
P->>DB: update/process where {}
end
P-->>R: result
R-->>Q: ack
Q-->>A: delivery complete
A-->>UI: success status
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 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 |
✅ Actions performedFull review triggered. |
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: 0
🧹 Nitpick comments (2)
apps/web/ui/partners/confirm-payouts-sheet.tsx (2)
320-347: Consider disabling the cutoff period selector when a specific payout is selected.When
selectedPayoutIdis present, the cutoff period selector (lines 321-347) remains enabled, but the selected payout likely has a fixed cutoff period. This could confuse users who might expect changing the cutoff period to affect the selected payout.If the cutoff period should not apply when a specific payout is selected, consider applying this diff:
{ key: "Cutoff Period", value: ( <div className="w-full"> <Combobox options={cutoffPeriodOptions} selected={selectedCutoffPeriodOption} setSelected={(option: ComboboxOption) => { if (!option) { return; } setCutoffPeriod(option.value as CUTOFF_PERIOD_TYPES); }} placeholder="Select cutoff period" buttonProps={{ className: "h-auto border border-neutral-200 px-3 py-1.5 text-xs focus:border-neutral-600 focus:ring-neutral-600", + disabled: !!selectedPayoutId, }} matchTriggerWidth hideSearch caret /> </div> ), tooltipContent: "Cutoff period in UTC. If set, only commissions accrued up to the cutoff period will be included in the payout invoice.", },
441-471: Hide exclude/include buttons when a payout is selected
The exclusion controls never take effect whenselectedPayoutIdis set (finalEligiblePayouts bypasses exclusions), so wrap the button container in a!selectedPayoutIdcheck to prevent confusing UX.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
apps/web/app/(ee)/api/cron/payouts/process/process-payouts.ts(3 hunks)apps/web/app/(ee)/api/cron/payouts/process/route.ts(4 hunks)apps/web/app/(ee)/api/cron/payouts/process/split-payouts.ts(1 hunks)apps/web/app/(ee)/api/programs/[programId]/payouts/eligible/route.ts(3 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-details-sheet.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-stats.tsx(2 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-table.tsx(1 hunks)apps/web/lib/actions/partners/confirm-payouts.ts(3 hunks)apps/web/ui/partners/confirm-payouts-sheet.tsx(9 hunks)
⏰ 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). (1)
- GitHub Check: build
🔇 Additional comments (15)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-details-sheet.tsx (1)
254-259: LGTM!The button text change accurately reflects the single payout confirmation behavior, and the addition of
selectedPayoutIdto the query parameters properly integrates with the new confirmation flow.apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-stats.tsx (1)
6-6: LGTM!The component rename from
PayoutInvoiceSheettoConfirmPayoutsSheetaligns with the PR's refactoring of the payout confirmation workflow.Also applies to: 71-71
apps/web/app/(ee)/api/cron/payouts/process/route.ts (1)
19-19: LGTM!The addition of
selectedPayoutIdto the schema and its propagation through bothsplitPayoutsandprocessPayoutsis correctly implemented and consistent with the PR objectives.Also applies to: 38-38, 58-59, 70-70
apps/web/lib/actions/partners/confirm-payouts.ts (1)
21-21: LGTM!The addition of
selectedPayoutIdto the schema and its inclusion in the Qstash payload properly integrates with the payout processing workflow.Also applies to: 37-37, 138-138
apps/web/app/(ee)/api/programs/[programId]/payouts/eligible/route.ts (2)
13-16: LGTM!The new
eligiblePayoutsQuerySchemaproperly defines the expected query parameters for the eligible payouts endpoint, including the optionalselectedPayoutId.
32-33: LGTM!The schema usage and conditional filtering by
selectedPayoutIdare correctly implemented, enabling the endpoint to fetch a specific payout when needed.Also applies to: 46-46
apps/web/app/(ee)/api/cron/payouts/process/process-payouts.ts (1)
36-36: LGTM!The conditional filtering logic is well-structured and correctly handles the three cases:
- When
selectedPayoutIdis provided, filter by that specific ID- When
excludedPayoutIdsis provided, exclude those IDs- Otherwise, apply no ID-based filter
This approach enables both single-payout processing and batch processing with exclusions.
Also applies to: 54-54, 63-67
apps/web/app/(ee)/api/cron/payouts/process/split-payouts.ts (1)
13-13: LGTM!The conditional filtering logic mirrors the implementation in
process-payouts.ts, ensuring consistency across the payout processing flow. The three-case handling is appropriate for this context as well.Also applies to: 18-18, 23-27
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-table.tsx (1)
71-71: Verify all required query parameters are included
Manual check needed: confirm pagination (page,pageSize), filtering (search,invoiceId), etc., are forwarded alongsidepartnerId,status,sortBy, andsortOrderin thegetQueryString({ include: […] })call.apps/web/ui/partners/confirm-payouts-sheet.tsx (6)
61-61: LGTM! Component renamed for clarity.The rename from
PayoutInvoiceSheetContenttoConfirmPayoutsSheetContentbetter reflects the component's purpose.
224-251: LGTM! Correctly uses filtered payouts.The amount calculation now uses
finalEligiblePayoutsinstead ofeligiblePayouts, ensuring that excluded payouts are not included in the total. The dependency array at line 251 is properly updated.
678-706: LGTM! Proper export rename and URL cleanup.The component export is renamed to
ConfirmPayoutsSheet, matching the internal component rename. The cleanup logic at line 699 correctly removes all related query parameters (confirmPayouts,selectedPayoutId,excludedPayoutIds) when the sheet closes.
101-111: Handle empty string edge case forexcludedPayoutIds.When
searchParamsObj.excludedPayoutIdsis empty,split(",")yields[""]. Filter out falsy values:- const excludedPayoutIds = searchParamsObj.excludedPayoutIds?.split(",") || []; + const excludedPayoutIds = + searchParamsObj.excludedPayoutIds?.split(",").filter(Boolean) || [];Could not locate the eligible payouts API implementation; please confirm that when
selectedPayoutIdis provided, the API returns only that payout.
570-580: selectedPayoutId optionality is already handled by the schema
TheconfirmPayoutsSchemamarksselectedPayoutIdas.optional(), so passingundefinedis acceptable.
84-96: Ensure API handles missing or undefinedselectedPayoutId
Omit or defaultselectedPayoutIdbefore building the query string, and verify the/payouts/eligibleGET handler properly validates or filters out undefined parameters.
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
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/ui/partners/confirm-payouts-sheet.tsx (1)
88-99: Prevent undefinedselectedPayoutIdfrom becoming "undefined" string.When
selectedPayoutIdis undefined,URLSearchParamsconverts it to the string"undefined", which is then sent to the backend as?selectedPayoutId=undefined. This causes the backend to receive"undefined"as a string value instead of the parameter being absent.Apply this diff to conditionally include the parameter only when it has a value:
const { data: eligiblePayouts, error: eligiblePayoutsError, isLoading: eligiblePayoutsLoading, } = useSWR<PayoutResponse[]>( - `/api/programs/${defaultProgramId}/payouts/eligible?${new URLSearchParams({ + `/api/programs/${defaultProgramId}/payouts/eligible?${new URLSearchParams({ workspaceId, cutoffPeriod, - selectedPayoutId, + ...(selectedPayoutId && { selectedPayoutId }), } as Record<string, any>).toString()}`, fetcher, );
🧹 Nitpick comments (3)
apps/web/app/(ee)/api/programs/[programId]/payouts/eligible/route.ts (1)
44-73: Consider explicit error handling when selectedPayoutId doesn't match eligible criteria.When
selectedPayoutIdis provided but the payout doesn't exist or doesn't meet the eligibility criteria (status, amount, payoutsEnabled), the endpoint returns an empty array. This could be confusing to callers who expect a specific payout.Consider returning a 404 or a descriptive error message when:
- The
selectedPayoutIddoesn't exist, or- The payout exists but doesn't meet eligibility criteria
This would provide clearer feedback to the UI layer.
Example implementation:
let payouts = await prisma.payout.findMany({ where: { ...(selectedPayoutId && { id: selectedPayoutId }), programId, status: "pending", amount: { gte: minPayoutAmount, }, partner: { payoutsEnabledAt: { not: null, }, }, }, // ... rest of the query }); + // If a specific payout was requested but not found or ineligible, return error + if (selectedPayoutId && payouts.length === 0) { + return NextResponse.json( + { error: "Selected payout not found or not eligible for processing" }, + { status: 404 } + ); + } + if (cutoffPeriodValue) { // ... existing cutoff logic }apps/web/ui/partners/confirm-payouts-sheet.tsx (2)
101-111: Refine empty array/string handling in query parameters.When
excludedPayoutIdsis an empty string,split(",")returns[""](an array with one empty string) rather than an empty array. While this doesn't break functionality (since no payout ID will match an empty string), it's semantically incorrect and creates an unnecessary filter iteration.Apply this diff to handle empty strings explicitly:
- const excludedPayoutIds = searchParamsObj.excludedPayoutIds?.split(",") || []; + const excludedPayoutIds = searchParamsObj.excludedPayoutIds?.split(",").filter(Boolean) || [];This ensures that empty strings are filtered out, resulting in a clean empty array when no IDs are excluded.
446-471: Ensure consistent empty array handling when toggling exclusions.When the last excluded payout is included, the resulting empty array is joined to an empty string (
[].join(",")→""), which then causes the split issue mentioned earlier. For consistency, consider handling empty arrays explicitly.Apply this diff to set the parameter only when the array has elements:
<Button variant="secondary" text={ excludedPayoutIds.includes(row.original.id) ? "Include" : "Exclude" } className="h-6 w-fit px-2" onClick={() => - // Toggle excluded - queryParams({ + { + const nextExcluded = excludedPayoutIds.includes(row.original.id) + ? excludedPayoutIds.filter((id) => id !== row.original.id) + : [...excludedPayoutIds, row.original.id]; + + queryParams({ set: { - excludedPayoutIds: excludedPayoutIds.includes( - row.original.id, - ) - ? excludedPayoutIds.filter( - (id) => id !== row.original.id, - ) - : [...excludedPayoutIds, row.original.id], + ...(nextExcluded.length > 0 && { + excludedPayoutIds: nextExcluded.join(","), + }), }, + ...(nextExcluded.length === 0 && { del: ["excludedPayoutIds"] }), replace: true, - }) + }); + } } />
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
apps/web/app/(ee)/api/cron/payouts/process/process-payouts.ts(3 hunks)apps/web/app/(ee)/api/cron/payouts/process/route.ts(4 hunks)apps/web/app/(ee)/api/cron/payouts/process/split-payouts.ts(1 hunks)apps/web/app/(ee)/api/programs/[programId]/payouts/eligible/route.ts(3 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-details-sheet.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-stats.tsx(2 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-table.tsx(1 hunks)apps/web/lib/actions/partners/confirm-payouts.ts(3 hunks)apps/web/ui/partners/confirm-payouts-sheet.tsx(9 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-stats.tsx (1)
apps/web/ui/partners/confirm-payouts-sheet.tsx (1)
ConfirmPayoutsSheet(678-706)
⏰ 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). (1)
- GitHub Check: build
🔇 Additional comments (11)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-stats.tsx (1)
6-6: LGTM!The component replacement from
PayoutInvoiceSheettoConfirmPayoutsSheetis straightforward and aligns with the broader refactoring to support selected payout confirmation.Also applies to: 71-71
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-details-sheet.tsx (1)
252-265: LGTM!The button text change from "Confirm all pending payouts" to "Confirm payout" accurately reflects the new single-payout confirmation behavior. The addition of
selectedPayoutIdto the query parameters correctly enables the targeted payout flow.apps/web/app/(ee)/api/cron/payouts/process/route.ts (1)
19-19: LGTM!The
selectedPayoutIdparameter is properly validated through the schema, extracted from the request body, and correctly propagated to bothsplitPayoutsandprocessPayoutsfunctions. This enables the targeted payout processing flow.Also applies to: 38-40, 58-59, 70-71
apps/web/app/(ee)/api/cron/payouts/process/process-payouts.ts (1)
61-104: LGTM! Filtering logic correctly implements selectedPayoutId precedence.The conditional filtering logic is sound:
- When
selectedPayoutIdis provided, filter to that specific payout- Otherwise, exclude
excludedPayoutIdsif provided- Otherwise, no additional id-based filtering
This precedence behavior (selectedPayoutId overrides excludedPayoutIds) is consistent with
split-payouts.tsand aligns with the single-payout confirmation flow introduced in this PR.apps/web/app/(ee)/api/cron/payouts/process/split-payouts.ts (1)
21-46: LGTM! Filtering logic is consistent with process-payouts.ts.The conditional filtering implementation matches the pattern in
process-payouts.ts, ensuring consistent behavior across the payout processing pipeline. The parameter is properly threaded through the function signature and type definition.apps/web/lib/actions/partners/confirm-payouts.ts (1)
21-22: Enforce mutual exclusivity in confirmPayoutsSchema
Add a.refine()to prevent providing bothselectedPayoutIdandexcludedPayoutIds, and verify downstream logic correctly prioritisesselectedPayoutIdwhen both are set.apps/web/ui/partners/confirm-payouts-sheet.tsx (5)
61-61: LGTM! Component rename is consistent.The component rename from
PayoutInvoiceSheettoConfirmPayoutsSheetaccurately reflects the new functionality and is consistently applied throughout the file.Also applies to: 678-678, 703-703
224-251: LGTM! Calculations correctly use filtered payouts.The amount calculation properly uses
finalEligiblePayouts, which respects bothselectedPayoutIdfiltering andexcludedPayoutIdsexclusions. The memoization dependencies are accurate.
697-701: LGTM! Proper cleanup of query parameters on close.The sheet correctly clears all related query parameters (
confirmPayouts,selectedPayoutId,excludedPayoutIds) when closing, maintaining clean URL state.
84-111: LGTM! URL-based state management improves UX.The migration from local state (
useState) to URL query parameters forexcludedPayoutIdsis a solid architectural choice. This enables shareable URLs, proper browser history integration, and state persistence across refreshes.
570-580: No changes needed: splitPayouts gives selectedPayoutId precedence over excludedPayoutIds
The backend’s splitPayouts uses the same ternary logic as the UI—when selectedPayoutId is provided, excludedPayoutIds is ignored.
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-table.tsx
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.
Actionable comments posted: 1
🧹 Nitpick comments (2)
apps/web/ui/partners/confirm-payouts-sheet.tsx (2)
103-111: Clarify comment and consider UI implications.The comment "return the payout directly" is misleading—it returns
eligiblePayouts, which should contain only the selected payout based on the API call. Consider clarifying this comment.Additionally, when
selectedPayoutIdis present, the exclude/include toggle buttons in the table remain visible but have no effect onfinalEligiblePayouts. This could confuse users.Consider one of the following:
- Update the comment to clarify the expected API behavior:
- // if there's a selected payout id, return the payout directly + // if there's a selected payout id, return eligiblePayouts (API should return only the selected payout) if (selectedPayoutId) return eligiblePayouts;
- Optionally hide or disable the exclude/include buttons when
selectedPayoutIdis present to avoid user confusion.
349-353: Consider showing included partners count.The "Partners" field displays
eligiblePayouts?.length, which includes both included and excluded payouts. SincefinalEligiblePayoutsdetermines the actual payouts in the invoice, consider showing the count of included partners instead.Update to use
finalEligiblePayouts:{ key: "Partners", - value: eligiblePayouts?.length ?? ( + value: finalEligiblePayouts?.length ?? ( <div className="h-4 w-24 animate-pulse rounded-md bg-neutral-200" /> ), },Alternatively, show both counts (e.g., "3/5 partners") if displaying excluded payouts in the UI is intentional.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
apps/web/app/(ee)/api/cron/payouts/process/process-payouts.ts(3 hunks)apps/web/app/(ee)/api/cron/payouts/process/split-payouts.ts(1 hunks)apps/web/ui/partners/confirm-payouts-sheet.tsx(9 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/web/app/(ee)/api/cron/payouts/process/split-payouts.ts
- apps/web/app/(ee)/api/cron/payouts/process/process-payouts.ts
⏰ 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). (1)
- GitHub Check: build
🔇 Additional comments (1)
apps/web/ui/partners/confirm-payouts-sheet.tsx (1)
457-469: Verify array-to-string conversion and handle empty array.This logic updates
excludedPayoutIdsas an array, which must be converted to a comma-separated string for the URL parameter. Additionally, when the last excluded payout is re-included, the resulting empty array should delete the query parameter rather than setting it to an empty string.Verify that
queryParamscorrectly converts arrays to comma-separated strings. Consider explicitly handling the conversion and deletion:- queryParams({ - set: { - excludedPayoutIds: excludedPayoutIds.includes( - row.original.id, - ) - ? excludedPayoutIds.filter( - (id) => id !== row.original.id, - ) - : [...excludedPayoutIds, row.original.id], - }, - replace: true, - }) + { + const newExcludedIds = excludedPayoutIds.includes( + row.original.id, + ) + ? excludedPayoutIds.filter( + (id) => id !== row.original.id, + ) + : [...excludedPayoutIds, row.original.id]; + + queryParams( + newExcludedIds.length > 0 + ? { + set: { + excludedPayoutIds: newExcludedIds.join(","), + }, + replace: true, + } + : { + del: ["excludedPayoutIds"], + replace: 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.
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)
apps/web/app/(ee)/api/cron/payouts/process/split-payouts.ts (1)
21-46: Handle missing selected payout and clarify filtering logic
- When
selectedPayoutIdis provided but no eligible payout is found,splitPayoutsreturns early with no indication (split-payouts.ts atif (payouts.length === 0)). Inroute.tsthe response is still “Payouts confirmed…”, so callers can’t distinguish a missing or ineligible selection. Either throw or return an explicit error when a selected payout isn’t found, or update the route handler to detect and handle the empty case.- Refactor the nested ternary in the
whereclause for readability (e.g., extract into a variable or add a clarifying comment).
🧹 Nitpick comments (3)
apps/web/app/(ee)/api/cron/payouts/process/split-payouts.ts (1)
10-20: Consider adding JSDoc to document the new parameter.The addition of
selectedPayoutIdis clean and backward-compatible. However, documenting its purpose and interaction withexcludedPayoutIds(specifically thatselectedPayoutIdtakes precedence) would improve clarity for future maintainers.Example JSDoc:
+/** + * Splits payouts based on commission creation dates relative to cutoff period. + * @param selectedPayoutId - If provided, processes only this specific payout (ignores excludedPayoutIds) + * @param excludedPayoutIds - If provided (and selectedPayoutId is not), excludes these payouts from processing + */ export async function splitPayouts({apps/web/ui/partners/confirm-payouts-sheet.tsx (2)
86-86: Consider validating selectedPayoutId.The
selectedPayoutIdis extracted from search params without validation. If an invalid or non-existent payout ID is provided via URL manipulation, the UI might display incorrect data or empty results without clear feedback.Consider adding validation:
const selectedPayoutId = searchParamsObj.selectedPayoutId || undefined; // After eligiblePayouts loads, validate the ID useEffect(() => { if (selectedPayoutId && eligiblePayouts && !eligiblePayouts.find(p => p.id === selectedPayoutId)) { toast.error("Selected payout not found"); queryParams({ del: ["selectedPayoutId"] }); } }, [selectedPayoutId, eligiblePayouts]);
103-111: Update comment for singular/plural consistencyReplace the existing comment with one that reflects the API-filtered array:
- // if there's a selected payout id, return the payout directly + // when `selectedPayoutId` is set, return the API-filtered payouts (usually a single item)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/web/app/(ee)/api/cron/payouts/process/process-payouts.ts(3 hunks)apps/web/app/(ee)/api/cron/payouts/process/split-payouts.ts(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-table.tsx(1 hunks)apps/web/ui/partners/confirm-payouts-sheet.tsx(9 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-table.tsx
- apps/web/app/(ee)/api/cron/payouts/process/process-payouts.ts
⏰ 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). (1)
- GitHub Check: build
🔇 Additional comments (4)
apps/web/ui/partners/confirm-payouts-sheet.tsx (4)
84-86: LGTM!Router-driven state for
selectedPayoutIdis correctly extracted from search params. The|| undefinedfallback appropriately handles both missing and empty string cases.
96-96: LGTM!Conditional inclusion of
selectedPayoutIdin the API query is correctly implemented.
575-575: LGTM!The
selectedPayoutIdis correctly passed to theconfirmPayoutsActionpayload.
678-706: LGTM!The component export and query param cleanup logic are correct. Clearing
confirmPayouts,selectedPayoutId, andexcludedPayoutIdson sheet close ensures proper state reset.
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
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: 0
🧹 Nitpick comments (2)
apps/web/ui/partners/confirm-payouts-sheet.tsx (2)
104-112: Consider adding a clarifying comment.The conditional logic is correct, but a brief comment would help explain why
selectedPayoutIdreturns the payouts directly (backend-filtered) versus client-side exclusion filtering.Example:
const finalEligiblePayouts = useMemo(() => { + // When selectedPayoutId is set, backend returns only that payout; + // otherwise, apply client-side exclusion filtering if (selectedPayoutId) return eligiblePayouts; - - // else, we need to filter out the excluded payout ids (if specified) return eligiblePayouts?.filter( (payout) => !excludedPayoutIds.includes(payout.id), ); }, [eligiblePayouts, selectedPayoutId, excludedPayoutIds]);
461-467: Consider defensive filtering in the toggle operation.While line 102 filters empty strings on read, adding a final
.filter(Boolean)or.filter(id => id !== "")when building the new array would provide defense-in-depth against edge cases.Apply this diff for additional safety:
queryParams({ set: { - excludedPayoutIds: excludedPayoutIds.includes( + excludedPayoutIds: (excludedPayoutIds.includes( row.original.id, ) ? excludedPayoutIds.filter( (id) => id !== row.original.id, ) - : [...excludedPayoutIds, row.original.id], + : [...excludedPayoutIds, row.original.id] + ).filter(id => id !== ""), }, replace: true, })
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/ui/partners/confirm-payouts-sheet.tsx(9 hunks)
⏰ 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). (1)
- GitHub Check: build
🔇 Additional comments (3)
apps/web/ui/partners/confirm-payouts-sheet.tsx (3)
101-102: LGTM! Empty string handling addressed.The
.filter(Boolean)correctly removes empty strings from the split result, addressing the edge case where an empty query parameter would produce[""].
447-474: LGTM! Conditional rendering addresses past review concern.The toggle button is now only rendered when
!selectedPayoutId, which effectively prevents toggling exclusions for a single pre-selected payout. This addresses the past review comment about disabling the button whenselectedPayoutIdis present.
702-702: LGTM! Proper query param cleanup.Deleting all relevant query parameters (
confirmPayouts,selectedPayoutId,excludedPayoutIds) on sheet close ensures clean state management.
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: 0
♻️ Duplicate comments (1)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-table.tsx (1)
68-73: Excluding the new payout selection parameters is correct.The updated
excludearray properly preventsselectedPayoutIdandexcludedPayoutIdsfrom being sent to the general payouts list endpoint, since these are UI state parameters specific to the confirmation flow.However, the past review comment about potentially missing query parameters (
eligibility,invoiceId,page,pageSize) in the URL generation strategy remains valid and should be addressed separately.
🧹 Nitpick comments (2)
apps/web/app/(ee)/api/programs/[programId]/payouts/eligible/route.ts (1)
44-57: Consider addinginvoiceId: nullfilter for consistency.When
selectedPayoutIdis provided, the query filters byidbut doesn't explicitly checkinvoiceId: null. While thestatus: "pending"filter on line 48 likely prevents payouts with invoices from being returned (since their status would have changed), it would be safer and more consistent withprocess-payouts.ts(line 70) to explicitly includeinvoiceId: nullin the where clause.Apply this diff to add the explicit filter:
let payouts = await prisma.payout.findMany({ where: { ...(selectedPayoutId && { id: selectedPayoutId }), programId, status: "pending", + invoiceId: null, amount: { gte: minPayoutAmount, },apps/web/ui/partners/confirm-payouts-sheet.tsx (1)
461-467: Consider defensive filtering of empty strings.While the root cause has been addressed at line 102, adding a defensive filter here would guard against edge cases where query parameters might be manually corrupted:
queryParams({ set: { - excludedPayoutIds: excludedPayoutIds.includes( + excludedPayoutIds: (excludedPayoutIds.includes( row.original.id, ) ? excludedPayoutIds.filter( (id) => id !== row.original.id, ) - : [...excludedPayoutIds, row.original.id], + : [...excludedPayoutIds, row.original.id]) + .filter(Boolean), }, replace: true, })
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
apps/web/app/(ee)/api/cron/payouts/process/process-payouts.ts(3 hunks)apps/web/app/(ee)/api/cron/payouts/process/route.ts(4 hunks)apps/web/app/(ee)/api/cron/payouts/process/split-payouts.ts(1 hunks)apps/web/app/(ee)/api/programs/[programId]/payouts/eligible/route.ts(3 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-details-sheet.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-stats.tsx(2 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-table.tsx(1 hunks)apps/web/lib/actions/partners/confirm-payouts.ts(3 hunks)apps/web/ui/partners/confirm-payouts-sheet.tsx(9 hunks)
⏰ 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). (1)
- GitHub Check: build
🔇 Additional comments (16)
apps/web/app/(ee)/api/cron/payouts/process/split-payouts.ts (1)
10-27: LGTM! Filtering logic is sound.The conditional filtering correctly prioritizes
selectedPayoutIdoverexcludedPayoutIds. When targeting a specific payout, exclusions are irrelevant, which makes sense. The implementation is clean and the logic matches the corresponding changes inprocess-payouts.ts.apps/web/app/(ee)/api/cron/payouts/process/process-payouts.ts (1)
29-67: LGTM! Consistent filtering implementation.The conditional filtering logic matches
split-payouts.tsexactly, ensuring consistent behavior when processing payouts with a selected payout ID.apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-stats.tsx (2)
6-6: LGTM! Component import updated.The import change from
PayoutInvoiceSheettoConfirmPayoutsSheetaligns with the renamed component that now handles the selected payout confirmation flow.
71-71: LGTM! Component usage updated.The component replacement is consistent with the import change and the broader refactor.
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-details-sheet.tsx (1)
252-265: LGTM! Button text and flow updated for single-payout confirmation.The button label change from "Confirm all pending payouts" to "Confirm payout" accurately reflects that clicking it now confirms a single, specific payout. Setting
selectedPayoutIdin the query params correctly passes the selected payout to theConfirmPayoutsSheet.apps/web/lib/actions/partners/confirm-payouts.ts (2)
17-27: LGTM! Schema extended for single-payout confirmation.Adding the optional
selectedPayoutIdfield to the schema is straightforward and consistent with the broader PR changes.
34-43: LGTM! Parameter properly destructured and propagated.The
selectedPayoutIdis correctly extracted from the parsed input and included in the Qstash payload sent to the payout processing cron job.apps/web/app/(ee)/api/cron/payouts/process/route.ts (2)
13-21: LGTM! Schema updated consistently.The schema change matches the corresponding update in
confirm-payouts.ts, ensuring the cron job can accept and process the selected payout ID.
32-72: LGTM! Parameter threaded through the processing pipeline.The
selectedPayoutIdis correctly extracted from the request body and passed to bothsplitPayouts(when a cutoff period is present) andprocessPayouts, ensuring consistent filtering throughout the payout processing flow.apps/web/app/(ee)/api/programs/[programId]/payouts/eligible/route.ts (1)
13-16: LGTM! Schema updated for single-payout filtering.The schema rename to
eligiblePayoutsQuerySchemaand the addition ofselectedPayoutIdare clear and consistent with the broader PR changes.apps/web/ui/partners/confirm-payouts-sheet.tsx (6)
84-86: LGTM! Router state management added correctly.The addition of
useRouterStuffand derivation ofselectedPayoutIdfrom search params is implemented correctly, enabling router-driven state for payout selection.
101-102: Empty string handling fixed.The addition of
.filter(Boolean)correctly addresses the critical issue flagged in previous reviews where empty strings would result from splitting an empty query parameter.
104-112: LGTM! Filtering logic is correct.The
finalEligiblePayoutsmemo correctly handles both scenarios:
- When
selectedPayoutIdis set, returns the backend-filtered single payout directly- Otherwise, applies client-side filtering to exclude specified payouts
The dependency array is complete and the logic is sound.
441-474: LGTM! Conditional rendering handles single-payout mode correctly.The exclude/include toggle is appropriately hidden when
selectedPayoutIdis set, which makes sense since you're working with a pre-selected single payout. This addresses the concern raised in previous reviews and provides clearer UX than disabling the button.
578-578: LGTM! Payload includes selectedPayoutId correctly.The addition of
selectedPayoutIdto the confirmation payload properly passes the selected payout context to the backend action.
681-706: LGTM! Component renaming and cleanup handled correctly.The renaming from
PayoutInvoiceSheettoConfirmPayoutsSheetis consistent, and the cleanup properly removes all related query parameters (confirmPayouts,selectedPayoutId,excludedPayoutIds) when the sheet closes.
Summary by CodeRabbit
New Features
Style