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 11, 2025

Summary by CodeRabbit

  • Refactor

    • Centralized zero-decimal currency detection and updated currency formatting to use the unified runtime check.
  • UI / UX

    • Adjusted currency displays across dashboards, tables, charts, emails and logs — many amounts now render using the updated unit convention for consistent, clearer presentation.

@vercel
Copy link
Contributor

vercel bot commented Nov 11, 2025

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

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Nov 11, 2025 7:40pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 11, 2025

Walkthrough

Centralizes zero-decimal currency detection into a new utility, updates the currency formatter to accept minor-unit inputs and decide scaling at runtime, removes the exported ZERO_DECIMAL_CURRENCIES constant, and changes many call sites to pass raw minor-unit values (removing prior /100 scaling).

Changes

Cohort / File(s) Summary
Zero-decimal utility
packages/utils/src/functions/currency-zero-decimal.ts
Added isZeroDecimalCurrency(currency: string) — predicate that uppercases input and checks membership in a zero-decimal list.
Functions index export
packages/utils/src/functions/index.ts
Re-exported the new zero-decimal utility.
Currency formatter
packages/utils/src/functions/currency-formatter.ts
Changed signature to accept minor-unit input (valueInCents), added options.currency selection, and uses isZeroDecimalCurrency(currency) to decide whether to divide by 100 before formatting.
Analytics convert
apps/web/lib/analytics/convert-currency.ts
Removed ZERO_DECIMAL_CURRENCIES constant; imported and replaced .includes(...) checks with isZeroDecimalCurrency(...).
Formatter call-site updates (UI, API, emails, scripts, logs)
apps/... apps/web/app/... apps/web/ui/... packages/email/... apps/web/lib/... apps/web/scripts/...
Replaced numerous value / 100 usages with direct value when calling currencyFormatter, treating values as minor-unit/raw inputs. Mostly presentation/logging changes; control flow largely unchanged.
Sample events
apps/web/lib/webhook/sample-events/partner-enrolled.json
Added trustedAt: null to the sample payload.

Sequence Diagram(s)

sequenceDiagram
  participant Caller as Call Site (UI / API / Email / Log)
  participant Formatter as currencyFormatter
  participant ZeroDec as isZeroDecimalCurrency
  participant Intl as Intl.NumberFormat

  Caller->>Formatter: currencyFormatter(valueInCents, { currency })
  Formatter->>ZeroDec: isZeroDecimalCurrency(currency)
  alt zero-decimal currency
    Formatter->>Intl: format(valueInCents)    %% no divide
  else standard currency
    Formatter->>Intl: format(valueInCents / 100) %% divide by 100
  end
  Intl-->>Formatter: formattedString
  Formatter-->>Caller: formattedString
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

  • Files to pay extra attention to:
    • packages/utils/src/functions/currency-formatter.ts — verify signature/options and zero-decimal branching.
    • Wide call-site changes — ensure values are consistently treated as minor-unit to avoid double-scaling.
    • apps/web/lib/analytics/convert-currency.ts — confirm behavior parity after replacing the constant with the new utility.

Possibly related PRs

Suggested reviewers

  • devkiran

Poem

🐇
I hopped through cents and currency trees,
Gathered codes and trimmed old keys.
One small util now shows the way —
Format by unit, clean as day.
Hop, format, deliver — carrots for play! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.13% 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 'Handle zero-decimal currencies in currencyFormatter' accurately summarizes the main change: introducing logic to properly handle zero-decimal currencies (like JPY, KRW) in the currency formatting utility.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch handle-zero-decimal-currency

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: 2

🧹 Nitpick comments (2)
packages/utils/src/functions/currency-formatter.ts (2)

8-21: Consider renaming parameter to better reflect its usage.

The parameter name valueInCents is misleading for zero-decimal currencies, which don't have fractional units. Consider using a more generic name like valueInSmallestUnit or amountInMinorUnits to better represent the expected input.

Apply this diff:

 export const currencyFormatter = (
-  valueInCents: number,
+  amountInMinorUnits: number,
   options?: CurrencyFormatterOptions,
 ) => {
   const currency = options?.currency || "USD";
   return Intl.NumberFormat("en-US", {
     style: "currency",
     currency,
     minimumFractionDigits: 2,
     maximumFractionDigits: 2,
     trailingZeroDisplay: "auto",
     ...options,
   } as CurrencyFormatterOptions).format(
-    isZeroDecimalCurrency(currency) ? valueInCents : valueInCents / 100,
+    isZeroDecimalCurrency(currency) ? amountInMinorUnits : amountInMinorUnits / 100,
   );
 };

7-22: Add JSDoc documentation to clarify expected input format.

The function should have documentation explaining that it expects values in the smallest currency unit (cents for USD, whole units for JPY, etc.) and how it handles zero-decimal currencies.

Add JSDoc above the function:

/**
 * Formats a monetary amount as a currency string.
 * 
 * @param valueInCents - The amount in the currency's smallest unit (e.g., cents for USD, whole units for JPY/KRW)
 * @param options - Intl.NumberFormat options including currency code (defaults to USD)
 * @returns Formatted currency string (e.g., "$10.00", "¥1,000")
 * 
 * @example
 * currencyFormatter(1000, { currency: "USD" }) // "$10.00"
 * currencyFormatter(1000, { currency: "JPY" }) // "¥1,000"
 */
export const currencyFormatter = (
  valueInCents: number,
  options?: CurrencyFormatterOptions,
) => {
  // ... rest of implementation
};
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between faebf8f and 6e3a069.

📒 Files selected for processing (4)
  • apps/web/lib/analytics/convert-currency.ts (3 hunks)
  • packages/utils/src/functions/currency-formatter.ts (1 hunks)
  • packages/utils/src/functions/currency-zero-decimal.ts (1 hunks)
  • packages/utils/src/functions/index.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
packages/utils/src/functions/currency-formatter.ts (1)
packages/utils/src/functions/currency-zero-decimal.ts (1)
  • isZeroDecimalCurrency (1-19)
apps/web/lib/analytics/convert-currency.ts (1)
packages/utils/src/functions/currency-zero-decimal.ts (1)
  • isZeroDecimalCurrency (1-19)
⏰ 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)
packages/utils/src/functions/index.ts (1)

9-9: LGTM!

The export is correctly placed and maintains alphabetical ordering.

apps/web/lib/analytics/convert-currency.ts (3)

1-1: LGTM!

The import correctly brings in the centralized zero-decimal currency utility.


26-28: LGTM!

The zero-decimal currency logic is correct. For zero-decimal currencies, the input amount is in whole units (which are already the smallest unit), so multiplying by 100 converts the USD result from dollars to cents.


