Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@steven-tey
Copy link
Collaborator

@steven-tey steven-tey commented Nov 22, 2025

Summary by CodeRabbit

  • New Features

    • Status badge tooltips now show exact payout dates and include commission + group context.
    • Emails and payout views show an estimated arrival date when available.
  • Refactor

    • Commission data and schemas now include group enrollment details and program schema trimmed of unused fields.
    • Partner status type removed from exports.
  • UI/Behavior

    • Payout links simplified (removed explicit sort param) and default payout sort orders updated.
  • Chore

    • Email package added a date utility dependency.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link
Contributor

vercel bot commented Nov 22, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Nov 22, 2025 9:40pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 22, 2025

Warning

Rate limit exceeded

@steven-tey has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 9 minutes and 26 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 57bec77 and ac0a250.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • apps/web/package.json (1 hunks)
  • packages/email/package.json (1 hunks)
  • packages/email/src/templates/partner-payout-confirmed.tsx (6 hunks)
  • packages/ui/package.json (1 hunks)

Walkthrough

Removed includeProgramEnrollment usage from commission queries and exports; switched groupId filtering to use programEnrollment; surfaced partner.groupId in commission payloads and UI; swapped partner schema to include groupId; removed PartnerStatus and several ProgramSchema fields; adjusted payouts sorting defaults and links.

Changes

