-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Handle zero-decimal currencies in currencyFormatter #3090
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughCentralizes 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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 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
valueInCentsis misleading for zero-decimal currencies, which don't have fractional units. Consider using a more generic name likevalueInSmallestUnitoramountInMinorUnitsto 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
📒 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
valuetovalueInCentsis 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:91—currencyFormatter(value)missing divisionapps/web/ui/partners/rewards/rewards-logic.tsx:264—currencyFormatter(Number(value), {...})missing divisionThe rewards form stores
amountInCents(line 665), yet passes it tocurrencyFormatterwithout dividing by 100, creating an inconsistency with the rest of the codebase and the new parameter contract.Required actions:
- Verify all callers now pass values in the smallest currency unit (cents for USD, whole units for zero-decimal currencies)
- Fix the two edge cases in bounty-logic and rewards-logic to divide by 100 before formatting
- Add a comment or test documenting that
valueInCentsmust 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.
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 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
currencyFormatternow expects values in cents and handles the division internally. This causes a cascading issue where lines 204 and 226 pass dollar values tocurrencyFormatter, 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: Passsale.currencyto formatter and remove thetrailingZeroDisplayoverride to enable proper zero-decimal currency handling.The
currencyFormatterfunction accepts acurrencyoption parameter and automatically setstrailingZeroDisplaybased on whether the currency is zero-decimal. The current code hardcodes both "USD" andtrailingZeroDisplay: "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
minPayoutAmountby 100, while line 307 correctly passespayout.amountdirectly. 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.tsxCritical bug found:
./apps/web/ui/layout/sidebar/payout-stats.tsx(lines 63–68 and 79–84) divides amounts by 100 before passing tocurrencyFormatter, 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
/ 100division 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
currencyFormatternow 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
/100division to align with the newcurrencyFormatterAPI.For better maintainability and consistency with the hardcoded
currency: "usd"on line 106, consider passing the currency explicitly to allcurrencyFormattercalls: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
currencyFormatterdefaults 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
currencyFormatteris 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 useNumberFlow, 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
📒 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.tsxpackages/email/src/templates/new-sale-alert-program-owner.tsxapps/web/ui/partners/overview/blocks/traffic-sources-block.tsxapps/web/ui/links/link-builder/link-partner-details.tsxpackages/email/src/templates/partner-program-summary.tsxapps/web/ui/partners/overview/blocks/links-block.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/links/page-client.tsxapps/web/ui/partners/program-reward-modifiers-tooltip.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-table.tsxapps/web/lib/partners/get-discoverability-requirements.tsapps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsxpackages/email/src/templates/partner-payout-withdrawal-initiated.tsxpackages/email/src/templates/partner-payout-confirmed.tsxpackages/email/src/templates/program-payout-reminder.tsxpackages/email/src/templates/partner-payout-failed.tsxpackages/email/src/templates/new-commission-alert-partner.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-composite-chart.tsxapps/web/ui/partners/overview/blocks/partners-block.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/partner-stats.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-stats.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsxpackages/email/src/templates/partner-payout-processed.tsxapps/web/ui/partners/partner-info-stats.tsxapps/web/ui/links/link-analytics-badge.tsxapps/web/app/api/callback/plain/partner/route.tsapps/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.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-table.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/payouts/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/links/page-client.tsxapps/web/ui/modals/import-rewardful-modal.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsxapps/web/app/(ee)/admin.dub.co/(dashboard)/commissions/client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/partner-stats.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-details-sheet.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/groups/groups-table.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/success/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/invoices/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-stats.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/commissions/commission-table.tsxapps/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.tsxapps/web/ui/links/link-builder/link-partner-details.tsxapps/web/ui/partners/program-reward-modifiers-tooltip.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsxapps/web/ui/customers/customer-table/customer-table.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/partner-stats.tsxapps/web/ui/partners/partner-info-stats.tsxapps/web/ui/customers/customer-partner-earnings-table.tsxapps/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.tsapps/web/app/(ee)/partners.dub.co/invoices/[payoutId]/route.tsxapps/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.tsxapps/web/ui/partners/bounties/bounty-performance.tsxapps/web/lib/api/sales/construct-reward-amount.tsapps/web/lib/api/bounties/generate-performance-bounty-name.tsapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-stats.tsxapps/web/lib/partners/get-bounty-reward-description.tsapps/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.tsapps/web/lib/partners/get-bounty-reward-description.tsapps/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
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 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 dividesminPayoutAmountby 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:
- The PR's centralized currency formatting approach
- Other templates in this file (
commissionCreatedTemplateline 317-318,payoutConfirmedTemplateline 481) that now usecurrencyFormatter- 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
currencyFormatterconsistently: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
currencyFormatterfunction expectsvalueInCentsas its parameter. At line 226, the YAxistickFormatreceives values from thevalueAccessor(line 95), which divides by 100, converting cents to dollars. Passing scaled dollars tocurrencyFormattercauses 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 fromoverview-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 guardWe still recreate the list on every invocation and throw if
currencyis ever missing. Hoist the codes into a module-levelSetand 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
/100is correct. However, if discounts can be in different currencies (especially zero-decimal ones), the function should accept and pass a currency parameter tocurrencyFormatter.If
DiscountPropsincludes 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
📒 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.tsxapps/web/ui/partners/commission-status-badges.tsxapps/web/ui/links/link-analytics-badge.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsxapps/web/lib/partners/get-discoverability-requirements.tsapps/web/ui/links/link-builder/link-partner-details.tsxpackages/email/src/templates/new-commission-alert-partner.tsxapps/web/ui/partners/mark-commission-fraud-or-canceled-modal.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-stats.tsxapps/web/ui/partners/overview/blocks/partners-block.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/transactional-campaign-logic.tsxpackages/email/src/templates/new-sale-alert-program-owner.tsxapps/web/app/api/callback/plain/partner/route.tsapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/links/page-client.tsxapps/web/ui/partners/overview/blocks/traffic-sources-block.tsxpackages/email/src/templates/partner-payout-processed.tsxpackages/email/src/templates/program-payout-reminder.tsxapps/web/ui/partners/partner-info-stats.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/partner-stats.tsxpackages/email/src/templates/partner-payout-confirmed.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-table.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-composite-chart.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsxpackages/email/src/templates/partner-payout-failed.tsxapps/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.tsxapps/web/ui/links/link-builder/link-partner-details.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/transactional-campaign-logic.tsxpackages/email/src/templates/new-sale-alert-program-owner.tsxapps/web/ui/partners/program-reward-modifiers-tooltip.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-submissions-table.tsxapps/web/lib/api/sales/construct-reward-amount.tsapps/web/ui/customers/customer-details-column.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/partner-stats.tsxapps/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.tsapps/web/lib/partners/create-stripe-transfer.tsapps/web/app/(ee)/partners.dub.co/invoices/[payoutId]/route.tsxapps/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.tsxapps/web/app/(ee)/app.dub.co/embed/referrals/activity.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/commissions/create-commission-sheet.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-stats.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/commissions/commission-table.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/invoices/page-client.tsxapps/web/app/(ee)/admin.dub.co/(dashboard)/commissions/client.tsxapps/web/ui/modals/import-rewardful-modal.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-submissions-table.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/links/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-details-sheet.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/success/page-client.tsxapps/web/app/(ee)/admin.dub.co/(dashboard)/payouts/client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/partner-stats.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/payouts/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-table.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsxapps/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.tsapps/web/lib/api/sales/construct-discount-amount.tsapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-stats.tsxapps/web/ui/partners/program-reward-modifiers-tooltip.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-submissions-table.tsxapps/web/lib/api/sales/construct-reward-amount.tsapps/web/ui/partners/bounties/bounty-performance.tsxapps/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.tsapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-submissions-table.tsxapps/web/lib/api/sales/construct-reward-amount.tsapps/web/ui/partners/bounties/bounty-performance.tsxapps/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.tsxapps/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
Summary by CodeRabbit
Refactor
UI / UX