61-63: LGTM!

Consistent usage of the zero-decimal currency check, matching the logic in convertCurrency.

packages/utils/src/functions/currency-formatter.ts (2)

20-20: LGTM!

The conditional formatting logic correctly handles both zero-decimal and standard currencies by formatting zero-decimal currencies directly while dividing standard currencies by 100 to convert from cents to base units.


8-8: Breaking change requires verification of all callers and fixes for inconsistent edge cases.

The parameter rename from value to valueInCents is a breaking API change. Review of 130+ call sites shows:

  • Consistent pattern (95%+): All callers pass values in cents, dividing by 100 before display: currencyFormatter(value / 100)
  • Inconsistencies found (2 cases):
    • apps/web/ui/partners/bounties/bounty-logic.tsx:91currencyFormatter(value) missing division
    • apps/web/ui/partners/rewards/rewards-logic.tsx:264currencyFormatter(Number(value), {...}) missing division

The rewards form stores amountInCents (line 665), yet passes it to currencyFormatter without dividing by 100, creating an inconsistency with the rest of the codebase and the new parameter contract.

Required actions:

  1. Verify all callers now pass values in the smallest currency unit (cents for USD, whole units for zero-decimal currencies)
  2. Fix the two edge cases in bounty-logic and rewards-logic to divide by 100 before formatting
  3. Add a comment or test documenting that valueInCents must always receive values in cents
⛔ Skipped due to learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/ui/partners/bounties/bounty-logic.tsx:88-96
Timestamp: 2025-08-26T15:03:05.381Z
Learning: In bounty forms, currency values are stored in cents in the backend but converted to dollars when loaded into forms, and converted back to cents when saved. The form logic works entirely with dollar amounts. Functions like generateBountyName that run during save logic receive cent values and need to divide by 100, but display logic within the form should format dollar values directly.
packages/utils/src/functions/currency-zero-decimal.ts (1)

2-18: The review comment is based on a false premise and can be dismissed.

The code list matches Stripe's official zero-decimal currencies exactly. Stripe's zero-decimal currencies are: BIF, CLP, DJF, GNF, JPY, KMF, KRW, MGA, PYG, RWF, UGX, VND, VUV, XAF, XOF, XPF. The review comment's concern about TWD (Taiwan Dollar) is invalid—TWD is not on Stripe's zero-decimal list. Special cases like ISK, HUF, and TWD are noted separately in Stripe documentation and are intentionally omitted from this utility function, which correctly implements the primary zero-decimal currency list.

Likely an incorrect or invalid review comment.

@steven-tey
Copy link
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 11, 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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-composite-chart.tsx (1)

95-95: Critical: Remove the division by 100 in valueAccessor.

The valueAccessor divides values by 100, but the updated currencyFormatter now expects values in cents and handles the division internally. This causes a cascading issue where lines 204 and 226 pass dollar values to currencyFormatter, resulting in values that are 100× too small (double division by 100).

Apply this diff:

-            valueAccessor: (d) => (d.values[item] || 0) / 100,
+            valueAccessor: (d) => d.values[item] || 0,

This aligns with the change on line 182, which correctly passes raw cent values to currencyFormatter.

apps/web/ui/analytics/events/events-table.tsx (1)

208-213: Pass sale.currency to formatter and remove the trailingZeroDisplay override to enable proper zero-decimal currency handling.

The currencyFormatter function accepts a currency option parameter and automatically sets trailingZeroDisplay based on whether the currency is zero-decimal. The current code hardcodes both "USD" and trailingZeroDisplay: "stripIfInteger", which prevents the formatter from correctly handling non-USD currencies like JPY or KRW.

Update lines 208-212:

{currencyFormatter(getValue(), {
  currency: sale.currency?.toUpperCase() || "USD",
})}

This allows the formatter's built-in logic to properly format zero-decimal currencies and removes the conflicting override.

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

310-328: Critical: Inconsistent currency formatting for minimum payout amount.

Line 316 still divides minPayoutAmount by 100, while line 307 correctly passes payout.amount directly. This inconsistency will display the minimum payout threshold as 100× smaller than intended (e.g., $0.50 instead of $50).

Apply this diff to fix:

             title={`Your program's minimum payout amount is ${currencyFormatter(
-              minPayoutAmount / 100,
+              minPayoutAmount,
             )}. This payout will be accrued and processed during the next payout period.`}
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/commissions/commission-table.tsx (1)

1-330: Remove division by 100 before currencyFormatter calls in payout-stats.tsx

Critical bug found: ./apps/web/ui/layout/sidebar/payout-stats.tsx (lines 63–68 and 79–84) divides amounts by 100 before passing to currencyFormatter, which now expects values in cents. This causes incorrect currency formatting.

Change from:

currencyFormatter(
  (payoutsCount?.filter(...)?.reduce((acc, p) => acc + p.amount, 0) || 0) / 100,
)

To:

currencyFormatter(
  payoutsCount?.filter(...)?.reduce((acc, p) => acc + p.amount, 0) || 0,
)

Remove the / 100 division in both instances (lines 68 and 84).

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

86-87: Update the outdated comment.

The comment states "Convert total from cents to dollars," but the code no longer performs this conversion explicitly. The currencyFormatter now handles the conversion internally based on the currency type.

Apply this diff to update or remove the comment:

-  // Convert total from cents to dollars
-  const amountPaid = currencyFormatter(invoice.amount);
+  const amountPaid = currencyFormatter(invoice.amount);

or update it to reflect the actual behavior:

-  // Convert total from cents to dollars
+  // Format amount (currencyFormatter handles conversion internally)
   const amountPaid = currencyFormatter(invoice.amount);
apps/web/lib/partners/create-stripe-transfer.ts (1)

125-125: LGTM! Consider passing currency explicitly for consistency.

The change correctly removes the /100 division to align with the new currencyFormatter API.

For better maintainability and consistency with the hardcoded currency: "usd" on line 106, consider passing the currency explicitly to all currencyFormatter calls:

currencyFormatter(finalTransferableAmount, { currency: "USD" })

This would make the currency assumption explicit and easier to maintain if multi-currency support is added in the future. The same applies to lines 76 and 92.

apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-submissions-table.tsx (1)

246-254: Consider passing workspace currency if available.

The currencyFormatter defaults to USD when no currency option is provided. If the workspace supports multiple currencies, consider passing the workspace's currency setting to ensure accurate formatting.

Apply this diff if workspace has a currency property:

const formattedValue = isCurrencyAttribute(attribute)
  ? currencyFormatter(value, {
+     currency: workspace.currency,
      trailingZeroDisplay: "stripIfInteger",
    })
  : nFormatter(value, { full: true });

const formattedTarget = isCurrencyAttribute(attribute)
  ? currencyFormatter(target, {
+     currency: workspace.currency,
      trailingZeroDisplay: "stripIfInteger",
    })
  : nFormatter(target, { full: true });
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsx (1)

254-259: LGTM! Correctly updated for the new currencyFormatter API.

The change to pass the raw cent value directly to currencyFormatter is correct, as the formatter now handles the division by 100 internally. The other currency formatting paths in this file (lines 315, 355, 462) correctly remain unchanged since they use NumberFlow, which expects dollar amounts.

Minor note: The trailingZeroDisplay: "stripIfInteger" override on line 256 supersedes the formatter's built-in logic (which already sets this for zero-decimal currencies). While not incorrect, this explicit override ensures consistent behavior across all currency types.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e3a069 and ddf0560.

📒 Files selected for processing (87)
  • apps/web/app/(ee)/admin.dub.co/(dashboard)/commissions/client.tsx (3 hunks)
  • apps/web/app/(ee)/admin.dub.co/(dashboard)/components/user-info.tsx (1 hunks)
  • apps/web/app/(ee)/admin.dub.co/(dashboard)/payouts/client.tsx (3 hunks)
  • apps/web/app/(ee)/admin.dub.co/(dashboard)/revenue/client.tsx (1 hunks)
  • apps/web/app/(ee)/api/cron/payouts/balance-available/route.ts (2 hunks)
  • apps/web/app/(ee)/api/cron/payouts/process/process-payouts.ts (1 hunks)
  • apps/web/app/(ee)/api/cron/trigger-withdrawal/route.ts (2 hunks)
  • apps/web/app/(ee)/app.dub.co/embed/referrals/activity.tsx (1 hunks)
  • apps/web/app/(ee)/app.dub.co/embed/referrals/earnings-summary.tsx (1 hunks)
  • apps/web/app/(ee)/app.dub.co/embed/referrals/earnings.tsx (1 hunks)
  • apps/web/app/(ee)/app.dub.co/embed/referrals/leaderboard.tsx (1 hunks)
  • apps/web/app/(ee)/app.dub.co/embed/referrals/links-list.tsx (1 hunks)
  • apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/domain-renewal-invoice.tsx (3 hunks)
  • apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/partner-payout-invoice.tsx (4 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/partner-payout-details-sheet.tsx (2 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-stats.tsx (3 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-table.tsx (2 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-composite-chart.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-table.tsx (2 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/payouts-card.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/invoices/[payoutId]/route.tsx (1 hunks)
  • apps/web/app/api/callback/plain/partner/route.ts (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/analytics/analytics-partners-table.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-submissions-table.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/transactional-campaign-logic.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/commissions/commission-table.tsx (2 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/commissions/create-commission-sheet.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/groups/groups-table.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/links/page-client.tsx (2 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/partner-stats.tsx (3 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/payouts/page-client.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-details-sheet.tsx (2 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-stats.tsx (4 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-table.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/success/page-client.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/invoices/page-client.tsx (2 hunks)
  • apps/web/lib/api/bounties/generate-performance-bounty-name.ts (1 hunks)
  • apps/web/lib/api/errors.ts (1 hunks)
  • apps/web/lib/api/sales/construct-discount-amount.ts (1 hunks)
  • apps/web/lib/api/sales/construct-reward-amount.ts (2 hunks)
  • apps/web/lib/integrations/slack/transform.ts (2 hunks)
  • apps/web/lib/partners/create-partner-commission.ts (1 hunks)
  • apps/web/lib/partners/create-stripe-transfer.ts (3 hunks)
  • apps/web/lib/partners/get-bounty-reward-description.ts (1 hunks)
  • apps/web/lib/partners/get-discoverability-requirements.ts (1 hunks)
  • apps/web/lib/stripe/create-payment-intent.ts (1 hunks)
  • apps/web/scripts/stripe/fix-processed-payouts.ts (2 hunks)
  • apps/web/ui/analytics/events/events-table.tsx (1 hunks)
  • apps/web/ui/analytics/use-analytics-filters.tsx (1 hunks)
  • apps/web/ui/customers/customer-details-column.tsx (1 hunks)
  • apps/web/ui/customers/customer-partner-earnings-table.tsx (1 hunks)
  • apps/web/ui/customers/customer-sales-table.tsx (2 hunks)
  • apps/web/ui/customers/customer-table/customer-table.tsx (1 hunks)
  • apps/web/ui/links/link-analytics-badge.tsx (2 hunks)
  • apps/web/ui/links/link-builder/link-partner-details.tsx (1 hunks)
  • apps/web/ui/modals/domain-auto-renewal-modal.tsx (1 hunks)
  • apps/web/ui/modals/import-rewardful-modal.tsx (2 hunks)
  • apps/web/ui/partners/bounties/bounty-performance.tsx (1 hunks)
  • apps/web/ui/partners/commission-status-badges.tsx (1 hunks)
  • apps/web/ui/partners/confirm-payouts-sheet.tsx (7 hunks)
  • apps/web/ui/partners/mark-commission-duplicate-modal.tsx (1 hunks)
  • apps/web/ui/partners/mark-commission-fraud-or-canceled-modal.tsx (1 hunks)
  • apps/web/ui/partners/overview/blocks/commissions-block.tsx (1 hunks)
  • apps/web/ui/partners/overview/blocks/countries-block.tsx (1 hunks)
  • apps/web/ui/partners/overview/blocks/links-block.tsx (1 hunks)
  • apps/web/ui/partners/overview/blocks/partners-block.tsx (1 hunks)
  • apps/web/ui/partners/overview/blocks/traffic-sources-block.tsx (1 hunks)
  • apps/web/ui/partners/partner-info-stats.tsx (2 hunks)
  • apps/web/ui/partners/payout-status-descriptions.ts (1 hunks)
  • apps/web/ui/partners/program-reward-modifiers-tooltip.tsx (1 hunks)
  • apps/web/ui/partners/program-stats-filter.tsx (1 hunks)
  • packages/email/src/templates/connect-payout-reminder.tsx (1 hunks)
  • packages/email/src/templates/domain-renewal-reminder.tsx (1 hunks)
  • packages/email/src/templates/new-commission-alert-partner.tsx (2 hunks)
  • packages/email/src/templates/new-sale-alert-program-owner.tsx (1 hunks)
  • packages/email/src/templates/partner-payout-confirmed.tsx (1 hunks)
  • packages/email/src/templates/partner-payout-failed.tsx (2 hunks)
  • packages/email/src/templates/partner-payout-processed.tsx (1 hunks)
  • packages/email/src/templates/partner-payout-withdrawal-completed.tsx (1 hunks)
  • packages/email/src/templates/partner-payout-withdrawal-initiated.tsx (3 hunks)
  • packages/email/src/templates/partner-paypal-payout-failed.tsx (1 hunks)
  • packages/email/src/templates/partner-program-summary.tsx (2 hunks)
  • packages/email/src/templates/program-payout-reminder.tsx (1 hunks)
  • packages/utils/src/functions/currency-formatter.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (14)
📓 Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/ui/partners/bounties/bounty-logic.tsx:88-96
Timestamp: 2025-08-26T15:03:05.381Z
Learning: In bounty forms, currency values are stored in cents in the backend but converted to dollars when loaded into forms, and converted back to cents when saved. The form logic works entirely with dollar amounts. Functions like generateBountyName that run during save logic receive cent values and need to divide by 100, but display logic within the form should format dollar values directly.
📚 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
  • packages/email/src/templates/new-sale-alert-program-owner.tsx
  • apps/web/ui/partners/overview/blocks/traffic-sources-block.tsx
  • apps/web/ui/links/link-builder/link-partner-details.tsx
  • packages/email/src/templates/partner-program-summary.tsx
  • apps/web/ui/partners/overview/blocks/links-block.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/links/page-client.tsx
  • apps/web/ui/partners/program-reward-modifiers-tooltip.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-table.tsx
  • apps/web/lib/partners/get-discoverability-requirements.ts
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsx
  • packages/email/src/templates/partner-payout-withdrawal-initiated.tsx
  • packages/email/src/templates/partner-payout-confirmed.tsx
  • packages/email/src/templates/program-payout-reminder.tsx
  • packages/email/src/templates/partner-payout-failed.tsx
  • packages/email/src/templates/new-commission-alert-partner.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-composite-chart.tsx
  • apps/web/ui/partners/overview/blocks/partners-block.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/partner-stats.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-stats.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx
  • packages/email/src/templates/partner-payout-processed.tsx
  • apps/web/ui/partners/partner-info-stats.tsx
  • apps/web/ui/links/link-analytics-badge.tsx
  • apps/web/app/api/callback/plain/partner/route.ts
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/transactional-campaign-logic.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/app/(ee)/admin.dub.co/(dashboard)/payouts/client.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-table.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/payouts/page-client.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/links/page-client.tsx
  • apps/web/ui/modals/import-rewardful-modal.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsx
  • apps/web/app/(ee)/admin.dub.co/(dashboard)/commissions/client.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/partner-stats.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-details-sheet.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/groups/groups-table.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)/settings/billing/invoices/page-client.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-stats.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/commissions/commission-table.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-submissions-table.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/ui/customers/customer-details-column.tsx
  • apps/web/ui/links/link-builder/link-partner-details.tsx
  • apps/web/ui/partners/program-reward-modifiers-tooltip.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsx
  • apps/web/ui/customers/customer-table/customer-table.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/partner-stats.tsx
  • apps/web/ui/partners/partner-info-stats.tsx
  • apps/web/ui/customers/customer-partner-earnings-table.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/transactional-campaign-logic.tsx
📚 Learning: 2025-06-25T21:20:59.837Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 0
File: :0-0
Timestamp: 2025-06-25T21:20:59.837Z
Learning: In the Dub codebase, payout limit validation uses a two-stage pattern: server actions perform quick sanity checks (payoutsUsage > payoutsLimit) for immediate user feedback, while the cron job (/cron/payouts) performs authoritative validation (payoutsUsage + payoutAmount > payoutsLimit) with actual calculated amounts before processing. This design provides fast user feedback while ensuring accurate limit enforcement at transaction time.

Applied to files:

  • apps/web/lib/api/errors.ts
📚 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/scripts/stripe/fix-processed-payouts.ts
  • apps/web/app/(ee)/partners.dub.co/invoices/[payoutId]/route.tsx
  • apps/web/app/(ee)/api/cron/payouts/balance-available/route.ts
📚 Learning: 2025-07-30T15:25:13.936Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2673
File: apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx:56-66
Timestamp: 2025-07-30T15:25:13.936Z
Learning: In apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx, the form schema uses partial condition objects to allow users to add empty/unconfigured condition fields without type errors, while submission validation uses strict schemas to ensure data integrity. This two-stage validation pattern improves UX by allowing progressive completion of complex forms.

Applied to files:

  • apps/web/ui/partners/program-reward-modifiers-tooltip.tsx
📚 Learning: 2025-09-12T17:31:10.548Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2833
File: apps/web/lib/actions/partners/approve-bounty-submission.ts:53-61
Timestamp: 2025-09-12T17:31:10.548Z
Learning: In approve-bounty-submission.ts, the logic `bounty.rewardAmount ?? rewardAmount` is intentional. Bounties with preset reward amounts should use those fixed amounts, and the rewardAmount override parameter is only used when bounty.rewardAmount is null/undefined (for custom reward bounties). This follows the design pattern where bounties are either "flat rate" (fixed amount) or "custom" (variable amount set during approval).

Applied to files:

  • apps/web/ui/partners/program-reward-modifiers-tooltip.tsx
  • apps/web/ui/partners/bounties/bounty-performance.tsx
  • apps/web/lib/api/sales/construct-reward-amount.ts
  • apps/web/lib/api/bounties/generate-performance-bounty-name.ts
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-stats.tsx
  • apps/web/lib/partners/get-bounty-reward-description.ts
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-submissions-table.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
📚 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:

  • apps/web/ui/modals/import-rewardful-modal.tsx
📚 Learning: 2025-09-12T17:36:09.551Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2833
File: apps/web/app/(ee)/api/bounties/[bountyId]/route.ts:100-107
Timestamp: 2025-09-12T17:36:09.551Z
Learning: For performance bounties in the bounty system, names cannot be provided by users - they are always auto-generated based on the performance condition using generateBountyName(). This ensures consistency and clarity about what the bounty actually measures. Any provided name should be overridden for performance bounties when a performanceCondition exists.

Applied to files:

  • apps/web/lib/api/bounties/generate-performance-bounty-name.ts
📚 Learning: 2025-08-26T15:03:05.381Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/ui/partners/bounties/bounty-logic.tsx:88-96
Timestamp: 2025-08-26T15:03:05.381Z
Learning: In bounty forms, currency values are stored in cents in the backend but converted to dollars when loaded into forms, and converted back to cents when saved. The form logic works entirely with dollar amounts. Functions like generateBountyName that run during save logic receive cent values and need to divide by 100, but display logic within the form should format dollar values directly.

Applied to files:

  • apps/web/lib/api/bounties/generate-performance-bounty-name.ts
  • apps/web/lib/partners/get-bounty-reward-description.ts
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-submissions-table.tsx
📚 Learning: 2025-08-26T14:32:33.851Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/lib/actions/partners/create-bounty-submission.ts:105-112
Timestamp: 2025-08-26T14:32:33.851Z
Learning: Non-performance bounties are required to have submissionRequirements. In create-bounty-submission.ts, it's appropriate to let the parsing fail if submissionRequirements is null for non-performance bounties, as this indicates a data integrity issue that should be caught.

Applied to files:

  • apps/web/lib/api/bounties/generate-performance-bounty-name.ts
📚 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)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx
🧬 Code graph analysis (87)
apps/web/ui/modals/domain-auto-renewal-modal.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/lib/stripe/create-payment-intent.ts (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/lib/partners/create-partner-commission.ts (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/(ee)/app.dub.co/embed/referrals/activity.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/ui/partners/commission-status-badges.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/lib/api/sales/construct-discount-amount.ts (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/(ee)/admin.dub.co/(dashboard)/payouts/client.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/ui/partners/program-stats-filter.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
packages/email/src/templates/new-sale-alert-program-owner.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-table.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/ui/partners/overview/blocks/traffic-sources-block.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/payouts/page-client.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/ui/customers/customer-details-column.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/lib/api/errors.ts (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/ui/links/link-builder/link-partner-details.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/ui/partners/mark-commission-fraud-or-canceled-modal.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/(ee)/api/cron/payouts/process/process-payouts.ts (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
packages/email/src/templates/partner-program-summary.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/scripts/stripe/fix-processed-payouts.ts (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/ui/partners/overview/blocks/links-block.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/links/page-client.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
packages/email/src/templates/domain-renewal-reminder.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/ui/partners/overview/blocks/countries-block.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/lib/integrations/slack/transform.ts (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/ui/partners/program-reward-modifiers-tooltip.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/ui/partners/confirm-payouts-sheet.tsx (2)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/lib/constants/payouts.ts (2)
  • FAST_ACH_FEE_CENTS (6-6)
  • DIRECT_DEBIT_PAYMENT_METHOD_TYPES (55-59)
packages/email/src/templates/connect-payout-reminder.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/ui/partners/overview/blocks/commissions-block.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/(ee)/partners.dub.co/invoices/[payoutId]/route.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/ui/analytics/use-analytics-filters.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/ui/modals/import-rewardful-modal.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/ui/partners/payout-status-descriptions.ts (2)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/lib/constants/payouts.ts (1)
  • MIN_WITHDRAWAL_AMOUNT_CENTS (11-11)
apps/web/ui/customers/customer-sales-table.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/(ee)/app.dub.co/embed/referrals/earnings-summary.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-table.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/ui/partners/mark-commission-duplicate-modal.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/lib/partners/get-discoverability-requirements.ts (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/analytics/analytics-partners-table.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
packages/email/src/templates/partner-payout-withdrawal-initiated.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/(ee)/admin.dub.co/(dashboard)/commissions/client.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/(ee)/app.dub.co/embed/referrals/earnings.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/ui/partners/bounties/bounty-performance.tsx (2)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/lib/api/workflows/utils.ts (1)
  • isCurrencyAttribute (6-7)
packages/email/src/templates/partner-payout-confirmed.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/partner-payout-invoice.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
packages/email/src/templates/program-payout-reminder.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/lib/api/sales/construct-reward-amount.ts (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
packages/email/src/templates/partner-payout-failed.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
packages/email/src/templates/new-commission-alert-partner.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/lib/api/bounties/generate-performance-bounty-name.ts (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-composite-chart.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/ui/partners/overview/blocks/partners-block.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/ui/customers/customer-table/customer-table.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/partner-stats.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-table.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/ui/analytics/events/events-table.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/domain-renewal-invoice.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/(ee)/app.dub.co/embed/referrals/leaderboard.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/(ee)/api/cron/payouts/balance-available/route.ts (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-stats.tsx (2)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/lib/constants/payouts.ts (2)
  • MIN_WITHDRAWAL_AMOUNT_CENTS (11-11)
  • BELOW_MIN_WITHDRAWAL_FEE_CENTS (12-12)
apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/(ee)/api/cron/trigger-withdrawal/route.ts (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-details-sheet.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
packages/email/src/templates/partner-payout-processed.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/commissions/create-commission-sheet.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/ui/partners/partner-info-stats.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/payouts-card.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/ui/customers/customer-partner-earnings-table.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/groups/groups-table.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/success/page-client.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/(ee)/admin.dub.co/(dashboard)/components/user-info.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/ui/links/link-analytics-badge.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/lib/partners/create-stripe-transfer.ts (2)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/lib/constants/payouts.ts (1)
  • MIN_WITHDRAWAL_AMOUNT_CENTS (11-11)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/invoices/page-client.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
packages/utils/src/functions/currency-formatter.ts (1)
packages/utils/src/functions/currency-zero-decimal.ts (1)
  • isZeroDecimalCurrency (1-19)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-stats.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/api/callback/plain/partner/route.ts (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/(ee)/app.dub.co/embed/referrals/links-list.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/lib/partners/get-bounty-reward-description.ts (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/commissions/commission-table.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
packages/email/src/templates/partner-paypal-payout-failed.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/(ee)/admin.dub.co/(dashboard)/revenue/client.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/transactional-campaign-logic.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/partner-payout-details-sheet.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-submissions-table.tsx (2)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
apps/web/lib/api/workflows/utils.ts (1)
  • isCurrencyAttribute (6-7)
packages/email/src/templates/partner-payout-withdrawal-completed.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

@steven-tey
Copy link
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 11, 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: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-table.tsx (1)

315-317: Inconsistent currency formatting.

Line 307 was updated to remove /100, but line 316 still divides minPayoutAmount by 100. Both values should be formatted consistently, as they're both monetary amounts stored in cents.

Apply this diff:

           content={
             <TooltipContent
-              title={`Your program's minimum payout amount is ${currencyFormatter(
-                minPayoutAmount / 100,
-              )}. This payout will be accrued and processed during the next payout period.`}
+              title={`Your program's minimum payout amount is ${currencyFormatter(minPayoutAmount)}. This payout will be accrued and processed during the next payout period.`}
               cta="Update minimum payout amount"
               href={`/${slug}/program/payouts?status=pending&sortBy=amount`}
               target="_blank"
apps/web/lib/integrations/slack/transform.ts (1)

158-192: Critical: Inconsistent currency formatting in saleCreatedTemplate.

Line 160 still uses manual currency conversion (sale.amount / 100).toFixed(2), which is inconsistent with:

  1. The PR's centralized currency formatting approach
  2. Other templates in this file (commissionCreatedTemplate line 317-318, payoutConfirmedTemplate line 481) that now use currencyFormatter
  3. The need to handle zero-decimal currencies correctly

This manual division will break zero-decimal currencies (e.g., JPY 10000 would display as "100.00 JPY" instead of "10,000 JPY").

Apply this diff to use currencyFormatter consistently:

 const saleCreatedTemplate = ({ data }: { data: SaleEventWebhookPayload }) => {
   const { customer, click, sale, link } = data;
-  const amountInDollars = (sale.amount / 100).toFixed(2);
+  const formattedAmount = currencyFormatter(sale.amount, { currency: sale.currency });
   const linkToSales = `${APP_DOMAIN}/events?event=sales&domain=${link.domain}&key=${link.key}`;

   return {
     blocks: [
       {
         type: "section",
         text: {
           type: "mrkdwn",
           text: "*New sale created* :moneybag:",
         },
         fields: [
           {
             type: "mrkdwn",
             text: `*Customer*\n${customer.name}`,
           },
           {
             type: "mrkdwn",
             text: `*Email*\n<mailto:${customer.email}|${customer.email}>`,
           },
         ],
       },
       {
         type: "section",
         fields: [
           {
             type: "mrkdwn",
             text: `*Country*\n${click.country}`,
           },
           {
             type: "mrkdwn",
             text: `*Amount*\n${formattedAmount}`,
-            text: `*Amount*\n${amountInDollars} ${sale.currency.toUpperCase()}`,
           },
         ],
       },
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-composite-chart.tsx (1)

226-226: Fix Y-axis and tooltip formatting: currencyFormatter expects cents, not dollars.

The currencyFormatter function expects valueInCents as its parameter. At line 226, the YAxis tickFormat receives values from the valueAccessor (line 95), which divides by 100, converting cents to dollars. Passing scaled dollars to currencyFormatter causes amounts to display 100x smaller. The same issue occurs at line 211 in the tooltip.

Line 194's tooltip is correct because it passes raw values without using valueAccessor. Align lines 211 and 226 with the pattern from overview-chart.tsx (which passes unscaled values directly).

♻️ Duplicate comments (2)
apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-stats.tsx (1)

229-230: Same currency handling concern as MIN_WITHDRAWAL_AMOUNT_CENTS.

This constant has the same potential issue with zero-decimal currencies. Additionally, it's used in arithmetic (line 234-235), so it's critical that both the fee and the payout amount are in the same currency and scale.

See the comment on lines 223-226 for verification steps.

packages/utils/src/functions/currency-zero-decimal.ts (1)

1-19: Lift the currency list into a shared Set and add a guard

We still recreate the list on every invocation and throw if currency is ever missing. Hoist the codes into a module-level Set and short-circuit on falsy input so the helper stays cheap and defensive.

+const ZERO_DECIMAL_CURRENCIES = new Set([
+  "BIF",
+  "CLP",
+  "DJF",
+  "GNF",
+  "JPY",
+  "KMF",
+  "KRW",
+  "MGA",
+  "PYG",
+  "RWF",
+  "UGX",
+  "VND",
+  "VUV",
+  "XAF",
+  "XOF",
+  "XPF",
+]);
+
-export const isZeroDecimalCurrency = (currency: string) =>
-  [
-    "BIF",
-    "CLP",
-    "DJF",
-    "GNF",
-    "JPY",
-    "KMF",
-    "KRW",
-    "MGA",
-    "PYG",
-    "RWF",
-    "UGX",
-    "VND",
-    "VUV",
-    "XAF",
-    "XOF",
-    "XPF",
-  ].includes(currency.toUpperCase());
+export const isZeroDecimalCurrency = (currency?: string | null) => {
+  if (!currency) {
+    return false;
+  }
+
+  return ZERO_DECIMAL_CURRENCIES.has(currency.toUpperCase());
+};
🧹 Nitpick comments (2)
apps/web/lib/api/sales/construct-discount-amount.ts (1)

9-11: Consider adding currency parameter support.

The removal of /100 is correct. However, if discounts can be in different currencies (especially zero-decimal ones), the function should accept and pass a currency parameter to currencyFormatter.

If DiscountProps includes currency, consider this enhancement:

 export const constructDiscountAmount = (
-  discount: Pick<DiscountProps, "amount" | "type">,
+  discount: Pick<DiscountProps, "amount" | "type" | "currency">,
 ) => {
   return discount.type === "percentage"
     ? `${discount.amount}%`
-    : currencyFormatter(discount.amount, {
+    : currencyFormatter(discount.amount, {
+        currency: discount.currency,
         trailingZeroDisplay: "stripIfInteger",
       });
 };

Verify if DiscountProps has a currency field:

#!/bin/bash
# Find DiscountProps definition
ast-grep --pattern $'type DiscountProps = {
  $$$
}'

ast-grep --pattern $'interface DiscountProps {
  $$$
}'
packages/utils/src/functions/currency-formatter.ts (1)

7-22: Add documentation for breaking API change.

This function signature now expects values in minor units (the smallest currency unit), which is a breaking change from the previous behavior. Consider adding JSDoc to clarify:

  • The expected input format (minor units: cents for USD, yen for JPY, dong for VND, etc.)
  • That the function automatically handles conversion based on currency type
  • Examples for both zero-decimal and decimal currencies

Add documentation:

+/**
+ * Formats a monetary value according to the specified currency.
+ * 
+ * @param valueInMinorUnits - The amount in minor units (e.g., cents for USD, yen for JPY, dong for VND)
+ * @param options - Formatting options including currency code
+ * @returns Formatted currency string
+ * 
+ * @example
+ * currencyFormatter(10000, { currency: "USD" }) // "$100.00"
+ * currencyFormatter(260689, { currency: "VND" }) // "₫260,689"
+ */
 export const currencyFormatter = (
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between faebf8f and 687b503.

📒 Files selected for processing (91)
  • apps/web/app/(ee)/admin.dub.co/(dashboard)/commissions/client.tsx (3 hunks)
  • apps/web/app/(ee)/admin.dub.co/(dashboard)/components/user-info.tsx (1 hunks)
  • apps/web/app/(ee)/admin.dub.co/(dashboard)/payouts/client.tsx (3 hunks)
  • apps/web/app/(ee)/admin.dub.co/(dashboard)/revenue/client.tsx (1 hunks)
  • apps/web/app/(ee)/api/cron/payouts/balance-available/route.ts (2 hunks)
  • apps/web/app/(ee)/api/cron/payouts/process/process-payouts.ts (1 hunks)
  • apps/web/app/(ee)/api/cron/trigger-withdrawal/route.ts (2 hunks)
  • apps/web/app/(ee)/app.dub.co/embed/referrals/activity.tsx (1 hunks)
  • apps/web/app/(ee)/app.dub.co/embed/referrals/earnings-summary.tsx (1 hunks)
  • apps/web/app/(ee)/app.dub.co/embed/referrals/earnings.tsx (1 hunks)
  • apps/web/app/(ee)/app.dub.co/embed/referrals/leaderboard.tsx (1 hunks)
  • apps/web/app/(ee)/app.dub.co/embed/referrals/links-list.tsx (1 hunks)
  • apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/domain-renewal-invoice.tsx (3 hunks)
  • apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/partner-payout-invoice.tsx (4 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/partner-payout-details-sheet.tsx (2 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-stats.tsx (3 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-table.tsx (2 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-composite-chart.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-table.tsx (2 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/payouts-card.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/invoices/[payoutId]/route.tsx (1 hunks)
  • apps/web/app/api/callback/plain/partner/route.ts (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/analytics/analytics-partners-table.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-submissions-table.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/transactional-campaign-logic.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/commissions/commission-table.tsx (2 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/commissions/create-commission-sheet.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/groups/groups-table.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/links/page-client.tsx (2 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/partner-stats.tsx (3 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/payouts/page-client.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-details-sheet.tsx (2 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-stats.tsx (4 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-table.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/success/page-client.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/invoices/page-client.tsx (2 hunks)
  • apps/web/lib/analytics/convert-currency.ts (3 hunks)
  • apps/web/lib/api/bounties/generate-performance-bounty-name.ts (1 hunks)
  • apps/web/lib/api/errors.ts (1 hunks)
  • apps/web/lib/api/sales/construct-discount-amount.ts (1 hunks)
  • apps/web/lib/api/sales/construct-reward-amount.ts (2 hunks)
  • apps/web/lib/integrations/slack/transform.ts (2 hunks)
  • apps/web/lib/partners/create-partner-commission.ts (1 hunks)
  • apps/web/lib/partners/create-stripe-transfer.ts (3 hunks)
  • apps/web/lib/partners/get-bounty-reward-description.ts (1 hunks)
  • apps/web/lib/partners/get-discoverability-requirements.ts (1 hunks)
  • apps/web/lib/stripe/create-payment-intent.ts (1 hunks)
  • apps/web/lib/webhook/sample-events/partner-enrolled.json (1 hunks)
  • apps/web/scripts/stripe/fix-processed-payouts.ts (2 hunks)
  • apps/web/ui/analytics/events/events-table.tsx (1 hunks)
  • apps/web/ui/analytics/use-analytics-filters.tsx (1 hunks)
  • apps/web/ui/customers/customer-details-column.tsx (1 hunks)
  • apps/web/ui/customers/customer-partner-earnings-table.tsx (1 hunks)
  • apps/web/ui/customers/customer-sales-table.tsx (2 hunks)
  • apps/web/ui/customers/customer-table/customer-table.tsx (1 hunks)
  • apps/web/ui/links/link-analytics-badge.tsx (2 hunks)
  • apps/web/ui/links/link-builder/link-partner-details.tsx (1 hunks)
  • apps/web/ui/modals/domain-auto-renewal-modal.tsx (1 hunks)
  • apps/web/ui/modals/import-rewardful-modal.tsx (2 hunks)
  • apps/web/ui/partners/bounties/bounty-performance.tsx (1 hunks)
  • apps/web/ui/partners/commission-status-badges.tsx (1 hunks)
  • apps/web/ui/partners/confirm-payouts-sheet.tsx (7 hunks)
  • apps/web/ui/partners/mark-commission-duplicate-modal.tsx (1 hunks)
  • apps/web/ui/partners/mark-commission-fraud-or-canceled-modal.tsx (1 hunks)
  • apps/web/ui/partners/overview/blocks/commissions-block.tsx (1 hunks)
  • apps/web/ui/partners/overview/blocks/countries-block.tsx (1 hunks)
  • apps/web/ui/partners/overview/blocks/links-block.tsx (1 hunks)
  • apps/web/ui/partners/overview/blocks/partners-block.tsx (1 hunks)
  • apps/web/ui/partners/overview/blocks/traffic-sources-block.tsx (1 hunks)
  • apps/web/ui/partners/partner-info-stats.tsx (2 hunks)
  • apps/web/ui/partners/payout-status-descriptions.ts (1 hunks)
  • apps/web/ui/partners/program-reward-modifiers-tooltip.tsx (1 hunks)
  • apps/web/ui/partners/program-stats-filter.tsx (1 hunks)
  • packages/email/src/templates/connect-payout-reminder.tsx (1 hunks)
  • packages/email/src/templates/domain-renewal-reminder.tsx (1 hunks)
  • packages/email/src/templates/new-commission-alert-partner.tsx (2 hunks)
  • packages/email/src/templates/new-sale-alert-program-owner.tsx (1 hunks)
  • packages/email/src/templates/partner-payout-confirmed.tsx (1 hunks)
  • packages/email/src/templates/partner-payout-failed.tsx (2 hunks)
  • packages/email/src/templates/partner-payout-processed.tsx (1 hunks)
  • packages/email/src/templates/partner-payout-withdrawal-completed.tsx (1 hunks)
  • packages/email/src/templates/partner-payout-withdrawal-initiated.tsx (3 hunks)
  • packages/email/src/templates/partner-paypal-payout-failed.tsx (1 hunks)
  • packages/email/src/templates/partner-program-summary.tsx (2 hunks)
  • packages/email/src/templates/program-payout-reminder.tsx (1 hunks)
  • packages/utils/src/functions/currency-formatter.ts (1 hunks)
  • packages/utils/src/functions/currency-zero-decimal.ts (1 hunks)
  • packages/utils/src/functions/index.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (14)
📚 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:

  • packages/email/src/templates/partner-program-summary.tsx
  • apps/web/ui/partners/commission-status-badges.tsx
  • apps/web/ui/links/link-analytics-badge.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx
  • apps/web/lib/partners/get-discoverability-requirements.ts
  • apps/web/ui/links/link-builder/link-partner-details.tsx
  • packages/email/src/templates/new-commission-alert-partner.tsx
  • apps/web/ui/partners/mark-commission-fraud-or-canceled-modal.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-stats.tsx
  • apps/web/ui/partners/overview/blocks/partners-block.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/transactional-campaign-logic.tsx
  • packages/email/src/templates/new-sale-alert-program-owner.tsx
  • apps/web/app/api/callback/plain/partner/route.ts
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/links/page-client.tsx
  • apps/web/ui/partners/overview/blocks/traffic-sources-block.tsx
  • packages/email/src/templates/partner-payout-processed.tsx
  • packages/email/src/templates/program-payout-reminder.tsx
  • apps/web/ui/partners/partner-info-stats.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/partner-stats.tsx
  • packages/email/src/templates/partner-payout-confirmed.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-table.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-composite-chart.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsx
  • packages/email/src/templates/partner-payout-failed.tsx
  • apps/web/ui/partners/overview/blocks/links-block.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/ui/customers/customer-partner-earnings-table.tsx
  • apps/web/ui/links/link-builder/link-partner-details.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/transactional-campaign-logic.tsx
  • packages/email/src/templates/new-sale-alert-program-owner.tsx
  • apps/web/ui/partners/program-reward-modifiers-tooltip.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-submissions-table.tsx
  • apps/web/lib/api/sales/construct-reward-amount.ts
  • apps/web/ui/customers/customer-details-column.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/partner-stats.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.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)/api/cron/payouts/balance-available/route.ts
  • apps/web/lib/partners/create-stripe-transfer.ts
  • apps/web/app/(ee)/partners.dub.co/invoices/[payoutId]/route.tsx
  • apps/web/scripts/stripe/fix-processed-payouts.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/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx
  • apps/web/app/(ee)/app.dub.co/embed/referrals/activity.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/commissions/create-commission-sheet.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-stats.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/commissions/commission-table.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/invoices/page-client.tsx
  • apps/web/app/(ee)/admin.dub.co/(dashboard)/commissions/client.tsx
  • apps/web/ui/modals/import-rewardful-modal.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-submissions-table.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/links/page-client.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-details-sheet.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/success/page-client.tsx
  • apps/web/app/(ee)/admin.dub.co/(dashboard)/payouts/client.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/partner-stats.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/payouts/page-client.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-table.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/groups/groups-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)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx
📚 Learning: 2025-09-12T17:31:10.548Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2833
File: apps/web/lib/actions/partners/approve-bounty-submission.ts:53-61
Timestamp: 2025-09-12T17:31:10.548Z
Learning: In approve-bounty-submission.ts, the logic `bounty.rewardAmount ?? rewardAmount` is intentional. Bounties with preset reward amounts should use those fixed amounts, and the rewardAmount override parameter is only used when bounty.rewardAmount is null/undefined (for custom reward bounties). This follows the design pattern where bounties are either "flat rate" (fixed amount) or "custom" (variable amount set during approval).

Applied to files:

  • apps/web/lib/partners/get-bounty-reward-description.ts
  • apps/web/lib/api/sales/construct-discount-amount.ts
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-stats.tsx
  • apps/web/ui/partners/program-reward-modifiers-tooltip.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-submissions-table.tsx
  • apps/web/lib/api/sales/construct-reward-amount.ts
  • apps/web/ui/partners/bounties/bounty-performance.tsx
  • apps/web/lib/api/bounties/generate-performance-bounty-name.ts
📚 Learning: 2025-08-26T15:03:05.381Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/ui/partners/bounties/bounty-logic.tsx:88-96
Timestamp: 2025-08-26T15:03:05.381Z
Learning: In bounty forms, currency values are stored in cents in the backend but converted to dollars when loaded into forms, and converted back to cents when saved. The form logic works entirely with dollar amounts. Functions like generateBountyName that run during save logic receive cent values and need to divide by 100, but display logic within the form should format dollar values directly.

Applied to files:

  • apps/web/lib/partners/get-bounty-reward-description.ts
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-submissions-table.tsx
  • apps/web/lib/api/sales/construct-reward-amount.ts
  • apps/web/ui/partners/bounties/bounty-performance.tsx
  • apps/web/lib/api/bounties/generate-performance-bounty-name.ts
📚 Learning: 2025-07-30T15:25:13.936Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2673
File: apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx:56-66
Timestamp: 2025-07-30T15:25:13.936Z
Learning: In apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx, the form schema uses partial condition objects to allow users to add empty/unconfigured condition fields without type errors, while submission validation uses strict schemas to ensure data integrity. This two-stage validation pattern improves UX by allowing progressive completion of complex forms.

Applied to files:

  • apps/web/ui/partners/program-reward-modifiers-tooltip.tsx
📚 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:

  • apps/web/ui/modals/import-rewardful-modal.tsx
📚 Learning: 2025-08-26T14:32:33.851Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/lib/actions/partners/create-bounty-submission.ts:105-112
Timestamp: 2025-08-26T14:32:33.851Z
Learning: Non-performance bounties are required to have submissionRequirements. In create-bounty-submission.ts, it's appropriate to let the parsing fail if submissionRequirements is null for non-performance bounties, as this indicates a data integrity issue that should be caught.

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-submissions-table.tsx
  • apps/web/lib/api/bounties/generate-performance-bounty-name.ts
📚 Learning: 2025-07-09T20:52:56.592Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2614
File: apps/web/ui/partners/design/previews/lander-preview.tsx:181-181
Timestamp: 2025-07-09T20:52:56.592Z
Learning: In apps/web/ui/partners/design/previews/lander-preview.tsx, the ellipsis wave animation delay calculation `3 - i * -0.15` is intentionally designed to create negative delays that offset each dot's animation cycle. This pattern works correctly for the intended ellipsis effect and should not be changed to positive incremental delays.

Applied to files:

  • apps/web/ui/customers/customer-details-column.tsx
📚 Learning: 2025-06-25T21:20:59.837Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 0
File: :0-0
Timestamp: 2025-06-25T21:20:59.837Z
Learning: In the Dub codebase, payout limit validation uses a two-stage pattern: server actions perform quick sanity checks (payoutsUsage > payoutsLimit) for immediate user feedback, while the cron job (/cron/payouts) performs authoritative validation (payoutsUsage + payoutAmount > payoutsLimit) with actual calculated amounts before processing. This design provides fast user feedback while ensuring accurate limit enforcement at transaction time.

Applied to files:

  • apps/web/lib/api/errors.ts
📚 Learning: 2025-09-12T17:36:09.551Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2833
File: apps/web/app/(ee)/api/bounties/[bountyId]/route.ts:100-107
Timestamp: 2025-09-12T17:36:09.551Z
Learning: For performance bounties in the bounty system, names cannot be provided by users - they are always auto-generated based on the performance condition using generateBountyName(). This ensures consistency and clarity about what the bounty actually measures. Any provided name should be overridden for performance bounties when a performanceCondition exists.

Applied to files:

  • apps/web/lib/api/bounties/generate-performance-bounty-name.ts
📚 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

@steven-tey steven-tey merged commit a3a21c8 into main Nov 11, 2025
8 of 9 checks passed
@steven-tey steven-tey deleted the handle-zero-decimal-currency branch November 11, 2025 19:41
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