-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Customers pages updates #3235
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: main
Are you sure you want to change the base?
Customers pages updates #3235
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughAdds optional programId query handling to customer APIs and schema, remaps many customer links/pages to program-scoped routes, introduces modular customer layouts/pages (sales, earnings) for program and non-program contexts, adds PageNavTabs and related UI, updates redirects to point detail routes to their sales subpage, and applies multiple UI/styling tweaks. Changes
Estimated code review effortπ― 4 (Complex) | β±οΈ ~50 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 |
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: 10
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/customers/customer-partner-earnings-table.tsx (1)
63-69: Minor inconsistency in border-radius styling.The loading skeleton (line 63) no longer has
rounded-lg, but the "no earnings" empty state (line 65) still usesrounded-lg. Consider addingrounded-lgback to the loading skeleton for visual consistency between states.π Apply this diff to fix the inconsistency:
- <div className="flex h-32 w-full animate-pulse border border-transparent bg-neutral-100" /> + <div className="flex h-32 w-full animate-pulse rounded-lg border border-transparent bg-neutral-100" />
π§Ή Nitpick comments (6)
apps/web/ui/customers/customer-stats.tsx (1)
49-72: LGTM with optional cleanup.The component handles loading/error states well. Minor note:
hrefandtargetare passed even when rendering adiv(lines 54-55), which is harmless but slightly unnecessary.π Optional: Conditionally spread link-specific props
<As key={label} - href={href ?? "#"} - target="_blank" + {...(href && { href, target: "_blank" })} className={cn(apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/sales/page-client.tsx (1)
34-67: Add displayName for better debugging.The memoized
SalesTablecomponent lacks adisplayName, which will show as "Anonymous" in React DevTools, making debugging harder.π Apply this diff to add displayName:
const SalesTable = memo(({ customerId }: { customerId: string }) => { // ... component body }); + +SalesTable.displayName = "SalesTable";apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/earnings/page-client.tsx (1)
70-107: Add displayName to memoized component for better debugging.The
memo()wrapper strips the function name in React DevTools. Consider adding a displayName:const PartnerEarningsTable = memo(({ customerId }: { customerId: string }) => { // ... component body }); +PartnerEarningsTable.displayName = "PartnerEarningsTable";apps/web/ui/customers/customer-tabs.tsx (1)
1-7: Consider adding "use client" directive.This component uses React hooks (
useParams,useMemo) and theuseWorkspacehook. While it may work through client component promotion if only imported by client components, adding the"use client"directive explicitly would make the component's runtime requirements clear and prevent potential issues if imported from a Server Component in the future.Based on learnings, if the component is only imported by Client Components, this can be safely deferred.
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/earnings/page-client.tsx (1)
1-107: Significant code duplication with non-program earnings page.This file is identical to
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/earnings/page-client.tsx. Consider extracting the shared logic into a reusable component or hook to avoid maintaining duplicate code.For example, create a shared
CustomerEarningsContentcomponent in@/ui/customers/that both page-clients can import:// apps/web/ui/customers/customer-earnings-content.tsx export function CustomerEarningsContent({ customerId, slug }: Props) { // Shared earnings logic and UI }Then both page-clients would simply wrap this component with their respective context.
The same issues noted in the non-program version also apply here:
- Guard
slugbefore redirect (line 24)- Guard
workspaceIdin SWR key (line 78)- Add
displayNameto memoized component (line 107)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/sales/page-client.tsx (1)
34-67: Consider addingdisplayNamefor the memoized component.The memoized
SalesTablecomponent lacks adisplayName, which can make debugging in React DevTools harder.π Suggested fix
const SalesTable = memo(({ customerId }: { customerId: string }) => { // ... component body }); + +SalesTable.displayName = "SalesTable";
π Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
π Files selected for processing (34)
apps/web/app/(ee)/api/customers/count/route.ts(2 hunks)apps/web/app/(ee)/api/customers/route.ts(2 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/earnings/page-client.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/earnings/page.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/layout.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/page-client.tsx(0 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/page.tsx(0 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/sales/page-client.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/sales/page.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/commissions/commission-table.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/earnings/page-client.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/earnings/page.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/layout.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/sales/page-client.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/sales/page.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/page-client.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/page.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/partner-nav.tsx(3 hunks)apps/web/lib/middleware/utils/app-redirect.ts(1 hunks)apps/web/lib/zod/schemas/customers.ts(1 hunks)apps/web/ui/analytics/use-analytics-filters.tsx(1 hunks)apps/web/ui/customers/customer-activity-list.tsx(1 hunks)apps/web/ui/customers/customer-details-column.tsx(4 hunks)apps/web/ui/customers/customer-partner-earnings-table.tsx(5 hunks)apps/web/ui/customers/customer-sales-table.tsx(4 hunks)apps/web/ui/customers/customer-stats.tsx(1 hunks)apps/web/ui/customers/customer-table/customer-table.tsx(7 hunks)apps/web/ui/customers/customer-tabs.tsx(1 hunks)apps/web/ui/layout/page-nav-tabs.tsx(1 hunks)apps/web/ui/layout/sidebar/app-sidebar-nav.tsx(1 hunks)apps/web/ui/partners/fraud-risks/commissions-on-hold-table.tsx(1 hunks)apps/web/ui/partners/fraud-risks/fraud-events-tables/fraud-matching-customer-email-table.tsx(1 hunks)apps/web/ui/partners/fraud-risks/fraud-events-tables/fraud-paid-traffic-detected-table.tsx(1 hunks)packages/ui/src/timestamp-tooltip.tsx(1 hunks)
π€ Files with no reviewable changes (2)
- apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/page.tsx
- apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/page-client.tsx
π§° Additional context used
π§ Learnings (10)
π Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 3136
File: apps/web/ui/layout/sidebar/app-sidebar-nav.tsx:377-379
Timestamp: 2025-11-19T17:26:51.932Z
Learning: In the dub repository, TWilson023 prefers an incremental approach to route updates: fixing the most user-visible issues first (like sidebar navigation) while deferring less visible internal references to follow-up PRs, especially when redirects provide backward compatibility.
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
π Learning: 2025-10-15T01:05:43.266Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/earnings/page.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/sales/page.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/sales/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/layout.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/page.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/earnings/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/sales/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/earnings/page.tsxapps/web/ui/layout/page-nav-tabs.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/layout.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/sales/page.tsxapps/web/ui/customers/customer-tabs.tsxapps/web/ui/customers/customer-details-column.tsxapps/web/ui/layout/sidebar/app-sidebar-nav.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/earnings/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/partner-nav.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/commissions/commission-table.tsx
π Learning: 2025-12-08T09:44:28.429Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3200
File: apps/web/lib/api/fraud/detect-duplicate-payout-method-fraud.ts:55-73
Timestamp: 2025-12-08T09:44:28.429Z
Learning: In apps/web/lib/api/fraud/detect-duplicate-payout-method-fraud.ts, the fraud event creation logic intentionally generates self-referential fraud events (where partnerId equals duplicatePartnerId) for partners with duplicate payout methods. This is by design to create raw events for all partners involved in a duplicate payout method scenario, regardless of whether they reference themselves.
Applied to files:
apps/web/ui/partners/fraud-risks/fraud-events-tables/fraud-paid-traffic-detected-table.tsxapps/web/ui/partners/fraud-risks/fraud-events-tables/fraud-matching-customer-email-table.tsx
π Learning: 2025-09-18T16:33:17.719Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2858
File: apps/web/ui/partners/partner-application-tabs.tsx:1-1
Timestamp: 2025-09-18T16:33:17.719Z
Learning: When a React component in Next.js App Router uses non-serializable props (like setState functions), adding "use client" directive can cause serialization warnings. If the component is only imported by Client Components, it's better to omit the "use client" directive to avoid these warnings while still getting client-side execution through promotion.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/sales/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/earnings/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/sales/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/earnings/page-client.tsx
π Learning: 2025-08-25T21:03:24.285Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/bounty-card.tsx:1-1
Timestamp: 2025-08-25T21:03:24.285Z
Learning: In Next.js App Router, Server Components that use hooks can work without "use client" directive if they are only imported by Client Components, as they get "promoted" to run on the client side within the Client Component boundary.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/sales/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/earnings/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/sales/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/earnings/page-client.tsx
π Learning: 2025-11-24T09:10:12.536Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/lib/api/fraud/fraud-rules-registry.ts:17-25
Timestamp: 2025-11-24T09:10:12.536Z
Learning: In apps/web/lib/api/fraud/fraud-rules-registry.ts, the fraud rules `partnerCrossProgramBan` and `partnerDuplicatePayoutMethod` intentionally have stub implementations that return `{ triggered: false }` because they are partner-scoped rules handled separately during partner application/onboarding flows (e.g., in detect-record-fraud-application.ts), rather than being evaluated per conversion event like other rules in the registry. The stubs exist only to satisfy the `Record<FraudRuleType, ...>` type constraint.
Applied to files:
apps/web/lib/middleware/utils/app-redirect.ts
π Learning: 2025-07-30T15:29:54.131Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2673
File: apps/web/ui/partners/rewards/rewards-logic.tsx:268-275
Timestamp: 2025-07-30T15:29:54.131Z
Learning: In apps/web/ui/partners/rewards/rewards-logic.tsx, when setting the entity field in a reward condition, dependent fields (attribute, operator, value) should be reset rather than preserved because different entities (customer vs sale) have different available attributes. Maintaining existing fields when the entity changes would create invalid state combinations and confusing UX.
Applied to files:
apps/web/ui/customers/customer-details-column.tsx
π Learning: 2025-05-29T04:45:18.504Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:0-0
Timestamp: 2025-05-29T04:45:18.504Z
Learning: In the PartnerProgramSummary email template (packages/email/src/templates/partner-program-summary.tsx), the stat titles are hardcoded constants ("Clicks", "Leads", "Sales", "Earnings") that will always match the ICONS object keys after toLowerCase() conversion, so icon lookup failures are not possible.
Applied to files:
apps/web/ui/layout/sidebar/app-sidebar-nav.tsx
π Learning: 2025-08-25T20:58:39.467Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-submissions-table.tsx:537-542
Timestamp: 2025-08-25T20:58:39.467Z
Learning: The useTable hook includes an isClickOnInteractiveChild check in the onRowClick handler that prevents row click events from firing when clicking on interactive child elements like buttons, making stopPropagation() calls unnecessary in those cases.
Applied to files:
apps/web/ui/customers/customer-table/customer-table.tsxpackages/ui/src/timestamp-tooltip.tsx
π Learning: 2025-06-16T19:21:23.506Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2519
File: apps/web/ui/analytics/utils.ts:35-37
Timestamp: 2025-06-16T19:21:23.506Z
Learning: In the `useAnalyticsFilterOption` function in `apps/web/ui/analytics/utils.ts`, the pattern `options?.context ?? useContext(AnalyticsContext)` is intentionally designed as a complete replacement strategy, not a merge. When `options.context` is provided, it should contain all required fields (`baseApiPath`, `queryString`, `selectedTab`, `requiresUpgrade`) and completely replace the React context, not be merged with it. This is used for dependency injection or testing scenarios.
Applied to files:
apps/web/ui/analytics/use-analytics-filters.tsx
𧬠Code graph analysis (13)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/earnings/page.tsx (1)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/earnings/page-client.tsx (1)
CustomerEarningsPageClient(15-68)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/sales/page.tsx (1)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/sales/page-client.tsx (1)
CustomerSalesPageClient(13-32)
apps/web/ui/customers/customer-sales-table.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
currencyFormatter(7-22)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/page-client.tsx (3)
apps/web/lib/swr/use-workspace.ts (1)
useWorkspace(7-48)apps/web/ui/customers/customer-table/customer-table.tsx (1)
CustomerTable(71-440)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/page-client.tsx (1)
CustomersPageClient(3-5)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/earnings/page-client.tsx (5)
apps/web/lib/swr/use-workspace.ts (1)
useWorkspace(7-48)apps/web/lib/types.ts (2)
CustomerEnriched(436-436)CommissionResponse(451-451)packages/utils/src/constants/misc.ts (1)
OG_AVATAR_URL(29-29)apps/web/lib/constants/misc.ts (1)
CUSTOMER_PAGE_EVENTS_LIMIT(2-2)apps/web/ui/customers/customer-partner-earnings-table.tsx (1)
CustomerPartnerEarningsTable(12-125)
apps/web/ui/customers/customer-stats.tsx (3)
apps/web/lib/types.ts (1)
CustomerEnriched(436-436)packages/utils/src/functions/currency-formatter.ts (1)
currencyFormatter(7-22)packages/email/src/react-email.d.ts (1)
Link(14-14)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/earnings/page.tsx (1)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/earnings/page-client.tsx (1)
CustomerEarningsPageClient(15-68)
apps/web/ui/layout/page-nav-tabs.tsx (1)
packages/ui/src/icons/index.tsx (1)
Icon(82-82)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/layout.tsx (5)
apps/web/lib/swr/use-workspace.ts (1)
useWorkspace(7-48)apps/web/lib/types.ts (2)
CustomerEnriched(436-436)CustomerActivityResponse(529-531)apps/web/ui/layout/page-content/index.tsx (1)
PageContent(10-39)apps/web/ui/customers/customer-tabs.tsx (1)
CustomerTabs(8-48)apps/web/ui/customers/customer-activity-list.tsx (1)
CustomerActivityList(110-154)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/sales/page.tsx (1)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/sales/page-client.tsx (1)
CustomerSalesPageClient(13-32)
apps/web/ui/customers/customer-tabs.tsx (3)
apps/web/lib/types.ts (1)
CustomerEnriched(436-436)apps/web/lib/swr/use-workspace.ts (1)
useWorkspace(7-48)apps/web/ui/layout/page-nav-tabs.tsx (1)
PageNavTabs(30-141)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/partner-nav.tsx (1)
apps/web/ui/layout/page-nav-tabs.tsx (1)
PageNavTabs(30-141)
apps/web/ui/customers/customer-partner-earnings-table.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
currencyFormatter(7-22)
β° 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 (38)
apps/web/ui/layout/sidebar/app-sidebar-nav.tsx (1)
290-294: LGTM!The new "Customers" navigation item is correctly placed in the program insights section and follows the established patterns. The
UserCheckicon is properly imported, and the route/${slug}/program/customersaligns with the PR's objective to introduce program-scoped customer pages.Based on learnings, this incremental approach to updating routes (prioritizing visible navigation first) is consistent with your preferred workflow.
packages/ui/src/timestamp-tooltip.tsx (1)
151-152: Good semantic HTML structure.The explicit
<tbody>wrapper properly structures the table according to HTML5 standards.apps/web/ui/customers/customer-activity-list.tsx (1)
120-122: LGTM!The addition of
rounded-lgimproves visual consistency with the loading state and other card-based UI components in the customer details flow.apps/web/lib/zod/schemas/customers.ts (1)
41-41: LGTM!The
programIdfield follows the established pattern for optional filter parameters and is correctly inherited bygetCustomersCountQuerySchemafor consistent filtering across endpoints.apps/web/app/(ee)/api/customers/count/route.ts (1)
36-40: Verify behavior for customers without links.The nested
link: { programId }filter will exclude customers withlinkId: null. If this is the intended behavior for program-scoped views (only showing customers acquired through program links), this looks correct.apps/web/ui/analytics/use-analytics-filters.tsx (1)
766-770: LGTM!The permalink update correctly aligns with the new program-scoped routing structure (
/{slug}/program/customers/...), maintaining consistency with the broader route restructuring in this PR.apps/web/ui/customers/customer-partner-earnings-table.tsx (2)
36-41: LGTM!Cell renderers are appropriately simplified to concise single-line arrow functions.
79-79: LGTM!Padding and layout updates improve visual consistency with the rest of the table UI.
Also applies to: 96-96, 107-119
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/sales/page-client.tsx (2)
22-22: Verify redirect behavior during render.Using
redirect()during the render phase is valid in Next.js App Router but throws an exception to interrupt rendering. Ensure this doesn't cause issues with any parent error boundaries or Suspense boundaries.
45-55: LGTM!Smart optimization to conditionally fetch total sales count only when the initial data reaches the limit, avoiding unnecessary API calls.
apps/web/ui/layout/page-nav-tabs.tsx (2)
72-104: LGTM!The tab rendering with animated selection indicator using
motionis well-implemented. Thepathname.endsWith()approach for selection detection is reasonable for the controlled tab IDs used in this context.
108-133: LGTM!QuickLinks section is cleanly implemented with external link indicators and proper target handling.
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/partner-nav.tsx (1)
5-5: LGTM! Clean refactoring to shared PageNavTabs component.The refactoring successfully consolidates the inline navigation logic into the reusable PageNavTabs component, reducing code duplication and improving maintainability. The quicklinks β quickLinks rename also improves naming consistency.
Also applies to: 15-15, 62-62, 81-86
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/sales/page.tsx (1)
1-5: LGTM! Follows the standard page/page-client separation pattern.This wrapper correctly delegates to the client component for data fetching and rendering, which is the expected Next.js pattern for this type of page structure.
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/sales/page.tsx (1)
1-5: LGTM! Consistent with the established page wrapper pattern.This follows the same clean separation pattern used in the non-program-scoped sales page, maintaining consistency across the routing structure.
apps/web/ui/customers/customer-sales-table.tsx (1)
61-61: LGTM! UI polish improves table spacing and formatting.The padding updates (p-2 β px-4 py-3) provide better vertical and horizontal spacing, and the simplified amount cell formatting using currencyFormatter directly is cleaner than the previous implementation.
Also applies to: 108-108, 125-125, 136-140
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/page.tsx (1)
1-20: LGTM! Well-structured page with helpful user guidance.The page composition is clean, using appropriate layout wrappers and providing clear context through the title and help link. The separation of concerns between the page wrapper and client component is well executed.
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/earnings/page.tsx (1)
1-5: LGTM! Follows the consistent page wrapper pattern.Clean implementation that maintains consistency with the other customer detail pages.
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/earnings/page.tsx (1)
1-5: LGTM! Consistent wrapper implementation.This completes the consistent page structure pattern across all customer detail pages (both general and program-scoped).
apps/web/app/(ee)/api/customers/route.ts (1)
48-48: [Rewritten review comment]
[Classification tag]apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/page-client.tsx (1)
6-14: LGTM!The component correctly wires up the program-scoped customer table by passing
defaultProgramIdfrom the workspace context and settingisProgramPageto enable program-specific behavior. The use of|| undefinedproperly handles falsy values for the query parameter.apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/layout.tsx (2)
40-106: Well-structured layout implementation.The two-column responsive grid with proper ordering, loading states, and activity section is cleanly organized. The conditional rendering for customer name skeleton and the activity list integration follow good UX patterns.
37-38: Consider handling the loading state before redirecting.The redirect fires immediately when
customerError.status === 404, but ifworkspaceSlugis still loading (undefined), this could redirect to/undefined/customers. Consider adding a guard:- if (customerError && customerError.status === 404) - redirect(`/${workspaceSlug}/customers`); + if (customerError && customerError.status === 404 && workspaceSlug) + redirect(`/${workspaceSlug}/customers`);β Skipped due to learnings
Learnt from: steven-tey Repo: dubinc/dub PR: 2958 File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457 Timestamp: 2025-10-15T01:05:43.266Z Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.Learnt from: TWilson023 Repo: dubinc/dub PR: 2736 File: apps/web/lib/swr/use-bounty.ts:11-16 Timestamp: 2025-08-26T15:05:55.081Z Learning: In the Dub codebase, workspace authentication and route structures prevent endless loading states when workspaceId or similar route parameters are missing, so gating SWR loading states on parameter availability is often unnecessary.apps/web/ui/customers/customer-tabs.tsx (1)
8-47: Clean tab navigation implementation.The conditional tab rendering based on customer's program association and the dynamic basePath for program vs non-program contexts are well-implemented. The
useMemodependencies correctly track the relevant customer properties.apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/earnings/page-client.tsx (1)
73-82: Guard SWR key against undefined workspaceId.The non-null assertion
workspaceId!will cause the API call to be made withworkspaceId=undefinedas a string if the workspace hasn't loaded yet. Consider guarding the SWR key:const { data: commissions, isLoading: isComissionsLoading } = useSWR< CommissionResponse[] >( - `/api/commissions?${new URLSearchParams({ + workspaceId && + `/api/commissions?${new URLSearchParams({ customerId, - workspaceId: workspaceId!, + workspaceId, pageSize: CUSTOMER_PAGE_EVENTS_LIMIT.toString(), })}`, fetcher, );β Skipped due to learnings
Learnt from: TWilson023 Repo: dubinc/dub PR: 2736 File: apps/web/lib/swr/use-bounty.ts:11-16 Timestamp: 2025-08-26T15:05:55.081Z Learning: In the Dub codebase, workspace authentication and route structures prevent endless loading states when workspaceId or similar route parameters are missing, so gating SWR loading states on parameter availability is often unnecessary.Learnt from: TWilson023 Repo: dubinc/dub PR: 2538 File: apps/web/ui/partners/overview/blocks/commissions-block.tsx:16-27 Timestamp: 2025-06-18T20:26:25.177Z Learning: In the Dub codebase, components that use workspace data (workspaceId, defaultProgramId) are wrapped in `WorkspaceAuth` which ensures these values are always available, making non-null assertions safe. This is acknowledged as a common pattern in their codebase, though not ideal.apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/sales/page-client.tsx (1)
13-32: LGTM! Clean component structure with proper data fetching.The component correctly uses
useCustomerhook with enriched fields and handles the loading/not-found states. The redirect pattern for missing customers after loading is appropriate.apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/layout.tsx (2)
20-38: LGTM! Data fetching and error handling look correct.The component properly chains data fetching (workspace β customer β activity) and handles the 404 case with a redirect. The conditional SWR key for activity ensures the request only fires when customer data is available.
40-107: Well-structured layout with proper loading states.The responsive grid layout with loading skeletons and proper composition of child components follows good patterns. The
isCustomerActivityLoadingprop correctly accounts for both customer loading and activity loading states.apps/web/ui/customers/customer-table/customer-table.tsx (1)
71-77: LGTM! Clean prop interface for the table component.The updated function signature properly types the query prop using the Zod schema inference and provides sensible defaults.
apps/web/ui/customers/customer-details-column.tsx (4)
42-65: Clean pattern for rendering basic fields with loading states.The
basicFieldsarray pattern with conditional inclusion based on customer data provides a clean way to handle the email/country display with proper loading skeletons.
198-212: LGTM! Proper program-aware path construction for analytics links.The
isProgramPageconditional correctly adjusts the analytics path to include/program/when viewing from the program context.
258-301: Well-structured referral section with partner support.The referral section cleanly handles three states: loading, with partner, and without partner. The conditional rendering and link construction are correct.
306-314: LGTM! Clean helper component for detail headings.The
DetailHeadingcomponent provides consistent styling for section headings throughout the details column.apps/web/ui/partners/fraud-risks/commissions-on-hold-table.tsx (1)
90-90: LGTM: Customer link updated to program scope.The update to the program-scoped customer path is consistent with the PR's routing restructure.
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/commissions/commission-table.tsx (1)
162-162: LGTM: Customer link correctly scoped to program context.Since this table is already in the program context (
/program/commissions/), the program-scoped customer path is the appropriate choice.apps/web/ui/partners/fraud-risks/fraud-events-tables/fraud-matching-customer-email-table.tsx (1)
75-75: LGTM: Fraud event customer link updated to program scope.The program-scoped path is appropriate for fraud detection features.
apps/web/ui/partners/fraud-risks/fraud-events-tables/fraud-paid-traffic-detected-table.tsx (2)
87-87: LGTM: Customer link updated to program scope.The program-scoped path aligns with the fraud detection feature context.
87-87: Add redirect rules for old customer paths and deprecate or remove old routes.The path update is correct, but migration is incomplete. Both old (
/${slug}/customers/) and new (/${slug}/program/customers/) routes are currently active with fully functional pages. Configure redirects from old customer paths to new program paths, or remove the old route files. Without redirects, bookmarks and incoming links to the old path will break.
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/earnings/page-client.tsx
Show resolved
Hide resolved
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/layout.tsx
Outdated
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: 2
π Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
π Files selected for processing (3)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/layout.tsx(1 hunks)apps/web/lib/middleware/utils/app-redirect.ts(1 hunks)apps/web/ui/customers/customer-table/customer-table.tsx(7 hunks)
π§° Additional context used
π§ Learnings (4)
π Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 3136
File: apps/web/ui/layout/sidebar/app-sidebar-nav.tsx:377-379
Timestamp: 2025-11-19T17:26:51.932Z
Learning: In the dub repository, TWilson023 prefers an incremental approach to route updates: fixing the most user-visible issues first (like sidebar navigation) while deferring less visible internal references to follow-up PRs, especially when redirects provide backward compatibility.
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
π Learning: 2025-07-30T15:29:54.131Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2673
File: apps/web/ui/partners/rewards/rewards-logic.tsx:268-275
Timestamp: 2025-07-30T15:29:54.131Z
Learning: In apps/web/ui/partners/rewards/rewards-logic.tsx, when setting the entity field in a reward condition, dependent fields (attribute, operator, value) should be reset rather than preserved because different entities (customer vs sale) have different available attributes. Maintaining existing fields when the entity changes would create invalid state combinations and confusing UX.
Applied to files:
apps/web/ui/customers/customer-table/customer-table.tsx
π Learning: 2025-10-15T01:05:43.266Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.
Applied to files:
apps/web/ui/customers/customer-table/customer-table.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/layout.tsx
π Learning: 2025-11-24T09:10:12.536Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/lib/api/fraud/fraud-rules-registry.ts:17-25
Timestamp: 2025-11-24T09:10:12.536Z
Learning: In apps/web/lib/api/fraud/fraud-rules-registry.ts, the fraud rules `partnerCrossProgramBan` and `partnerDuplicatePayoutMethod` intentionally have stub implementations that return `{ triggered: false }` because they are partner-scoped rules handled separately during partner application/onboarding flows (e.g., in detect-record-fraud-application.ts), rather than being evaluated per conversion event like other rules in the registry. The stubs exist only to satisfy the `Record<FraudRuleType, ...>` type constraint.
Applied to files:
apps/web/lib/middleware/utils/app-redirect.ts
𧬠Code graph analysis (2)
apps/web/ui/customers/customer-table/customer-table.tsx (2)
apps/web/lib/zod/schemas/customers.ts (1)
getCustomersQuerySchema(9-60)apps/web/ui/partners/partner-row-item.tsx (1)
PartnerRowItem(129-178)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/layout.tsx (8)
apps/web/lib/swr/use-workspace.ts (1)
useWorkspace(7-48)apps/web/lib/types.ts (2)
CustomerEnriched(436-436)CustomerActivityResponse(529-531)apps/web/ui/layout/page-content/index.tsx (1)
PageContent(10-39)apps/web/ui/customers/customer-stats.tsx (1)
CustomerStats(7-78)apps/web/ui/customers/customer-details-column.tsx (1)
CustomerDetailsColumn(23-304)apps/web/ui/customers/customer-tabs.tsx (1)
CustomerTabs(8-48)packages/ui/src/button.tsx (1)
Button(158-158)apps/web/ui/customers/customer-activity-list.tsx (1)
CustomerActivityList(110-154)
β° 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 (8)
apps/web/lib/middleware/utils/app-redirect.ts (1)
84-98: LGTM! Previous issues resolved.The customer redirect rules are correctly implemented and follow the established pattern from the partner redirect above. The comments accurately describe the behavior (previous typo concerns about "partnerId" have been fixed), and the regex patterns appropriately validate that customer IDs start with "cus_" before redirecting to the sales subpage.
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/layout.tsx (1)
66-102: Layout structure and props look good.The
isProgramPageprop is now correctly passed to bothCustomerDetailsColumn(line 72) andCustomerTabs(line 77), ensuring proper program-page URL construction. Loading states are appropriately propagated to child components.apps/web/ui/customers/customer-table/customer-table.tsx (6)
7-9: LGTM!The new imports are necessary for the program-scoped functionality:
getCustomersQuerySchemafor typing the query prop,PartnerRowItemfor the partner column, andzfor type inference.
53-61: LGTM!Adding "partner" to the available columns while keeping it out of
defaultVisibleis appropriate since the partner column is only conditionally rendered on program pages.
71-77: LGTM!The props interface uses schema inference for type safety, and
isProgramPagedefaulting tofalsemaintains backward compatibility with existing usages.
138-150: LGTM!The conditional routing based on
isProgramPagecorrectly directs users to program-scoped or workspace-scoped customer detail pages.
282-283: LGTM!The dependency array now correctly includes both
isProgramPageandworkspaceSlug, addressing the stale closure concern from the previous review.
99-114: LGTM!The
queryprop is correctly propagated to bothuseCustomersCountand the SWR fetch, enabling proper filtering for program-scoped views.
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/layout.tsx
Outdated
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: 0
π§Ή Nitpick comments (1)
apps/web/ui/customers/customer-table/customer-table.tsx (1)
53-63: Consider default visibility for the partner column on program pages.The partner column is included in
customersColumns.allbut not indefaultVisible, meaning it will be hidden by default even whenisProgramPageis true. Users would need to manually enable it via the column visibility menu.If the partner column is a key feature of the program pages, consider conditionally adding it to the visible columns or documenting this UX choice.
π Optional: conditionally show partner column by default
One approach would be to update the column visibility after initialization:
const { columnVisibility, setColumnVisibility } = useColumnVisibility( "customers-table-columns", customersColumns, ); const { pagination, setPagination } = usePagination(); if (!canManageCustomers) columnVisibility.link = false; + if (isProgramPage && columnVisibility.partner === undefined) { + columnVisibility.partner = true; + }Alternatively, you could adjust the config based on page context before passing it to the hook.
π Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
π Files selected for processing (2)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/layout.tsx(1 hunks)apps/web/ui/customers/customer-table/customer-table.tsx(7 hunks)
π§ Files skipped from review as they are similar to previous changes (1)
- apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/layout.tsx
π§° Additional context used
π§ Learnings (4)
π Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 3136
File: apps/web/ui/layout/sidebar/app-sidebar-nav.tsx:377-379
Timestamp: 2025-11-19T17:26:51.932Z
Learning: In the dub repository, TWilson023 prefers an incremental approach to route updates: fixing the most user-visible issues first (like sidebar navigation) while deferring less visible internal references to follow-up PRs, especially when redirects provide backward compatibility.
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
π Learning: 2025-10-15T01:05:43.266Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.
Applied to files:
apps/web/ui/customers/customer-table/customer-table.tsx
π Learning: 2025-08-16T11:14:00.667Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2754
File: apps/web/lib/partnerstack/schemas.ts:47-52
Timestamp: 2025-08-16T11:14:00.667Z
Learning: The PartnerStack API always includes the `group` field in partner responses, so the schema should use `.nullable()` rather than `.nullish()` since the field is never omitted/undefined.
Applied to files:
apps/web/ui/customers/customer-table/customer-table.tsx
π Learning: 2025-11-12T22:23:10.414Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 3098
File: apps/web/lib/actions/partners/message-program.ts:49-58
Timestamp: 2025-11-12T22:23:10.414Z
Learning: In apps/web/lib/actions/partners/message-program.ts, when checking if a partner can continue messaging after messaging is disabled, the code intentionally requires `senderPartnerId: null` (program-initiated messages) to prevent partners from continuing to send junk messages. Only conversations started by the program should continue after messaging is disabled, as a spam prevention mechanism.
Applied to files:
apps/web/ui/customers/customer-table/customer-table.tsx
𧬠Code graph analysis (1)
apps/web/ui/customers/customer-table/customer-table.tsx (2)
apps/web/lib/zod/schemas/customers.ts (1)
getCustomersQuerySchema(9-60)apps/web/ui/partners/partner-row-item.tsx (1)
PartnerRowItem(129-178)
β° 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 (2)
apps/web/ui/customers/customer-table/customer-table.tsx (2)
158-161: All previous review feedback addressed! βGreat work addressing the prior review comments:
- The debug
console.logstatement has been removedworkspaceSlugis now included in theuseMemodependency array (line 285)- The partner column now has a proper null guard (lines 158-161)
Also applies to: 285-285
71-77: LGTM: Clean component API extension.The new props are well-typed using Zod schema inference, and the
isProgramPagedefault ensures backward compatibility for existing usages.
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 (4)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx (2)
56-63: Consider removing or completing the commented-out link code.There's commented-out code for what appears to be a back navigation link (
href,aria-label,title, and hover styles). Since this is a draft PR, please either:
- Remove the comments if the link functionality is intentionally omitted
- Restore the link using
<Link>if back navigation is desiredLeaving commented-out code in production can reduce clarity.
147-184: Consider adding displayName to the memoized component.The
EarningsTablecomponent usesmemobut lacks adisplayName. Adding one improves debugging in React DevTools.π Proposed fix
const EarningsTable = memo(({ customerId }: { customerId: string }) => { // ... component body }); + +EarningsTable.displayName = "EarningsTable";apps/web/ui/customers/customer-stats.tsx (1)
49-60: Consider conditional prop spreading for polymorphic component.When
Asis"div", thehrefandtargetprops are passed but have no effect. While harmless, conditionally spreading these props only whenAsisLinkwould be more semantically precise.π Proposed refactor
const As = href ? Link : "div"; return ( <As key={label} - href={href ?? "#"} - target="_blank" + {...(href ? { href, target: "_blank" } : {})} className={cn( "group relative flex flex-col bg-white p-3", href && "transition-colors duration-150 hover:bg-neutral-50", )} >apps/web/ui/customers/customer-details-column.tsx (1)
328-331: Heading level changed from h2 to h3.The
DetailHeadingcomponent now renders anh3instead ofh2. Ensure this change aligns with the document outline hierarchy in the contexts where this component is used.
π Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
π Files selected for processing (6)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx(2 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/layout.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/layout.tsx(1 hunks)apps/web/ui/customers/customer-details-column.tsx(4 hunks)apps/web/ui/customers/customer-stats.tsx(1 hunks)
π§ Files skipped from review as they are similar to previous changes (1)
- apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/customers/[customerId]/layout.tsx
π§° Additional context used
π§ Learnings (4)
π Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 3136
File: apps/web/ui/layout/sidebar/app-sidebar-nav.tsx:377-379
Timestamp: 2025-11-19T17:26:51.932Z
Learning: In the dub repository, TWilson023 prefers an incremental approach to route updates: fixing the most user-visible issues first (like sidebar navigation) while deferring less visible internal references to follow-up PRs, especially when redirects provide backward compatibility.
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
π Learning: 2025-10-15T01:05:43.266Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/layout.tsxapps/web/ui/customers/customer-details-column.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx
π Learning: 2025-05-29T04:45:18.504Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:0-0
Timestamp: 2025-05-29T04:45:18.504Z
Learning: In the PartnerProgramSummary email template (packages/email/src/templates/partner-program-summary.tsx), the stat titles are hardcoded constants ("Clicks", "Leads", "Sales", "Earnings") that will always match the ICONS object keys after toLowerCase() conversion, so icon lookup failures are not possible.
Applied to files:
apps/web/ui/customers/customer-stats.tsx
π Learning: 2025-07-30T15:29:54.131Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2673
File: apps/web/ui/partners/rewards/rewards-logic.tsx:268-275
Timestamp: 2025-07-30T15:29:54.131Z
Learning: In apps/web/ui/partners/rewards/rewards-logic.tsx, when setting the entity field in a reward condition, dependent fields (attribute, operator, value) should be reset rather than preserved because different entities (customer vs sale) have different available attributes. Maintaining existing fields when the entity changes would create invalid state combinations and confusing UX.
Applied to files:
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx
𧬠Code graph analysis (1)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page.tsx (1)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx (1)
ProgramCustomerPageClient(21-145)
β° 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 (6)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx (1)
78-142: Clean responsive layout structure.The two-column responsive layout using
@containerqueries is well-structured. The order swapping (@3xl/page:order-1and@3xl/page:order-2) ensures appropriate visual hierarchy across viewport sizes.apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page.tsx (1)
1-5: LGTM!Clean simplification. Moving the layout wrappers into
page-client.tsxkeeps this server component as a thin shell while consolidating rendering logic in the client component.apps/web/ui/customers/customer-details-column.tsx (2)
246-251: Inconsistent fallback behavior between analytics and UTM links.The UTM parameter links only render when
workspaceSlugis present (line 248), while the analytics links above (line 207) fall back to/programs/${programSlug}/whenworkspaceSlugis undefined. Is this intentional? Consider whether UTM links should also support the program-slug fallback for consistency.
300-313: LGTM! Clear fallback logic for referral links.The conditional href construction appropriately handles both workspace (direct link) and program (analytics query) contexts, with a sensible fallback to
undefinedwhen neither is available.apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/customers/[customerId]/layout.tsx (2)
37-38: Past review issues have been resolved.The 404 redirect now correctly targets the program-scoped customers route (line 38), and
isProgramPageis properly passed toCustomerTabs(line 78). These changes address the previous review feedback.Also applies to: 78-78
89-92: This link to the workspace events page is correct. Events are aggregated at the workspace level in the application, not scoped per-program. There is no program-level events page to navigate to, and the same link pattern is used consistently across both program and non-program customer layouts.Likely an incorrect or invalid review 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.
Actionable comments posted: 0
π§Ή Nitpick comments (1)
apps/web/lib/middleware/utils/app-redirect.ts (1)
84-96: Align customer redirect regexes with partner pattern usingcus_prefix validation.The partner redirect (line 80) correctly validates partner IDs with
(pn_[^\/]+)to exclude non-ID paths. The customer redirects use([^\/]+), which is overly permissive and inconsistent. Since customer IDs are generated with acus_prefix throughout the codebase (tolt, rewardful, partnerstack, firstpromoter imports), tightening the regex matches the actual ID format and prevents unintended redirects if future non-ID routes are added under/customers/:
- Line 85: Update
/^\/([^\/]+)\/customers\/([^\/]+)$/to/^\/([^\/]+)\/customers\/(cus_[^\/]+)$/- Lines 90-91: Update
/^\/([^\/]+)\/program\/customers\/([^\/]+)$/to/^\/([^\/]+)\/program\/customers\/(cus_[^\/]+)$/
π Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
π Files selected for processing (1)
apps/web/lib/middleware/utils/app-redirect.ts(1 hunks)
π§° Additional context used
π§ Learnings (1)
π Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 3136
File: apps/web/ui/layout/sidebar/app-sidebar-nav.tsx:377-379
Timestamp: 2025-11-19T17:26:51.932Z
Learning: In the dub repository, TWilson023 prefers an incremental approach to route updates: fixing the most user-visible issues first (like sidebar navigation) while deferring less visible internal references to follow-up PRs, especially when redirects provide backward compatibility.
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
β° 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
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/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx (2)
56-66: Clean up or complete the commented back-link implementation.The commented-out
href,aria-label,title(lines 57-59), and hover styles (line 62) suggest an incomplete back-link to the customers list. The currentdivappears interactive due to its styling but has no actual functionality.Consider either:
- Completing the implementation by converting this to a clickable link/button with proper navigation
- Removing the commented code if the back-link feature is deferred to a future PR
This will improve code clarity and prevent confusion about the element's intended purpose.
Option 1: Complete the back-link implementation
- <div className="flex items-center gap-1.5"> - <div - // href={`/programs/${programSlug}/customers`} - // aria-label="Back to customers" - // title="Back to customers" + <div className="flex items-center gap-1.5"> + <Link + href={`/programs/${programSlug}/customers`} + aria-label="Back to customers" + title="Back to customers" className={cn( - "bg-bg-subtle flex size-8 shrink-0 items-center justify-center rounded-lg", - // "hover:bg-bg-emphasis transition-[transform,background-color] duration-150 active:scale-95", + "bg-bg-subtle flex size-8 shrink-0 items-center justify-center rounded-lg", + "hover:bg-bg-emphasis transition-[transform,background-color] duration-150 active:scale-95", )} > <UserCheck className="size-4" /> - </div> + </Link>Option 2: Remove commented code if deferred
<div className="flex items-center gap-1.5"> <div - // href={`/programs/${programSlug}/customers`} - // aria-label="Back to customers" - // title="Back to customers" className={cn( "bg-bg-subtle flex size-8 shrink-0 items-center justify-center rounded-lg", - // "hover:bg-bg-emphasis transition-[transform,background-color] duration-150 active:scale-95", )} >
147-184: Consider adding a display name to the memoized component.The
EarningsTablecomponent is properly memoized with good data-fetching logic (conditional total count fetch,keepPreviousDatafor smooth UX). However, adding a display name would improve the debugging experience in React DevTools.Add display name
const EarningsTable = memo(({ customerId }: { customerId: string }) => { // ... component implementation }); + +EarningsTable.displayName = "EarningsTable";
π Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
π Files selected for processing (3)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx(2 hunks)apps/web/lib/swr/use-partner-customer.ts(1 hunks)apps/web/ui/customers/customer-details-column.tsx(4 hunks)
π§° Additional context used
π§ Learnings (9)
π Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 3136
File: apps/web/ui/layout/sidebar/app-sidebar-nav.tsx:377-379
Timestamp: 2025-11-19T17:26:51.932Z
Learning: In the dub repository, TWilson023 prefers an incremental approach to route updates: fixing the most user-visible issues first (like sidebar navigation) while deferring less visible internal references to follow-up PRs, especially when redirects provide backward compatibility.
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
π Learning: 2025-10-15T01:05:43.266Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.
Applied to files:
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsxapps/web/ui/customers/customer-details-column.tsx
π Learning: 2025-07-30T15:29:54.131Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2673
File: apps/web/ui/partners/rewards/rewards-logic.tsx:268-275
Timestamp: 2025-07-30T15:29:54.131Z
Learning: In apps/web/ui/partners/rewards/rewards-logic.tsx, when setting the entity field in a reward condition, dependent fields (attribute, operator, value) should be reset rather than preserved because different entities (customer vs sale) have different available attributes. Maintaining existing fields when the entity changes would create invalid state combinations and confusing UX.
Applied to files:
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx
π Learning: 2025-08-25T17:42:13.600Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2736
File: apps/web/lib/api/get-workspace-users.ts:76-83
Timestamp: 2025-08-25T17:42:13.600Z
Learning: Business rule confirmed: Each workspace has exactly one program. The code should always return workspace.programs[0] since there's only one program per workspace.
Applied to files:
apps/web/ui/customers/customer-details-column.tsx
π Learning: 2025-06-10T19:16:23.445Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2510
File: apps/web/lib/actions/partners/onboard-program.ts:16-19
Timestamp: 2025-06-10T19:16:23.445Z
Learning: Business rule: Each workspace may have at most one program; attempting to create more must be blocked in code.
Applied to files:
apps/web/ui/customers/customer-details-column.tsx
π Learning: 2025-08-26T15:05:55.081Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/lib/swr/use-bounty.ts:11-16
Timestamp: 2025-08-26T15:05:55.081Z
Learning: In the Dub codebase, workspace authentication and route structures prevent endless loading states when workspaceId or similar route parameters are missing, so gating SWR loading states on parameter availability is often unnecessary.
Applied to files:
apps/web/ui/customers/customer-details-column.tsx
π Learning: 2025-06-18T20:26:25.177Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2538
File: apps/web/ui/partners/overview/blocks/commissions-block.tsx:16-27
Timestamp: 2025-06-18T20:26:25.177Z
Learning: In the Dub codebase, components that use workspace data (workspaceId, defaultProgramId) are wrapped in `WorkspaceAuth` which ensures these values are always available, making non-null assertions safe. This is acknowledged as a common pattern in their codebase, though not ideal.
Applied to files:
apps/web/ui/customers/customer-details-column.tsx
π Learning: 2025-11-19T17:26:51.932Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 3136
File: apps/web/ui/layout/sidebar/app-sidebar-nav.tsx:377-379
Timestamp: 2025-11-19T17:26:51.932Z
Learning: In the dub repository, TWilson023 prefers an incremental approach to route updates: fixing the most user-visible issues first (like sidebar navigation) while deferring less visible internal references to follow-up PRs, especially when redirects provide backward compatibility.
Applied to files:
apps/web/ui/customers/customer-details-column.tsx
π Learning: 2025-06-06T07:59:03.120Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2177
File: apps/web/lib/api/links/bulk-create-links.ts:66-84
Timestamp: 2025-06-06T07:59:03.120Z
Learning: In apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined results from links.find() operations when building invalidLinks arrays, because existing links are fetched from the database based on the input links, so matches are expected to always exist.
Applied to files:
apps/web/ui/customers/customer-details-column.tsx
𧬠Code graph analysis (1)
apps/web/lib/swr/use-partner-customer.ts (1)
apps/web/lib/types.ts (1)
PartnerProfileCustomerProps(465-467)
β° 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 (9)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx (2)
10-14: LGTM! Clean import additions for layout refactoring.The new imports for
PageContent,PageWidthWrapper, icons, andformatDateare all appropriately used in the refactored component structure.
78-142: LGTM! Well-structured responsive layout refactoring.The two-column grid layout is well-implemented with:
- Proper responsive breakpoints and column sizing
- Correct order manipulation for different screen sizes
- All business logic preserved (reward eligibility, period warnings, loading states)
- Clean separation of CustomerDetailsColumn, Earnings, and Activity sections
The refactoring successfully maintains functionality while improving the page structure.
apps/web/ui/customers/customer-details-column.tsx (6)
27-42: LGTM: Props correctly support program/workspace routing contexts.The addition of
isProgramPageandworkspaceSlugprops properly enables the component to generate correct URLs for both workspace-scoped and partner-scoped contexts. The type change toCustomerEnrichedaligns with the enriched data structure.
44-67: LGTM: Elegant conditional field collection.The
basicFieldsarray construction intelligently handles different states:
- Email field included during loading (shows skeleton) or when present
- Email field excluded when customer exists without email
- Country field always present with sensible fallback
The pattern cleanly separates field definition from rendering logic.
204-208: LGTM: URL construction correctly implements business rules.The href logic properly handles both workspace and partner contexts. Based on the established architectural constraint that
isProgramPageis only true whenworkspaceSlugis defined, the URL patterns generated are:
- Workspace program:
/${workspaceSlug}/program/analytics?...- Workspace non-program:
/${workspaceSlug}/analytics?...- Partner-side:
/programs/${programSlug}/analytics?...This aligns with the route structure clarified in the previous review discussion.
Based on learnings: TWilson023 confirmed that all program pages have
workspaceSlugavailable, ensuring the conditional logic here is sound.
238-263: LGTM: UTM parameter handling is clean and consistent.The UTM section:
- Only renders when parameters exist (line 238)
- Correctly limits analytics links to workspace context (line 248)
- Uses the same sound URL construction pattern as device/browser/OS links
The grid layout efficiently displays parameter labels and values while handling overflow properly.
266-319: LGTM: Partner and referral link handling is well-structured.The section correctly adapts to different contexts:
- Partner information (lines 272-291): Renders partner avatar and name with link when in workspace context
- Referral link routing (lines 302-307): Appropriately distinguishes between:
- Workspace: Direct link to full link detail page
- Partner-side: Analytics page with domain/key filters
The different routing approaches make sense given partner users likely have restricted access to link detail pages.
84-332: LGTM: Clean card-based layout with excellent loading state handling.The refactored component structure provides:
- Clear visual hierarchy with the new card container
- Consistent skeleton loading states throughout (lines 96, 118, 130, 149, 218, 296)
- Responsive grid layouts that adapt to different viewport sizes
- Improved semantic HTML with
DetailHeadingusingh3instead ofh2(line 328)The modular organization with distinct sections (avatar, basic fields, details, UTM, referral) makes the component easy to maintain and extend.
apps/web/lib/swr/use-partner-customer.ts (1)
13-15: The current implementation is correct and should not be changed. The API endpoint conditionally adds thenamefield to the response based oncustomerDataSharingEnabledAt, so the field is not always present. UpdatingPartnerProfileCustomerSchemato unconditionally includenamewould misrepresent the API contract. The frontend's type intersectionPartnerProfileCustomerProps & { name?: string | null }correctly reflects that the field may or may not be present in the response.Likely an incorrect or invalid review comment.
Summary by CodeRabbit
New Features
Improvements
Removals / Migration
Bug Fixes / UX
βοΈ Tip: You can customize this high-level summary in your review settings.