Cohort / File(s) Summary
Commission API & Export
apps/web/app/(ee)/api/commissions/route.ts, apps/web/app/(ee)/api/commissions/export/route.ts
GET now augments returned partner with groupId from programEnrollment; export/remove calls no longer pass includeProgramEnrollment.
Commission Batch Fetch
apps/web/app/(ee)/api/cron/commissions/export/fetch-commissions-batch.ts
Removed includeProgramEnrollment argument from getCommissions in pagination loop.
Query Helpers
apps/web/lib/api/commissions/get-commissions.ts, apps/web/lib/api/commissions/get-commissions-count.ts
Removed includeProgramEnrollment flag; group filtering changed from partner.programs.some to programEnrollment.groupId; always include partner and programEnrollment.
Zod Schemas — Commissions & Partners
apps/web/lib/zod/schemas/commissions.ts, apps/web/lib/zod/schemas/partners.ts, apps/web/lib/zod/schemas/partner-profile.ts
CommissionEnriched now uses EnrolledPartnerSchema including groupId; removed status field/PartnerStatus import; added partnerProfilePayoutsQuerySchema default.
Program Schema
apps/web/lib/zod/schemas/programs.ts
Removed fields: brandColor, holdingPeriodDays, landerPublishedAt, autoApprovePartnersEnabledAt, wordmark.
UI — Commission Table & Badges
apps/web/app/app.dub.co/.../commission-table.tsx, apps/web/ui/partners/commission-status-badges.tsx, apps/web/app/(ee)/partners.dub.co/.../earnings/earnings-table.tsx
Added groups hook and group column render; StatusBadge tooltip payload now receives group and commission (createdAt); earnings tooltip now passes commission row.
Payouts: Sorting & Links
multiple files (examples: apps/web/app/.../payouts/payout-table.tsx, apps/web/app/.../overview-tasks.tsx, apps/web/ui/layout/sidebar/app-sidebar-nav.tsx, packages/email/src/templates/*.tsx, apps/web/app/(ee)/app.dub.co/invoices/*)
Changed payouts sorting defaults/allowed fields (default moved to amount / initiatedAt available); removed sortBy=amount from many generated links.
Prisma schema & client exports
packages/prisma/schema/partner.prisma, packages/prisma/client.ts
Removed PartnerStatus enum and status field on Partner; removed PartnerStatus from client exports.
Email & Payout Payloads
apps/web/app/(ee)/api/cron/payouts/charge-succeeded/queue-external-payouts.ts, apps/web/app/(ee)/api/cron/payouts/process/updates/route.ts, packages/email/src/templates/*.tsx
Added initiatedAt to payout payloads sent to email/webhook templates; updated templates to display estimated arrival when initiatedAt present; added date-fns dependency.
Misc — Discoverability & Branding types
apps/web/lib/partners/get-discoverability-requirements.ts, apps/web/ui/partners/groups/design/branding-form.tsx
Adjusted discovery label formatting using raw value; BrandingForm now types branding fields from PartnerGroupProps instead of ProgramProps.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API as Commissions API
    participant DB as Database
    participant UI as Frontend

    Note over API,DB: groupId filtering now uses programEnrollment

    Client->>API: GET /api/commissions?groupId=...
    API->>DB: getCommissions(filters with programEnrollment.groupId)
    DB-->>API: Commissions[] with programEnrollment
    API->>API: For each commission, set partner.groupId = programEnrollment.groupId
    API-->>Client: Commissions[] (partner enriched with groupId)

    Client->>UI: render table (useGroups)
    UI->>Client: groups[]
    Client->>Client: resolve group by partner.groupId
    Client->>Client: compute payout date (commission.createdAt + holdingPeriodDays)
    Client-->>Client: render StatusBadge tooltip with group + commission data
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Areas needing extra attention:
    • apps/web/ui/partners/commission-status-badges.tsx — payout date calculation, URL construction, variant conditions
    • apps/web/lib/api/commissions/get-commissions.ts and get-commissions-count.ts — ensure programEnrollment/groupId semantics are correct
    • packages/prisma/schema/partner.prisma and packages/prisma/client.ts — removal of PartnerStatus may require migrations or client regen
    • Zod schema changes (commissions/partners) — verify downstream type consumers

Possibly related PRs

Suggested reviewers

  • devkiran

Poem

🐇 I hopped through commits, groupId in tow,

swapped schemas and links so the payouts can show.
No status enum, just leaner delight,
tooltips and tables now peek at the right night.
A tiny rabbit cheer — code feels light. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately captures the main objective: improving payouts UX by adding clear timestamps related to holding periods, which is reflected across multiple files introducing date calculations, UI improvements, and schema changes.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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/partners/commission-status-badges.tsx (1)

1-25: New holding‑period tooltips are logically correct; tighten type imports for Prisma

The updated CommissionTooltipDataProps and pending tooltip logic correctly:

  • Require a commission object so you can compute the payout date from createdAt.
  • Use group.holdingPeriodDays to show an exact “eligible for payout on `` …” message when configured, and fall back to “shortly” otherwise.
  • Provide a more specific workspace link to the partner group’s settings while keeping the partner variant on the generic help article.

Two follow-ups to consider:

  1. Use type-only imports for Prisma models

Since Commission, PartnerGroup, and Program are used purely for typing in a UI module that’s consumed by client components, it’s safer to make this a type-only import to avoid accidentally bundling Prisma client code into the browser:

-import { Commission, PartnerGroup, Program } from "@dub/prisma/client";
+import type { Commission, PartnerGroup, Program } from "@dub/prisma/client";
  1. Assumptions about data presence

The pending tooltip assumes:

  • commission.createdAt is a valid Date/ISO string (so addDays and formatDateTimeSmart behave sensibly), and
  • when you expect a concrete payout date, group.holdingPeriodDays is configured.

Those assumptions look consistent with how the call sites were wired in this PR, but are worth keeping in mind if any new callers are added later.

Also applies to: 29-37

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8d7fd8c and aff74d3.

📒 Files selected for processing (12)
  • apps/web/app/(ee)/api/commissions/export/route.ts (0 hunks)
  • apps/web/app/(ee)/api/commissions/route.ts (1 hunks)
  • apps/web/app/(ee)/api/cron/commissions/export/fetch-commissions-batch.ts (0 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-table.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/commissions/commission-table.tsx (3 hunks)
  • apps/web/lib/api/commissions/get-commissions-count.ts (1 hunks)
  • apps/web/lib/api/commissions/get-commissions.ts (1 hunks)
  • apps/web/lib/partners/get-discoverability-requirements.ts (1 hunks)
  • apps/web/lib/zod/schemas/commissions.ts (2 hunks)
  • apps/web/lib/zod/schemas/programs.ts (0 hunks)
  • apps/web/ui/partners/commission-status-badges.tsx (2 hunks)
  • packages/prisma/schema/partner.prisma (0 hunks)
💤 Files with no reviewable changes (4)
  • apps/web/app/(ee)/api/cron/commissions/export/fetch-commissions-batch.ts
  • apps/web/app/(ee)/api/commissions/export/route.ts
  • apps/web/lib/zod/schemas/programs.ts
  • packages/prisma/schema/partner.prisma
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-10-17T08:18:19.278Z
Learnt from: devkiran
Repo: dubinc/dub PR: 0
File: :0-0
Timestamp: 2025-10-17T08:18:19.278Z
Learning: In the apps/web codebase, `@/lib/zod` should only be used for places that need OpenAPI extended zod schema. All other places should import from the standard `zod` package directly using `import { z } from "zod"`.

Applied to files:

  • apps/web/lib/zod/schemas/commissions.ts
📚 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/commissions/commission-table.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/partners/commission-status-badges.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-table.tsx
  • apps/web/lib/partners/get-discoverability-requirements.ts
🧬 Code graph analysis (2)
apps/web/lib/zod/schemas/commissions.ts (1)
apps/web/lib/zod/schemas/partners.ts (1)
  • EnrolledPartnerSchema (344-444)
apps/web/lib/partners/get-discoverability-requirements.ts (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 (7)
apps/web/lib/partners/get-discoverability-requirements.ts (1)

95-95: LGTM! Correct usage of currencyFormatter.

The change correctly passes the value in cents directly to currencyFormatter, which handles the division by 100 internally for USD. This will properly display "$10" as the commission threshold (with stripIfInteger removing the decimal zeros), and is consistent with the validation logic at line 22 that compares cents to cents.

apps/web/lib/api/commissions/get-commissions-count.ts (1)

57-60: Group-based filtering via programEnrollment looks correct

Using ...(groupId && { programEnrollment: { groupId } }) is a clean way to scope counts to a specific group and avoids touching unaffected queries when groupId is undefined. No issues from a Prisma or logic perspective.

apps/web/lib/api/commissions/get-commissions.ts (1)

57-61: Alignment of commissions query with programEnrollment/group usage

Routing the groupId filter through programEnrollment: { groupId } and always including both partner and programEnrollment matches the new data model and downstream tooltip/schema expectations. This keeps the group-aware behavior localized to the enrollment relation while keeping the invoice branch untouched.

Also applies to: 64-67

apps/web/lib/zod/schemas/commissions.ts (1)

6-7: Using EnrolledPartnerSchema for commission partner shape is consistent

Switching CommissionEnrichedSchema.partner to EnrolledPartnerSchema.pick(...) and explicitly including groupId aligns the commissions API with the enrolled-partner model and the new group-aware UI. This avoids schema drift and makes it clear which partner fields are guaranteed on commission responses.

Also applies to: 32-40

apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-table.tsx (1)

239-252: Passing commission into status tooltip matches new tooltip contract

Adding commission: row.original to the tooltip payload correctly satisfies the new CommissionStatusBadges signature and enables date-based messaging while reusing existing row data.

apps/web/app/(ee)/api/commissions/route.ts (1)

52-59: Confirm programEnrollment is always present for returned commissions

You’re now deriving partner.groupId from c.programEnrollment.groupId, which is what the enriched schema and tooltips expect. This is fine as long as every commission returned by this route has a non-null programEnrollment with a groupId; otherwise, c.programEnrollment.groupId will throw.

If there are any legacy/manual commissions without an enrollment, consider either:

  • enforcing the invariant at the Prisma schema/migration level, or
  • guarding here (e.g. using optional chaining and handling the no-group case explicitly).
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/commissions/commission-table.tsx (1)

4-5: Group-aware status tooltip wiring is sound

Looking up the group via groups?.find((g) => g.id === row.original.partner.groupId) and passing it, along with commission: row.original, cleanly connects the new group/holding‑period context into the status tooltip. The conditional groupId check and optional chaining ensure it degrades gracefully when group data is absent.

Also applies to: 54-55, 192-208

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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/app/app.dub.co/(dashboard)/[slug]/(ee)/program/commissions/commission-table.tsx (1)

4-4: Group resolution for the new column and status tooltip is correct; consider de‑duplicating the lookup

The useGroups hook and the new group column correctly guard for missing groups and missing matches, returning "-" safely. The status cell also passes the resolved group (or undefined) plus commission, program, and workspace into CommissionStatusBadges[...] .tooltip, which lines up with the new pending‑state copy that references the group’s holding period and settings URL.

You’re doing groups.find((g) => g.id === row.original.partner.groupId) in both the group column and the status tooltip; if the groups list grows, it’s worth centralizing to a memoized lookup map keyed by id to avoid repeated linear scans and to keep the column definitions a bit leaner.

@@
-  const { program } = useProgram();
-  const { groups } = useGroups();
+  const { program } = useProgram();
+  const { groups } = useGroups();
+
+  const groupsById = useMemo(
+    () =>
+      new Map(
+        (groups ?? []).map((group) => [group.id, group] as const),
+      ),
+    [groups],
+  );
@@
-        {
-          id: "group",
-          header: "Group",
-          cell: ({ row }) => {
-            if (!groups) return "-";
-
-            const group = groups.find(
-              (g) => g.id === row.original.partner.groupId,
-            );
-
-            if (!group) return "-";
-
-            return (
-              <div className="flex items-center gap-2">
-                <GroupColorCircle group={group} />
-                <span className="truncate text-sm font-medium">
-                  {group.name}
-                </span>
-              </div>
-            );
-          },
-        },
+        {
+          id: "group",
+          header: "Group",
+          cell: ({ row }) => {
+            if (!groups) return "-";
+
+            const group = groupsById.get(row.original.partner.groupId);
+            if (!group) return "-";
+
+            return (
+              <div className="flex items-center gap-2">
+                <GroupColorCircle group={group} />
+                <span className="truncate text-sm font-medium">
+                  {group.name}
+                </span>
+              </div>
+            );
+          },
+        },
@@
-                tooltip={badge.tooltip({
+                tooltip={badge.tooltip({
                   variant: "workspace",
                   program,
                   workspace,
-                  group: row.original.partner.groupId
-                    ? groups?.find((g) => g.id === row.original.partner.groupId)
-                    : undefined,
+                  group:
+                    row.original.partner.groupId && groups
+                      ? groupsById.get(row.original.partner.groupId)
+                      : undefined,
                   commission: row.original,
                 })}

Also applies to: 80-81, 181-201, 266-280

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aff74d3 and 0a9eaf5.

📒 Files selected for processing (16)
  • apps/web/app/(ee)/api/partner-profile/payouts/route.ts (1 hunks)
  • apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/partner-payout-invoice.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-table.tsx (2 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/commissions/commission-table.tsx (6 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/overview-tasks.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-table.tsx (3 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/success/page-client.tsx (2 hunks)
  • apps/web/lib/zod/schemas/partner-profile.ts (2 hunks)
  • apps/web/lib/zod/schemas/partners.ts (0 hunks)
  • apps/web/lib/zod/schemas/payouts.ts (1 hunks)
  • apps/web/ui/layout/sidebar/app-sidebar-nav.tsx (1 hunks)
  • apps/web/ui/partners/commission-status-badges.tsx (3 hunks)
  • apps/web/ui/partners/groups/design/branding-form.tsx (2 hunks)
  • packages/email/src/templates/partner-payout-failed.tsx (2 hunks)
  • packages/email/src/templates/program-payout-reminder.tsx (1 hunks)
  • packages/prisma/client.ts (0 hunks)
💤 Files with no reviewable changes (2)
  • packages/prisma/client.ts
  • apps/web/lib/zod/schemas/partners.ts
🧰 Additional context used
🧠 Learnings (4)
📚 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/layout/sidebar/app-sidebar-nav.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/success/page-client.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-table.tsx
  • apps/web/ui/partners/commission-status-badges.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/overview-tasks.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/commissions/commission-table.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
  • packages/email/src/templates/partner-payout-failed.tsx
  • apps/web/ui/partners/commission-status-badges.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/partners/groups/design/branding-form.tsx
📚 Learning: 2025-07-11T16:28:55.693Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2635
File: packages/prisma/schema/payout.prisma:24-25
Timestamp: 2025-07-11T16:28:55.693Z
Learning: In the Dub codebase, multiple payout records can now share the same stripeTransferId because payouts are grouped by partner and processed as single Stripe transfers. This is why the unique constraint was removed from the stripeTransferId field in the Payout model - a single transfer can include multiple payouts for the same partner.

Applied to files:

  • apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/partner-payout-invoice.tsx
🧬 Code graph analysis (4)
apps/web/ui/partners/groups/design/branding-form.tsx (1)
apps/web/lib/types.ts (1)
  • PartnerGroupProps (581-583)
apps/web/app/(ee)/api/partner-profile/payouts/route.ts (2)
apps/web/lib/auth/partner.ts (1)
  • withPartnerProfile (47-175)
apps/web/lib/zod/schemas/partner-profile.ts (1)
  • partnerProfilePayoutsQuerySchema (198-200)
apps/web/lib/zod/schemas/partner-profile.ts (1)
apps/web/lib/zod/schemas/payouts.ts (1)
  • payoutsQuerySchema (26-36)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/commissions/commission-table.tsx (5)
packages/ui/src/timestamp-tooltip.tsx (1)
  • TimestampTooltip (28-50)
apps/web/ui/partners/partner-row-item.tsx (1)
  • PartnerRowItem (127-172)
packages/ui/src/tooltip.tsx (1)
  • Tooltip (66-117)
apps/web/ui/partners/commission-status-badges.tsx (1)
  • CommissionStatusBadges (27-129)
packages/ui/src/table/table.tsx (1)
  • useTable (51-243)
⏰ 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 (23)
apps/web/ui/partners/groups/design/branding-form.tsx (2)

9-9: LGTM!

The import addition is necessary for the type change in BrandingFormData and is correctly formatted.


40-43: LGTM! Type definition now aligns with actual usage.

This change correctly updates BrandingFormData to source branding fields from PartnerGroupProps instead of ProgramProps, which aligns with how the form actually accesses these fields from the group object (lines 177-179).

apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-table.tsx (2)

36-36: LGTM: Default sort improves UX.

Changing the default sort from "periodEnd" to "initiatedAt" aligns with the PR's goal of improving payout UX with clear timestamps. The "Initiated" column includes helpful tooltips explaining the payout timeline.


67-71: Verify removal of Period column sortability.

The "Period" column (periodEnd) is still displayed in the table but has been removed from the sortableColumns array. Users may expect to sort by this column since it's prominently displayed. Please confirm this is intentional.

If removing sortability is intentional, consider whether users will find this confusing, especially since Period is the first column shown.

Also applies to: 181-181

packages/email/src/templates/program-payout-reminder.tsx (1)

92-92: LGTM: Consistent URL simplification.

Removing the explicit sortBy=amount parameter is consistent with the PR's approach of relying on default sorting behavior at the destination.

apps/web/lib/zod/schemas/partner-profile.ts (1)

197-200: LGTM: Partner-specific sort default.

The new partnerProfilePayoutsQuerySchema appropriately overrides the default sortBy to "initiatedAt" for partner-facing payout views, aligning with the UX improvements in the partner payout table.

apps/web/ui/layout/sidebar/app-sidebar-nav.tsx (1)

220-220: LGTM: Consistent navigation URL.

Removing the sortBy=amount parameter from the Payouts navigation link is consistent with the PR's approach of relying on default sorting at the destination page.

apps/web/app/(ee)/api/partner-profile/payouts/route.ts (1)

3-4: LGTM: Correct schema for partner context.

Switching to partnerProfilePayoutsQuerySchema ensures the partner-facing API route uses the appropriate default sortBy value ("initiatedAt"), consistent with the partner UX improvements.

Also applies to: 12-12

apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/partner-payout-invoice.tsx (1)

401-401: LGTM: Consistent URL in PDF invoice.

Removing the sortBy=amount parameter from the payouts link in the PDF invoice aligns with the PR's URL simplification pattern.

apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-table.tsx (3)

59-59: LGTM: Appropriate default for program view.

Defaulting to "amount" sorting for the program owner's payout table makes sense, as program owners likely want to prioritize payouts by size. This differs appropriately from the partner view which defaults to "initiatedAt".


108-111: Verify removal of Period column sortability.

Similar to the partner payout table, the "Period" column (periodEnd) is displayed but removed from sortableColumns. This change appears in multiple payout tables, suggesting it may be intentional, but please confirm this design decision is consistent with user expectations.

Also applies to: 233-233


343-343: LGTM: Consistent tooltip link.

Removing the sortBy=amount parameter from the tooltip link is consistent with the PR's URL simplification approach.

packages/email/src/templates/partner-payout-failed.tsx (1)

119-119: LGTM: Consistent email links.

Removing the sortBy=amount parameter from both payout links in the email template is consistent with the PR's URL simplification pattern.

Also applies to: 130-130

apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/overview-tasks.tsx (1)

39-39: LGTM! Removal of redundant sortBy parameter.

The removal of sortBy=amount is appropriate since the schema now defaults to sorting by amount (as updated in apps/web/lib/zod/schemas/payouts.ts line 33).

apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/success/page-client.tsx (1)

79-79: LGTM! Consistent removal of redundant sortBy parameter.

Both navigation links now correctly omit sortBy=amount since it matches the schema default.

Also applies to: 98-98

apps/web/ui/partners/commission-status-badges.tsx (4)

1-15: LGTM! Proper imports for date calculations.

The new imports support computing exact payout dates based on holding periods. The types and utilities are appropriate for the functionality being added.


19-24: LGTM! Extended type definitions for payout date calculations.

The addition of slug to the group type and createdAt from Commission enables the tooltip to display exact payout dates and link to group settings.


50-50: LGTM! Consistent removal of redundant sortBy parameter.

The removal of &sortBy=amount aligns with the schema changes and is consistent with other files in this PR.


35-36: Date calculation logic is correct and handles timezone and boundary cases appropriately.

Verification confirms:

  • The addDays function from date-fns properly handles date arithmetic
  • The conditional check data.group?.holdingPeriodDays ensures the calculation only executes when the value exists
  • The formatDateTimeSmart function uses toLocaleDateString and properly handles year display based on the current date
  • Edge cases are properly handled: when holdingPeriodDays is 0, the date shows as today (correct); when undefined/null, it displays "shortly"
  • Timezone handling is appropriate: JavaScript's Date object automatically manages UTC-to-local conversion for display purposes

The code follows best practices with standard libraries and doesn't require fixes. The suggestion to add unit tests (if desired) would be optional for improving test coverage, but the implementation itself is sound.

apps/web/lib/zod/schemas/payouts.ts (1)

33-33: Schema change verified—no breaking references remain in the codebase.

The removal of "createdAt" and "periodEnd" from the sortBy enum is properly implemented:

  • Both UI components (payout-table.tsx in partner and program dashboards) use only the new enum values: ["amount", "initiatedAt", "paidAt"]
  • No code references the removed sortBy options
  • The initiatedAt field is correctly wired into sortable columns and display logic in both dashboards
  • Data model fields (createdAt, periodEnd) remain unchanged—only the sortBy enum was updated
  • A backfill migration (backfill-payout-initiated-at.ts) exists to populate the new field
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/commissions/commission-table.tsx (3)

43-63: Column visibility + dynamic defaults are wired cleanly; just ensure useColumnVisibility reacts as expected

The commissionsColumns config, defaultVisibleColumns derived from program.primaryRewardEvent, and the useColumnVisibility("commissions-table-columns", …) integration with useTable all look consistent. The menu column is correctly forced to stay visible via enableHiding: false and the final .filter against commissionsColumns.all keeps things sane.

One thing to double‑check: when program transitions from undefined to a loaded "sale" program, defaultVisibleColumns changes to include "amount". This relies on useColumnVisibility honoring runtime changes to its defaultVisible option; otherwise, the "amount" column may remain hidden by default even for sale programs on first load. If the hook does not already handle this, consider an internal useEffect there keyed on defaultVisible to resync initial visibility when no user overrides are stored.

Also applies to: 107-127, 289-297, 301-322


129-144: TimestampTooltip usage on the Date column aligns well with the UX goal

Wrapping createdAt in TimestampTooltip with rows={["local", "utc", "unix"]}, a small delay, and keeping the human‑readable formatDateTimeSmart as the visible text is a good balance of clarity and detail. This should make the holding‑period timing much clearer without cluttering the table.


224-260: Commission cell tooltip and styling logic look solid

The commission cell cleanly derives earnings, uses CLAWBACK_REASONS_MAP to surface a human‑readable clawback reason when description is present, and falls back to the raw description string otherwise. The Tooltip integration and conditional red styling for negative earnings are applied consistently in both the tooltip and non‑tooltip branches, and the cursor-help + dotted underline give a clear affordance without impacting non‑clawback rows.

@steven-tey
Copy link
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 22, 2025

✅ Actions performed

Full review triggered.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

🧹 Nitpick comments (1)
apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-table.tsx (1)

144-157: Extract duplicate addBusinessDays calculation to variable; clarify holiday handling limitation.

The expression addBusinessDays(row.original.initiatedAt, 5) is computed twice (lines 146 and 150). Extract it to a variable for readability and efficiency:

) : row.original.initiatedAt ? (
+  (() => {
+    const estimatedDate = addBusinessDays(row.original.initiatedAt, 5);
+    return (
     <Tooltip
-      content={`This payout is estimated to be processed on \`${formatDateTimeSmart(addBusinessDays(row.original.initiatedAt, 5), { month: "short" })}\` (after 5 business days)`}
+      content={`This payout is estimated to be processed on \`${formatDateTimeSmart(estimatedDate, { month: "short" })}\` (after 5 business days)`}
     >
       <span className="hover:text-content-emphasis text-content-muted flex items-center gap-1 underline decoration-dotted underline-offset-2">
         <CircleHalfDottedClock className="size-3.5 shrink-0" />{" "}
-        {formatDateSmart(addBusinessDays(row.original.initiatedAt, 5), {
+        {formatDateSmart(estimatedDate, {
           month: "short",
         })}
       </span>
     </Tooltip>
+    );
+  })()
) : (

Note: The addBusinessDays function from date-fns skips weekends (Mon–Fri) but does not handle public holidays—there's no built-in holiday list. The email template claims payouts are "excluding weekends and public holidays," but the code only excludes weekends. Consider adding holiday logic or updating the messaging to match the actual behavior.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a9eaf5 and 57bec77.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • apps/web/app/(ee)/api/cron/payouts/charge-succeeded/queue-external-payouts.ts (1 hunks)
  • apps/web/app/(ee)/api/cron/payouts/process/updates/route.ts (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-table.tsx (4 hunks)
  • packages/email/package.json (1 hunks)
  • packages/email/src/templates/partner-payout-confirmed.tsx (6 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-24T16:10:37.349Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/ui/partners/partner-about.tsx:11-11
Timestamp: 2025-09-24T16:10:37.349Z
Learning: In the Dub codebase, the team prefers to import Icon as a runtime value from "dub/ui" and uses Icon as both a type and variable name in component props, even when this creates shadowing. This is their established pattern and should not be suggested for refactoring.

Applied to files:

  • packages/email/src/templates/partner-payout-confirmed.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-table.tsx
📚 Learning: 2025-09-17T17:44:03.965Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2857
File: apps/web/lib/actions/partners/update-program.ts:96-0
Timestamp: 2025-09-17T17:44:03.965Z
Learning: In apps/web/lib/actions/partners/update-program.ts, the team prefers to keep the messagingEnabledAt update logic simple by allowing client-provided timestamps rather than implementing server-controlled timestamp logic to avoid added complexity.

Applied to files:

  • apps/web/app/(ee)/api/cron/payouts/process/updates/route.ts
🧬 Code graph analysis (1)
apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-table.tsx (1)
packages/ui/src/tooltip.tsx (1)
  • Tooltip (66-117)
⏰ 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/app/(ee)/api/cron/payouts/process/updates/route.ts (1)

111-111: LGTM!

The addition of initiatedAt to the email payload is consistent with the template changes and properly passes the timestamp from the payout record.

apps/web/app/(ee)/api/cron/payouts/charge-succeeded/queue-external-payouts.ts (1)

136-136: LGTM!

Consistent addition of initiatedAt to the external payout email payload.

packages/email/src/templates/partner-payout-confirmed.tsx (3)

1-6: LGTM!

The new imports (formatDateTimeSmart and addBusinessDays) are properly utilized in the estimated arrival date feature.

Also applies to: 20-20


50-50: LGTM!

The nullable initiatedAt field is appropriate and aligns with the conditional rendering logic.


123-130: LGTM!

The wording changes improve clarity by using "transferred" and explicitly referencing "Stripe Express account."

apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-table.tsx (3)

21-34: LGTM! Imports are correctly added for the new feature.

The new imports (CircleHalfDottedClock, formatDateTimeSmart, and addBusinessDays) are all utilized in the estimated payout date functionality.


43-43: Inconsistency between code and AI-generated summary.

The default sortBy is set to "initiatedAt", but the AI summary states it changed to "amount". The code takes precedence, so this appears to be an inaccuracy in the summary.


199-199: LGTM! sortableColumns correctly updated.

The change from ["periodEnd", "amount", "paidAt"] to ["amount", "initiatedAt", "paidAt"] aligns well with the new focus on initiated timestamps and improves the payout UX as intended.

@socket-security
Copy link

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updateddate-fns@​3.6.0 ⏵ 4.1.01001009280100

View full report

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants