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

Skip to content

Conversation

@steven-tey
Copy link
Collaborator

@steven-tey steven-tey commented Sep 1, 2025

Summary by CodeRabbit

  • Style

    • Standardized currency display across the app and emails. Amounts now use a unified formatter with default precision, improving consistency in dashboards, tables, charts, invoices, payouts, and embeds.
    • Whole-number amounts may no longer show trailing “.00” in select views for cleaner readability.
  • Refactor

    • Unified currency formatting logic throughout the product and removed scattered custom formatting, ensuring consistent presentation in partner views, analytics, invoices, and notifications.

@vercel
Copy link
Contributor

vercel bot commented Sep 1, 2025

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

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Sep 1, 2025 10:30pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 1, 2025

Walkthrough

Standardized currency display across the app by simplifying currencyFormatter usage, often removing explicit fraction options. Updated the core currency-formatter utility’s signature and defaults. Minor signature tweaks in Slack templates. No data flow or structural changes; updates are presentation-focused.

Changes

Cohort / File(s) Summary
Core utility
packages/utils/src/functions/currency-formatter.ts
Introduced CurrencyFormatterOptions with optional trailingZeroDisplay; removed currency param; set defaults to USD with 2 fraction digits and trailingZeroDisplay: "auto"; updated function signature accordingly.
Admin dashboards
apps/web/app/(ee)/admin.dub.co/(dashboard)/commissions/client.tsx, .../payouts/client.tsx, .../revenue/client.tsx
Removed fixed two-decimal options from currencyFormatter in table cells; rely on defaults.
Cron/API routes
apps/web/app/(ee)/api/cron/payouts/balance-available/route.ts, apps/web/app/api/callback/plain/partner/route.ts
Simplified currencyFormatter option objects in logs/metadata; no logic changes.
Embeds
apps/web/app/(ee)/app.dub.co/embed/referrals/earnings-summary.tsx, .../earnings.tsx, .../leaderboard.tsx
Dropped explicit min/max fraction digits in currency rendering.
Invoices (web app)
apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/domain-renewal-invoice.tsx, .../partner-payout-invoice.tsx
Switched to default currencyFormatter usage for amounts/fees/totals; adjusted one path to pass currency via options; display-only changes.
Partners dashboards — payouts
apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-details-sheet.tsx, .../payout-stats.tsx, .../payout-table.tsx
Removed enforced two-decimal options for amounts/earnings; rely on defaults.
Partners dashboards — programs (enrolled)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/*
Simplified currency formatting in charts, tables, tooltips; some tooltips now use defaults.
Program analytics
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/analytics/*
Tooltips and column accessors now format currency via default currencyFormatter.
Program bounties
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/*
Currency fields use default formatter; range/target displays unchanged.
Program commissions/groups/partners/overview
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/*
Removed fixed two-decimal options from Amount/Commission/Revenue-related displays across tables and tooltips.
Program payouts
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/*
Amounts/totals formatted with defaults across details, stats, tables.
Billing invoices page
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/invoices/page-client.tsx
Simplified total amount formatting to defaults.
Sales reward amount logic
apps/web/lib/api/sales/construct-reward-amount.ts
Removed local formatCurrency; now directly uses currencyFormatter.
Slack integration
apps/web/lib/integrations/slack/transform.ts
Currency displays use defaults; updated bountyCreatedTemplate and bountyUpdatedTemplate parameter shapes to an ignored empty-object form.
Customers UI
apps/web/ui/customers/*
Tables/columns now use default currency formatting; some switched to trailingZeroDisplay: "stripIfInteger".
Analytics events
apps/web/ui/analytics/events/events-table.tsx
Set trailingZeroDisplay: "stripIfInteger" for Amount column.
Links UI
apps/web/ui/links/link-analytics-badge.tsx, .../link-builder/link-partner-details.tsx
Adopted defaults; for sales displays, used trailingZeroDisplay: "stripIfInteger"; removed local formatter helper.
Partners overview blocks
apps/web/ui/partners/overview/blocks/*
Revenue/earnings/saleAmount fields now use default currency formatting across lists/blocks.
Partner details & payouts sheets
apps/web/ui/partners/partner-details-sheet.tsx, .../payout-invoice-sheet.tsx
Moved to default currency formatting; selectively apply trailingZeroDisplay: "stripIfInteger" for some revenue fields.
Partners rewards logic
apps/web/ui/partners/rewards/rewards-logic.tsx, .../program-reward-modifiers-tooltip.tsx
Simplified currency path to use trailingZeroDisplay: "stripIfInteger"; removed conditional digit logic.
Invoices route
apps/web/app/(ee)/partners.dub.co/invoices/[payoutId]/route.tsx
Simplified payout amount formatting to defaults.
Sidebar payout stats
apps/web/ui/layout/sidebar/payout-stats.tsx
Totals use default formatting; computation unchanged.
Email templates
packages/email/src/templates/*
Replaced fixed two-decimal options with default currencyFormatter for amounts, earnings, totals; no template logic changes.

Sequence Diagram(s)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • TWilson023

Poem

A hop, a bop, I tweak the cents,
Two tails of zeros jump the fence.
USD’s the burrow now,
Format clean—take a bow.
With every nibble, numbers gleam,
Carrots tall in the ledger stream. 🥕

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch currency-formatter

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

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/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-table.tsx (1)

152-159: Treat 0 amounts as $0.00, not "-"

Truthiness check hides legitimate zero values. Prefer a nullish check so 0 renders as $0.00.

-        cell: ({ row }) =>
-          row.original.amount
-            ? currencyFormatter(row.original.amount / 100)
-            : "-",
+        cell: ({ row }) =>
+          row.original.amount != null
+            ? currencyFormatter(row.original.amount / 100)
+            : "-",
apps/web/lib/integrations/slack/transform.ts (2)

285-292: Fix lint error: unexpected empty object pattern

Biome flags the empty destructuring. Accept but ignore the param using an underscore.

-const bountyCreatedTemplate = ({} /* data */ : { data: unknown }) => ({
+const bountyCreatedTemplate = (_: { data: unknown }) => ({

294-301: Fix lint error: unexpected empty object pattern

Same issue as above.

-const bountyUpdatedTemplate = ({} /* data */ : { data: unknown }) => ({
+const bountyUpdatedTemplate = (_: { data: unknown }) => ({
🧹 Nitpick comments (27)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/page-client.tsx (1)

428-431: Match tooltip with header number formatting (strip .00 for integers).

Top “Earnings” uses NumberFlow with trailingZeroDisplay: "stripIfInteger"; align the tooltip for consistency.

-                  {currency
-                    ? currencyFormatter(d.values.main)
+                  {currency
+                    ? currencyFormatter(d.values.main, {
+                        trailingZeroDisplay: "stripIfInteger",
+                      })
                     : nFormatter(d.values.main)}
apps/web/ui/partners/overview/blocks/partners-block.tsx (1)

78-78: Optional: strip .00 in compact list rows.

Slightly tighter look for integer amounts:

-              <span>{currencyFormatter(partner.saleAmount / 100)}</span>
+              <span>
+                {currencyFormatter(partner.saleAmount / 100, {
+                  trailingZeroDisplay: "stripIfInteger",
+                })}
+              </span>
packages/email/src/templates/partner-payout-failed.tsx (1)

44-44: Confirm currency and format failureFee consistently

  • This assumes USD defaults. If partner payouts can be non-USD, add currency to props and pass it to currencyFormatter.
  • Also format the failure fee with currencyFormatter for consistency (Line 79 currently interpolates a raw number).

Example (outside this hunk):

<span className="font-semibold text-neutral-800">
  {currencyFormatter((payout.failureFee ?? 0) / 10 ** 2)}
</span>
packages/email/src/templates/partner-paypal-payout-failed.tsx (1)

43-43: Pass explicit currency if PayPal payouts aren’t always USD

If payouts may be non-USD, surface currency on the template props and pass it to currencyFormatter to avoid USD-only rendering.

apps/web/ui/partners/overview/blocks/countries-block.tsx (1)

83-83: Verify currency semantics for saleAmount

This assumes USD (defaults) and 2-decimal scaling. If analytics can be multi-currency, pass currency and convert from minor units by exponent.

apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx (2)

256-269: Prefer raw numbers for accessorFn; format in cell (future-proof sorting/aggregation)

Returning formatted strings from accessorFn can hinder numeric ops later. Keep the accessor numeric and format in cell.

-        {
-          id: "saleAmount",
-          header: "Revenue",
-          accessorFn: (d) => currencyFormatter(d.saleAmount / 100),
-        },
+        {
+          id: "saleAmount",
+          header: "Revenue",
+          accessorFn: (d) => d.saleAmount,
+          cell: ({ row }) => currencyFormatter(row.original.saleAmount / 100),
+        },
         {
           id: "totalCommissions",
           header: "Commissions",
-          accessorFn: (d) => currencyFormatter(d.totalCommissions / 100),
+          accessorFn: (d) => d.totalCommissions,
+          cell: ({ row }) =>
+            currencyFormatter(row.original.totalCommissions / 100),
         },
         {
           id: "netRevenue",
           header: "Net Revenue",
-          accessorFn: (d) => currencyFormatter(d.netRevenue / 100),
+          accessorFn: (d) => d.netRevenue,
+          cell: ({ row }) => currencyFormatter(row.original.netRevenue / 100),
         },

301-310: Sortable column id mismatch: use "totalCommissions" not "commissions"

This prevents sorting that column.

       sortableColumns: [
         "createdAt",
         "clicks",
         "leads",
         "conversions",
         "sales",
         "saleAmount",
-        "commissions",
+        "totalCommissions",
         "netRevenue",
       ],
apps/web/app/(ee)/admin.dub.co/(dashboard)/commissions/client.tsx (1)

127-128: Prefer getValue over row.original in cells.

Keeps display concerns decoupled from row shape and leverages the column accessor.

-        cell: ({ row }) => currencyFormatter(row.original.commissions / 100),
+        cell: ({ getValue }) => currencyFormatter(Number(getValue()) / 100),

Note: If you want display parity with the NumberFlow total (which strips .00 for integers), consider passing { trailingZeroDisplay: "stripIfInteger" } to currencyFormatter in this view.

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

82-82: Avoid hard-coded "$0.00"; defer zero/negative handling to the formatter.

Using a literal breaks consistency if defaults change (currency, locale) and misreports negatives. Always call the formatter for 0/negatives.

-                  <>{amount > 0 ? currencyFormatter(amount / 100) : "$0.00"}</>
+                  {currencyFormatter(((amount ?? 0)) / 100)}

26-33: Loading vs. error UX is inconsistent.

isLoading collapses errors into the skeleton state, making the inner error ? "-" : ... branch effectively dead on error. Consider keeping error distinct.

-  const isLoading = amount === undefined || error;
+  const isLoading = amount === undefined;

This will render "-" on errors as intended.

Also applies to: 36-36, 79-86

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

143-147: Confirm zero-reward display semantics.

commission?.earnings ? ... : "-" hides a $0.00 reward with "-". If you want explicit zeros for clarity, format 0 instead.

-                  value: commission?.earnings
-                    ? currencyFormatter(commission.earnings / 100)
-                    : "-",
+                  value:
+                    commission?.earnings !== undefined
+                      ? currencyFormatter((commission?.earnings ?? 0) / 100)
+                      : "-",
apps/web/ui/links/link-analytics-badge.tsx (2)

107-111: Remove the ts-ignore by widening the formatter’s option type.

Avoid suppressing TS for trailingZeroDisplay. Update the shared CurrencyFormatterOptions to include it, then drop the ignore here.

Outside this file (in @dub/utils):

// packages/utils/src/functions/currency-formatter.ts
export type CurrencyFormatterOptions = Intl.NumberFormatOptions & {
  trailingZeroDisplay?: "auto" | "stripIfInteger";
};

After that, this call compiles without the ts-ignore.


171-176: Duplicate config — factor into a tiny helper.

You pass the same { trailingZeroDisplay: "stripIfInteger" } twice. A local formatSales helper improves readability and avoids drift.

+  const formatSales = (cents: number) =>
+    currencyFormatter(cents / 100, { trailingZeroDisplay: "stripIfInteger" });

...
-                    ? currencyFormatter(value / 100, {
-                        trailingZeroDisplay: "stripIfInteger",
-                      })
+                    ? formatSales(value)
...
-                      ? currencyFormatter(value / 100, {
-                          // @ts-ignore – trailingZeroDisplay is a valid option but TS is outdated
-                          trailingZeroDisplay: "stripIfInteger",
-                        })
+                      ? formatSales(value)
apps/web/app/(ee)/admin.dub.co/(dashboard)/payouts/client.tsx (1)

155-167: Optional: match NumberFlow’s no-decimal integers

Top totals use trailingZeroDisplay: "stripIfInteger" (Lines 219-223), but table cells always show two decimals via the default formatter. If you want visual consistency in this view, pass the same option here.

-        cell: ({ row }) => currencyFormatter(row.original.amount / 100),
+        cell: ({ row }) =>
+          currencyFormatter(row.original.amount / 100, {
+            trailingZeroDisplay: "stripIfInteger",
+          }),
...
-        cell: ({ row }) => currencyFormatter(row.original.fee / 100),
+        cell: ({ row }) =>
+          currencyFormatter(row.original.fee / 100, {
+            trailingZeroDisplay: "stripIfInteger",
+          }),
...
-        cell: ({ row }) => currencyFormatter(row.original.total / 100),
+        cell: ({ row }) =>
+          currencyFormatter(row.original.total / 100, {
+            trailingZeroDisplay: "stripIfInteger",
+          }),

Additionally (outside this hunk), consider applying the same option to tooltip and Y-axis values (Lines 272 and 283) to keep all numbers in this page consistent.

// Line 272
currencyFormatter(d.values.value / 100, { trailingZeroDisplay: "stripIfInteger" })
// Line 283
value => currencyFormatter(value / 100, { trailingZeroDisplay: "stripIfInteger" })
apps/web/ui/partners/rewards/rewards-logic.tsx (1)

257-259: stripIfInteger likely ineffective with formatter’s fixed 2 fraction digits; pass digits here if you want $100 instead of $100.00.

currencyFormatter defaults to minimumFractionDigits: 2 and maximumFractionDigits: 2. Without overriding them, trailingZeroDisplay: "stripIfInteger" won’t hide .00. If the intent is to trim zeros in this badge, override digits locally:

-      : currencyFormatter(Number(value), {
-          trailingZeroDisplay: "stripIfInteger",
-        });
+      : currencyFormatter(Number(value), {
+          minimumFractionDigits: 0,
+          maximumFractionDigits: 2,
+          trailingZeroDisplay: "stripIfInteger",
+        });
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-composite-chart.tsx (1)

182-183: Optional: align tooltip formatting with header (no .00 for integers).

Header uses NumberFlow with trailingZeroDisplay: "stripIfInteger". Consider matching it in tooltip rows for consistency:

-                        {currencyFormatter((d.values.total || 0) / 100)}
+                        {currencyFormatter((d.values.total || 0) / 100, {
+                          trailingZeroDisplay: "stripIfInteger",
+                        })}
-                              {currencyFormatter(valueAccessor(d))}
+                              {currencyFormatter(valueAccessor(d), {
+                                trailingZeroDisplay: "stripIfInteger",
+                              })}

Also applies to: 204-205

packages/email/src/templates/new-sale-alert-partner.tsx (1)

58-60: Clarify preview copy: it shows commission, not sale amount.

Preview reads “You just made a {earningsInDollars} sale…”, but earningsInDollars is commission. Consider rewording to avoid confusion.

-        You just made a {earningsInDollars} sale via your referral link{" "}
+        You just earned {earningsInDollars} via your referral link{" "}
apps/web/ui/partners/bounties/bounty-performance.tsx (1)

32-39: Guard zero/invalid targets and show 0% progress.

If target is 0 or negative, value/target yields Infinity/NaN and the bar can incorrectly render as full width. Also, value && (...) hides a valid 0% bar.

-      <div className="h-1.5 w-full overflow-hidden rounded-full bg-neutral-200">
-        {value && (
+      <div className="h-1.5 w-full overflow-hidden rounded-full bg-neutral-200">
+        {typeof value === "number" && (
           <div
             style={{
-              width: Math.min(Math.max(value / target, 0), 1) * 100 + "%",
+              width:
+                (target > 0
+                  ? Math.min(Math.max(value / target, 0), 1)
+                  : 0) *
+                  100 + "%",
             }}
             className="h-full rounded-full bg-orange-600"
           />
         )}
       </div>
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-details-sheet.tsx (1)

86-86: Prefer a cents-to-currency helper to avoid repeated /100.

This math appears many times across the app. A tiny wrapper improves consistency and reduces chance of accidental double division.

Apply locally:

-      Total: currencyFormatter(payout.amount / 100),
+      Total: currencyFromCents(payout.amount),

Helper (outside this file):

// packages/utils/src/functions/currency-formatter.ts
export const currencyFromCents = (
  cents: number,
  options?: CurrencyFormatterOptions,
) => currencyFormatter(cents / 100, options);
apps/web/ui/partners/mark-commission-duplicate-modal.tsx (1)

82-86: Inline /100 reads well, but a shared helper is safer.

Minor DX/consistency win: reuse a currencyFromCents(...) wrapper.

-          ? currencyFormatter(commission.amount / 100)
+          ? currencyFromCents(commission.amount)
@@
-      Commission: currencyFormatter(commission.earnings / 100),
+      Commission: currencyFromCents(commission.earnings),

(See suggested helper in previous comment.)

apps/web/ui/links/link-builder/link-partner-details.tsx (1)

57-61: Nice simplification; consider a shared currencyFromCents helper.

Removes the bespoke helper and standardizes on the utils formatter. Small follow-up: use a cents wrapper to eliminate inline division and ensure consistency everywhere.

-            partner ? currencyFormatter(partner.saleAmount / 100) : undefined,
+            partner ? currencyFromCents(partner.saleAmount) : undefined,
@@
-              ? currencyFormatter(partner.totalCommissions / 100)
+              ? currencyFromCents(partner.totalCommissions)
@@
-            partner ? currencyFormatter(partner.netRevenue / 100) : undefined,
+            partner ? currencyFromCents(partner.netRevenue) : undefined,

Helper shown in the first file’s comment.

Also applies to: 64-66, 69-70

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

74-86: Guard percentage calc against divide-by-zero

If invoice.amount can be 0, Math.round((invoice.fee / invoice.amount) * 100) will throw. Add a safe fallback.

-  {
-    label: `Platform fees (${Math.round((invoice.fee / invoice.amount) * 100)}%)`,
-    value: `${currencyFormatter(invoice.fee / 100)}`,
-  },
+  {
+    label: `Platform fees (${
+      invoice.amount ? Math.round((invoice.fee / invoice.amount) * 100) : 0
+    }%)`,
+    value: `${currencyFormatter(invoice.fee / 100)}`,
+  },
apps/web/ui/partners/partner-details-sheet.tsx (2)

151-157: stripIfInteger + minFractionDigits=2 still shows 1.50; set min to 0 for true “dynamic” decimals

With the utils default min/max = 2, trailingZeroDisplay: "stripIfInteger" only strips integer ".00". If you want 1.5 instead of 1.50, override minimumFractionDigits to 0.

-                : currencyFormatter(partner.saleAmount / 100, {
-                    trailingZeroDisplay: "stripIfInteger",
-                  }),
+                : currencyFormatter(partner.saleAmount / 100, {
+                    minimumFractionDigits: 0,
+                    maximumFractionDigits: 2,
+                    trailingZeroDisplay: "stripIfInteger",
+                  }),

388-403: Match “dynamic decimals” intent for link revenue

Same as above: override minimumFractionDigits for non-integers to avoid 1.50.

-        accessorFn: (d) =>
-          currencyFormatter(d.saleAmount / 100, {
-            trailingZeroDisplay: "stripIfInteger",
-          }),
+        accessorFn: (d) =>
+          currencyFormatter(d.saleAmount / 100, {
+            minimumFractionDigits: 0,
+            maximumFractionDigits: 2,
+            trailingZeroDisplay: "stripIfInteger",
+          }),
...
-            {currencyFormatter(row.original.saleAmount / 100, {
-              trailingZeroDisplay: "stripIfInteger",
-            })}
+            {currencyFormatter(row.original.saleAmount / 100, {
+              minimumFractionDigits: 0,
+              maximumFractionDigits: 2,
+              trailingZeroDisplay: "stripIfInteger",
+            })}
apps/web/lib/integrations/slack/transform.ts (1)

154-190: Optional: unify sale amount formatting and handle currency exponents

createSaleTemplate still hand-rolls toFixed(2) and appends the code. Consider using currencyFormatter with proper exponent handling for non-USD currencies, consistent with the invoice fix.

-  const amountInDollars = (sale.amount / 100).toFixed(2);
+  const digits = new Intl.NumberFormat("en-US", {
+    style: "currency",
+    currency: sale.currency.toUpperCase(),
+  }).resolvedOptions().maximumFractionDigits;
+  const amountInMajor = sale.amount / Math.pow(10, digits);
...
-            text: `*Amount*\n${amountInDollars} ${sale.currency.toUpperCase()}`,
+            text: `*Amount*\n${currencyFormatter(amountInMajor, {
+              currency: sale.currency.toUpperCase(),
+            })}`,
packages/utils/src/functions/currency-formatter.ts (2)

11-16: Non-USD overrides will still force 2 decimals. Prefer currency-native minor units when options.currency is provided.

Today JPY/EUR overrides would render with 2 decimals unless callers remember to set digits. Make the default smarter.

-  Intl.NumberFormat("en-US", {
-    style: "currency",
-    currency: "USD",
-    minimumFractionDigits: 2,
-    maximumFractionDigits: 2,
-    trailingZeroDisplay: "auto",
-    ...options,
-  } as CurrencyFormatterOptions).format(value);
+  {
+    const defaults: CurrencyFormatterOptions = {
+      style: "currency",
+      currency: "USD",
+      minimumFractionDigits: 2,
+      maximumFractionDigits: 2,
+      trailingZeroDisplay: "auto",
+    };
+    const merged: CurrencyFormatterOptions = { ...defaults, ...options };
+    // If caller overrides currency and didn't explicitly set fraction digits,
+    // use that currency's native minor units (defer to Intl defaults).
+    if (options?.currency &&
+        options.minimumFractionDigits === undefined &&
+        options.maximumFractionDigits === undefined) {
+      delete (merged as any).minimumFractionDigits;
+      delete (merged as any).maximumFractionDigits;
+    }
+    return new Intl.NumberFormat("en-US", merged).format(value);
+  }

11-16: Micro-perf: cache the default formatter.

Most calls appear to use defaults; reuse a single Intl.NumberFormat instance.

+const defaultCurrencyFormatter = new Intl.NumberFormat("en-US", {
+  style: "currency",
+  currency: "USD",
+  minimumFractionDigits: 2,
+  maximumFractionDigits: 2,
+  trailingZeroDisplay: "auto",
+} as CurrencyFormatterOptions);
+
 export const currencyFormatter = (
   value: number,
   options?: CurrencyFormatterOptions,
 ) =>
-  Intl.NumberFormat("en-US", {
-    style: "currency",
-    currency: "USD",
-    minimumFractionDigits: 2,
-    maximumFractionDigits: 2,
-    trailingZeroDisplay: "auto",
-    ...options,
-  } as CurrencyFormatterOptions).format(value);
+  (options
+    ? new Intl.NumberFormat("en-US", {
+        style: "currency",
+        currency: "USD",
+        minimumFractionDigits: 2,
+        maximumFractionDigits: 2,
+        trailingZeroDisplay: "auto",
+        ...options,
+      } as CurrencyFormatterOptions)
+    : defaultCurrencyFormatter
+  ).format(value);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a3c8c2c and a8eeb76.

📒 Files selected for processing (64)
  • apps/web/app/(ee)/admin.dub.co/(dashboard)/commissions/client.tsx (1 hunks)
  • apps/web/app/(ee)/admin.dub.co/(dashboard)/payouts/client.tsx (1 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)/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/invoices/[invoiceId]/domain-renewal-invoice.tsx (3 hunks)
  • apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/partner-payout-invoice.tsx (3 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-details-sheet.tsx (2 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-stats.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-table.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-composite-chart.tsx (2 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)/page-client.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/analytics/analytics-timeseries-chart.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-submission-details-sheet.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/commissions/commission-table.tsx (2 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/overview-chart.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)/settings/billing/invoices/page-client.tsx (1 hunks)
  • apps/web/lib/api/sales/construct-reward-amount.ts (2 hunks)
  • apps/web/lib/integrations/slack/transform.ts (3 hunks)
  • apps/web/ui/analytics/events/events-table.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/layout/sidebar/payout-stats.tsx (0 hunks)
  • apps/web/ui/links/link-analytics-badge.tsx (1 hunks)
  • apps/web/ui/links/link-builder/link-partner-details.tsx (1 hunks)
  • apps/web/ui/partners/bounties/bounty-performance.tsx (1 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-details-sheet.tsx (4 hunks)
  • apps/web/ui/partners/payout-invoice-sheet.tsx (4 hunks)
  • apps/web/ui/partners/program-reward-modifiers-tooltip.tsx (1 hunks)
  • apps/web/ui/partners/rewards/rewards-logic.tsx (1 hunks)
  • packages/email/src/templates/connect-payout-reminder.tsx (1 hunks)
  • packages/email/src/templates/new-sale-alert-partner.tsx (1 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 (1 hunks)
  • packages/email/src/templates/partner-payout-processed.tsx (1 hunks)
  • packages/email/src/templates/partner-payout-withdrawal-completed.tsx (0 hunks)
  • packages/email/src/templates/partner-payout-withdrawal-initiated.tsx (0 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)
💤 Files with no reviewable changes (3)
  • apps/web/ui/layout/sidebar/payout-stats.tsx
  • packages/email/src/templates/partner-payout-withdrawal-completed.tsx
  • packages/email/src/templates/partner-payout-withdrawal-initiated.tsx
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-05-29T04:45:18.504Z
Learnt from: devkiran
PR: dubinc/dub#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/app/api/callback/plain/partner/route.ts
  • packages/email/src/templates/new-sale-alert-program-owner.tsx
📚 Learning: 2025-07-30T15:29:54.131Z
Learnt from: TWilson023
PR: dubinc/dub#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/partners/program-reward-modifiers-tooltip.tsx
📚 Learning: 2025-07-11T16:28:55.693Z
Learnt from: devkiran
PR: dubinc/dub#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
🧬 Code graph analysis (56)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-submission-details-sheet.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/overview-chart.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/ui/partners/overview/blocks/partners-block.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
packages/email/src/templates/partner-program-summary.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/ui/partners/payout-invoice-sheet.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/ui/partners/program-reward-modifiers-tooltip.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
packages/email/src/templates/program-payout-reminder.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
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 (5-16)
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 (5-16)
apps/web/app/(ee)/partners.dub.co/invoices/[payoutId]/route.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/ui/partners/overview/blocks/commissions-block.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
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 (5-16)
apps/web/app/(ee)/app.dub.co/embed/referrals/leaderboard.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-stats.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
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 (5-16)
apps/web/app/(ee)/api/cron/payouts/balance-available/route.ts (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/commissions/commission-table.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/ui/partners/overview/blocks/traffic-sources-block.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/lib/api/sales/construct-reward-amount.ts (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/ui/partners/overview/blocks/countries-block.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-submissions-table.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/ui/partners/bounties/bounty-performance.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
packages/email/src/templates/partner-payout-confirmed.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/page-client.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/groups/groups-table.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
packages/email/src/templates/partner-paypal-payout-failed.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
packages/email/src/templates/partner-payout-failed.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/app/(ee)/admin.dub.co/(dashboard)/payouts/client.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/app/(ee)/app.dub.co/embed/referrals/earnings-summary.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/app/(ee)/app.dub.co/embed/referrals/earnings.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/app/(ee)/admin.dub.co/(dashboard)/commissions/client.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/ui/customers/customer-sales-table.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
packages/email/src/templates/partner-payout-processed.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/ui/partners/mark-commission-duplicate-modal.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
packages/email/src/templates/new-sale-alert-partner.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-details-sheet.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/ui/partners/rewards/rewards-logic.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/ui/links/link-builder/link-partner-details.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-table.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/analytics/analytics-timeseries-chart.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/app/api/callback/plain/partner/route.ts (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/ui/customers/customer-partner-earnings-table.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-table.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/partner-payout-invoice.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/ui/partners/overview/blocks/links-block.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/domain-renewal-invoice.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
packages/email/src/templates/connect-payout-reminder.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/payouts-card.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
packages/email/src/templates/new-sale-alert-program-owner.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/ui/partners/partner-details-sheet.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
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 (5-16)
apps/web/app/(ee)/admin.dub.co/(dashboard)/revenue/client.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/ui/partners/mark-commission-fraud-or-canceled-modal.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/lib/integrations/slack/transform.ts (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-stats.tsx (1)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (5-16)
🪛 Biome (2.1.2)
apps/web/lib/integrations/slack/transform.ts

[error] 285-285: Unexpected empty object pattern.

(lint/correctness/noEmptyPattern)


[error] 294-294: Unexpected empty object pattern.

(lint/correctness/noEmptyPattern)

⏰ 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: Vade Review
🔇 Additional comments (48)
apps/web/app/(ee)/partners.dub.co/invoices/[payoutId]/route.tsx (1)

118-118: LGTM: default formatter keeps 2 decimals (invoice-safe).

Dropping the explicit fraction options retains the same output given the utility’s defaults. No behavior change here.

apps/web/ui/customers/customer-table/customer-table.tsx (1)

164-165: LGTM: clearer currency display.

Using trailingZeroDisplay: "stripIfInteger" improves readability while preserving cents when present.

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

213-215: LGTM: consistent currency formatting for amounts.

Matches the Customers table pattern; good consistency.

apps/web/ui/customers/customer-details-column.tsx (1)

163-164: Good use of trailingZeroDisplay

Nice touch to drop “.00” for integers. Ensure all target runtimes (Node/Edge/browsers) support Intl trailingZeroDisplay in your deployment targets.

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

45-45: Check remaining currencyFormatter overrides
Explicit overrides detected in:

  • packages/email/src/templates/partner-payout-withdrawal-completed.tsx (line 35)
  • packages/email/src/templates/partner-payout-withdrawal-initiated.tsx (line 33)
    Confirm these align with the new default (USD, two decimals) or remove the custom options.
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-submissions-table.tsx (1)

233-234: Consistent currency formatting with new defaults.

Good swap to currencyFormatter(value / 100); matches the standardized formatter.

Double-check that performanceCondition.value (target) cannot be 0; ProgressCircle uses value/target and would divide by zero. If 0 is possible, guard or clamp before computing progress.

apps/web/app/(ee)/app.dub.co/embed/referrals/earnings-summary.tsx (1)

47-47: Embed: formatting aligned with utility defaults.

Looks correct; keeps USD with 2 decimals by default.

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

99-99: Email template: standardized currency display.

Change is consistent with the utility’s default USD/2-decimal behavior.

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

67-67: Partner payouts: formatter refactor looks good.

Keeps prior display semantics via defaults.

apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/invoices/page-client.tsx (1)

116-116: LGTM — aligned with centralized currency defaults

Replacing per-call options with the standardized formatter is correct here.

apps/web/ui/partners/overview/blocks/links-block.tsx (1)

88-88: LGTM — uses shared formatter defaults

Consistent with the refactor; no issues.

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

312-312: LGTM — unified currency formatting

Matches the shared defaults; tooltip copies already reuse the same helper.

apps/web/app/api/callback/plain/partner/route.ts (1)

222-223: LGTM — consistent formatter in Plain card usage

Standardizes display; expected two-decimal output in USD.

apps/web/ui/customers/customer-sales-table.tsx (1)

54-55: LGTM – consistent formatter usage.

Switch to the shared currencyFormatter with defaults reads well and keeps logic centralized.

Also applies to: 63-64

apps/web/ui/customers/customer-partner-earnings-table.tsx (1)

37-38: LGTM – consistent formatter usage.

The cells now rely on the standardized currencyFormatter; no issues spotted.

Also applies to: 44-45

apps/web/ui/partners/overview/blocks/traffic-sources-block.tsx (1)

91-92: LGTM – simplified currency formatting.

Matches the project-wide refactor; no functional changes beyond centralizing defaults.

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

44-44: LGTM — consistent formatter usage in emails.

Using the shared formatter keeps email totals aligned with UI.

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

238-238: LGTM — formatter unification in table cells.

Single-source currency formatting simplifies consistency across tooltips and cells.

apps/web/ui/partners/mark-commission-fraud-or-canceled-modal.tsx (1)

89-92: Currency formatting refactor: LGTM

Swapping to the defaulted currencyFormatter(x / 100) keeps behavior consistent with the new util. No issues spotted.

apps/web/ui/partners/overview/blocks/commissions-block.tsx (1)

70-70: Adopts standardized formatter correctly

Using currencyFormatter(earnings / 100) aligns with the new defaults. Looks good.

apps/web/app/(ee)/app.dub.co/embed/referrals/leaderboard.tsx (1)

83-83: Formatter usage updated correctly

Single-argument currencyFormatter(totalCommissions / 100) matches the new API. Good to go.

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

125-125: Groups monetary columns correctly migrated

Revenue, Commissions, and Net Revenue now use the standardized formatter. All good.

Also applies to: 130-130, 135-135

apps/web/ui/partners/program-reward-modifiers-tooltip.tsx (1)

145-145: LGTM — correct cents-to-dollars formatting.

Using currencyFormatter(Number(condition.value) / 100) matches the stored cents semantics for currency conditions.

packages/email/src/templates/partner-program-summary.tsx (1)

159-161: LGTM — standardized currency formatting (cents ➜ dollars).

Both monthly and lifetime earnings now consistently use currencyFormatter(… / 100).

Also applies to: 179-181

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

159-159: Tooltip currency formatting now relies on default USD/2-decimal settings — looks good.

Matches the updated currencyFormatter defaults and stays consistent with the YAxis tickFormat. No behavior change expected.

packages/email/src/templates/new-sale-alert-partner.tsx (1)

50-53: Simplified currency formatting — consistent with new defaults.

Switching to currencyFormatter(x / 100) preserves two decimals under the new utility defaults. Safe change.

apps/web/ui/partners/bounties/bounty-performance.tsx (1)

19-19: Currency formatter refactor aligns with new defaults.

Using the default formatter for currency attributes is correct and consistent with the PR’s direction.

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

77-77: Currency formatter simplification — OK.

Two-decimal USD output is preserved via the new defaults.

apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/analytics/analytics-timeseries-chart.tsx (1)

57-58: Tooltip currency formatting uses new defaults — consistent with YAxis.

Keeps tooltip and axis labels aligned on USD with two decimals.

packages/email/src/templates/new-sale-alert-program-owner.tsx (1)

69-75: LGTM on switching to the formatter defaults.

Division by 100 and relying on the new defaults is consistent with the updated utility.

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

86-86: LGTM: rely on default currency formatting.


165-166: LGTM: consistent table currency formatting.

apps/web/app/(ee)/admin.dub.co/(dashboard)/revenue/client.tsx (1)

92-93: LGTM: simplified currency formatting call.

apps/web/lib/api/sales/construct-reward-amount.ts (1)

42-45: LGTM: consistent use of shared formatter; clear handling of percentage vs. currency.

Also applies to: 56-57

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

165-165: LGTM — uses standardized formatter defaults.

Consistent with the refactor goals.

apps/web/app/(ee)/app.dub.co/embed/referrals/earnings.tsx (1)

59-60: LGTM — aligns embed with unified currency formatting.

No behavioral change vs prior explicit 2-decimal calls; just cleaner.

Also applies to: 67-68

apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/commissions/commission-table.tsx (2)

154-155: LGTM — formatter usage matches project defaults.

Keeps negative values readable; color is already handled elsewhere.


141-147: Use a numeric accessor for sorting and format in cell
AccessorFn currently returns formatted strings (currencyFormatter/nFormatter), which most table libs sort lexicographically (e.g. "$90" > "$1,200"). Change accessorFn to return the raw number (d.amount or d.quantity) and move formatting into a cell renderer—or use a dedicated sortValue/sortingFn if supported.

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

167-183: LGTM on formatter simplification

Using the shared currencyFormatter without explicit fraction options keeps this consistent with the PR’s direction.

apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/domain-renewal-invoice.tsx (2)

204-208: LGTM: total formatting via shared formatter

Consistent with the new utility.


233-236: LGTM: per-domain renewal fee formatting

Reads clearly and aligns with the new formatter.

apps/web/ui/partners/partner-details-sheet.tsx (1)

269-272: LGTM: payouts amount column

Good move to the shared formatter; keeps the table consistent.

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

137-149: LGTM: summary values use shared formatter

Formatting reads clean; pairs well with the non-USD note above once fixed.


279-281: LGTM: header total formatting

Consistent with other invoice totals.


339-340: LGTM: per-payout amount formatting

Looks good.

apps/web/lib/integrations/slack/transform.ts (1)

250-253: LGTM: switch to shared currencyFormatter

Simplifies and unifies Slack amounts.

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

12-14: Confirm the product shift to always show cents by default.

Switching from stripIfInteger to auto means 10 → $10.00 (not $10). Verify Slack/email templates and UI copy expect this.


7-8: Signature change is non-breaking; no callers pass a currency string
Ran a regex across all call sites—no instances of currencyFormatter(..., 'USD') or similar string literals as the second argument; existing two-arg usages all pass an options object.

if (availableBalance <= 0) {
return logAndRespond(
`The available balance (${currencyFormatter(availableBalance / 100, { maximumFractionDigits: 2, currency })}) for partner ${partner.email} (${stripeAccount}) is less than or equal to 0 after subtracting pending payouts. Skipping...`,
`The available balance (${currencyFormatter(availableBalance / 100, { currency })}) for partner ${partner.email} (${stripeAccount}) is less than or equal to 0 after subtracting pending payouts. Skipping...`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Bug: dividing by 100 is incorrect for non-2-decimal currencies

Stripe amounts are in minor units. Using /100 breaks for zero-decimal (e.g., JPY, HUF, TWD) and 3-decimal (e.g., KWD, BHD) currencies. Convert by currency exponent and normalize the code to uppercase for Intl.

Apply this minimal change:

- `The available balance (${currencyFormatter(availableBalance / 100, { currency })}) for partner ${partner.email} (${stripeAccount}) is less than or equal to 0 after subtracting pending payouts. Skipping...`,
+ `The available balance (${currencyFormatter(minorToMajor(availableBalance, currency), { currency: currency.toUpperCase() })}) for partner ${partner.email} (${stripeAccount}) is less than or equal to 0 after subtracting pending payouts. Skipping...`,

Helper (outside this hunk):

const EXPONENT: Record<string, number> = {
  // zero-decimal
  jpy: 0, huf: 0, twd: 0, krw: 0, vnd: 0, clp: 0, pyg: 0, rwf: 0, bif: 0,
  djf: 0, gnf: 0, kmf: 0, mga: 0, vuv: 0, xaf: 0, xof: 0, xpf: 0,
  // three-decimal
  kwd: 3, bhd: 3, jod: 3, omr: 3, tnd: 3, iqd: 3,
};
const minorToMajor = (amountMinor: number, ccy: string) =>
  amountMinor / 10 ** (EXPONENT[ccy.toLowerCase()] ?? 2);
🤖 Prompt for AI Agents
In apps/web/app/(ee)/api/cron/payouts/balance-available/route.ts around line 83,
the template currently divides availableBalance by 100 which is wrong for
currencies with non-2 decimal exponents; add the provided EXPONENT map and
minorToMajor helper outside this hunk, then replace the /100 conversion with
minorToMajor(availableBalance, currency) and ensure the currency passed to Intl
formatting is normalized to uppercase (e.g., { currency: currency.toUpperCase()
}) so amounts are correctly converted and formatted for zero-, two- and
three-decimal currencies.


console.log(
`Stripe payout created for partner ${partner.email} (${stripeAccount}): ${stripePayout.id} (${currencyFormatter(stripePayout.amount / 100, { maximumFractionDigits: 2, currency: stripePayout.currency })})`,
`Stripe payout created for partner ${partner.email} (${stripeAccount}): ${stripePayout.id} (${currencyFormatter(stripePayout.amount / 100, { currency: stripePayout.currency })})`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Same currency-scale issue here + normalize currency

Use exponent-aware conversion and uppercase the currency code for Intl.

- `Stripe payout created for partner ${partner.email} (${stripeAccount}): ${stripePayout.id} (${currencyFormatter(stripePayout.amount / 100, { currency: stripePayout.currency })})`,
+ `Stripe payout created for partner ${partner.email} (${stripeAccount}): ${stripePayout.id} (${currencyFormatter(minorToMajor(stripePayout.amount, stripePayout.currency), { currency: stripePayout.currency.toUpperCase() })})`,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
`Stripe payout created for partner ${partner.email} (${stripeAccount}): ${stripePayout.id} (${currencyFormatter(stripePayout.amount / 100, { currency: stripePayout.currency })})`,
`Stripe payout created for partner ${partner.email} (${stripeAccount}): ${stripePayout.id} (${currencyFormatter(minorToMajor(stripePayout.amount, stripePayout.currency), { currency: stripePayout.currency.toUpperCase() })})`,
🤖 Prompt for AI Agents
In apps/web/app/(ee)/api/cron/payouts/balance-available/route.ts around line
107, the payout log uses a naive cents-to-major conversion and a lowercase
currency code; change it to use an exponent-aware conversion (divide
stripePayout.amount by 10**minorUnits for the currency) and normalize the
currency code to uppercase before formatting with Intl.NumberFormat so the
displayed amount and currency are correct for currencies with different
minor-unit scales.

Comment on lines 130 to 136
const nonUsdTransactionDisplay =
chargeAmount && chargeCurrency && chargeCurrency !== "usd"
? ` (${currencyFormatter(
chargeAmount / 100,
{
minimumFractionDigits: 2,
maximumFractionDigits: 2,
},
chargeCurrency.toUpperCase(),
)})`
? ` (${currencyFormatter(chargeAmount / 100, {
currency: chargeCurrency.toUpperCase(),
})})`
: "";

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Incorrect minor-unit conversion for non-USD currencies

Stripe amounts are in the currency’s smallest unit (0/2/3 digits). Dividing by 100 will be wrong for JPY (0) and KWD/BHD/JOD… (3). Convert using the currency’s fraction digits.

-  const nonUsdTransactionDisplay =
-    chargeAmount && chargeCurrency && chargeCurrency !== "usd"
-      ? ` (${currencyFormatter(chargeAmount / 100, {
-          currency: chargeCurrency.toUpperCase(),
-        })})`
-      : "";
+  const nonUsdTransactionDisplay =
+    chargeAmount && chargeCurrency && chargeCurrency !== "usd"
+      ? ` (${currencyFormatter(
+          chargeAmount /
+            Math.pow(
+              10,
+              new Intl.NumberFormat("en-US", {
+                style: "currency",
+                currency: chargeCurrency.toUpperCase(),
+              }).resolvedOptions().maximumFractionDigits,
+            ),
+          { currency: chargeCurrency.toUpperCase() },
+        )})`
+      : "";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const nonUsdTransactionDisplay =
chargeAmount && chargeCurrency && chargeCurrency !== "usd"
? ` (${currencyFormatter(
chargeAmount / 100,
{
minimumFractionDigits: 2,
maximumFractionDigits: 2,
},
chargeCurrency.toUpperCase(),
)})`
? ` (${currencyFormatter(chargeAmount / 100, {
currency: chargeCurrency.toUpperCase(),
})})`
: "";
const nonUsdTransactionDisplay =
chargeAmount && chargeCurrency && chargeCurrency !== "usd"
? ` (${currencyFormatter(
chargeAmount /
Math.pow(
10,
new Intl.NumberFormat("en-US", {
style: "currency",
currency: chargeCurrency.toUpperCase(),
}).resolvedOptions().maximumFractionDigits,
),
{ currency: chargeCurrency.toUpperCase() },
)})`
: "";
🤖 Prompt for AI Agents
In apps/web/app/(ee)/app.dub.co/invoices/[invoiceId]/partner-payout-invoice.tsx
around lines 130-136, the code divides Stripe amounts by 100 for non-USD
currencies which is incorrect for currencies with 0 or 3 minor digits; instead,
determine the currency’s fraction digits (e.g. via new
Intl.NumberFormat(undefined, { style: 'currency', currency: chargeCurrency
}).resolvedOptions().maximumFractionDigits) and compute divisor = 10 **
fractionDigits, then convert the integer amount by dividing by that divisor
before passing to currencyFormatter; ensure chargeCurrency is upper-cased and
handle missing values safely.

Comment on lines +104 to 105
accessorFn: (d) => currencyFormatter(d.saleAmount / 100),
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Ensure numeric sorting for Revenue (use accessorKey + cell formatting).

Returning a formatted string from accessorFn causes lexicographic sorting on "saleAmount". Use the numeric field as the accessor and format in cell.

-              accessorFn: (d) => currencyFormatter(d.saleAmount / 100),
+              accessorKey: "saleAmount",
+              cell: ({ getValue }) => currencyFormatter(Number(getValue()) / 100),
+              sortingFn: "basic",

Tip: Apply the same pattern to other numeric columns that currently return formatted strings to avoid string-based sorts.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
accessorFn: (d) => currencyFormatter(d.saleAmount / 100),
},
accessorKey: "saleAmount",
cell: ({ getValue }) => currencyFormatter(Number(getValue()) / 100),
sortingFn: "basic",
🤖 Prompt for AI Agents
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/analytics/analytics-partners-table.tsx
around lines 104-105: the column currently returns a formatted string from
accessorFn (currencyFormatter(d.saleAmount / 100)) which causes lexicographic
sorting; change the column to use a numeric accessorKey (saleAmount) so the
table sorts by the raw number and move the formatting into the column's cell
renderer (format the cell value for display by dividing by 100 and applying
currencyFormatter); apply the same accessorKey + cell formatting pattern to any
other numeric columns that currently return formatted strings to ensure correct
numeric sorting.

Comment on lines +114 to 116
{currencyFormatter(amount / 100, {})}
</div>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Drop “USD” suffix and avoid passing empty options.

currencyFormatter already outputs USD; appending " USD" duplicates the currency. Also, {} is unnecessary noise.

Apply:

-                            {currencyFormatter(amount / 100, {})}
+                            {currencyFormatter(amount / 100)}
-                  {currencyFormatter(
-                    eligiblePendingPayouts?.amount
-                      ? eligiblePendingPayouts.amount / 100
-                      : 0,
-                    {},
-                  ) + " USD"}
+                  {currencyFormatter(
+                    eligiblePendingPayouts?.amount
+                      ? eligiblePendingPayouts.amount / 100
+                      : 0
+                  )}
-                            {currencyFormatter(amount / 100, {})}
+                            {currencyFormatter(amount / 100)}
-                  {currencyFormatter(totalPaid / 100, {}) + " USD"}
+                  {currencyFormatter(totalPaid / 100)}

Also applies to: 123-129, 176-177, 185-186

🤖 Prompt for AI Agents
In
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-stats.tsx
around lines 114-116 (also update the calls at 123-129, 176-177, and 185-186),
remove the explicit " USD" suffix being appended to the output of
currencyFormatter and stop passing an empty options object ({}); instead call
currencyFormatter(amount / 100) and render that result directly so the currency
is not duplicated and the unnecessary empty argument is eliminated.

💡 Verification agent

🧩 Analysis chain

Repo-wide sweep for “ USD” after formatted values.

There may be other instances. Run this to surface duplicates and clean them up.

Also applies to: 123-129, 176-177, 185-186


🏁 Script executed:

#!/bin/bash
# Find places where currencyFormatter output is followed by a manual " USD"
rg -nP --type=ts --type=tsx -C2 'currencyFormatter\([^)]*\)\s*\+\s*[\'"] USD[\'"]'

# Find JSX like {...currencyFormatter(...)} USD
rg -nP --type=ts --type=tsx -C2 '\}\s*USD\b'

Length of output: 116


🏁 Script executed:

#!/bin/bash
set -eo pipefail

# Find concatenated currencyFormatter output with manual " USD"
rg -nP --type ts --type tsx -C2 "currencyFormatter\([^)]*\)\s*\+\s*['\"][ ]*USD['\"]"

# Find JSX patterns like {currencyFormatter(...)} USD
rg -nP --type ts --type tsx -C2 "\{\s*currencyFormatter\([^)]*\)\s*\}\s*USD\b"

# Find any standalone "} USD" cases
rg -nP --type ts --type tsx -C2 "\}\s*USD\b"

Length of output: 138


#!/bin/bash
set -eo pipefail

# Search all .ts and .tsx files for currencyFormatter concatenated with " USD"
rg -nP -g '*.ts' -g '*.tsx' -C2 "currencyFormatter\\([^)]*\\)\\s*\\+\\s*['\"]\\s*USD['\"]"

# Search for JSX patterns where formatted value is directly followed by USD
rg -nP -g '*.ts' -g '*.tsx' -C2 "\\{\\s*currencyFormatter\\([^)]*\\)\\s*\\}\\s*USD\\b"

# Catch any standalone "} USD" sequences
rg -nP -g '*.ts' -g '*.tsx' -C2 "\\}\\s*USD\\b"

#!/bin/bash
set -eo pipefail

# Find lines where currencyFormatter and USD appear on the same line
rg -n 'currencyFormatter.*USD' -g '*.{ts,tsx}' -C2

# Find files that use currencyFormatter and then manual " USD" in separate lines
files=$(rg -l 'currencyFormatter' -g '*.{ts,tsx}')
if [ -n "$files" ]; then
  rg -n -C2 'USD' $files
fi

🏁 Script executed:

#!/bin/bash
set -eo pipefail

# Find concatenated currencyFormatter output with manual " USD"
rg -nP -g '*.ts' -g '*.tsx' -C2 "currencyFormatter\\([^)]*\\)\\s*\\+\\s*['\"]\\s*USD['\"]"

# Find JSX patterns like {currencyFormatter(...)} USD
rg -nP -g '*.ts' -g '*.tsx' -C2 "\\{\\s*currencyFormatter\\([^)]*\\)\\s*\\}\\s*USD\\b"

# Catch any standalone "} USD" or " USD" following formatted expressions
rg -nP -g '*.ts' -g '*.tsx' -C2 "(currencyFormatter|\\})\\s*USD\\b"

Length of output: 830


🏁 Script executed:

#!/bin/bash
set -eo pipefail

file="apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-stats.tsx"

sed -n '110,130p' "$file"
sed -n '170,190p' "$file"

Length of output: 1956


Remove manual “ USD” concatenations
In apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-stats.tsx, remove the + " USD" on the two Tooltip spans around eligiblePendingPayouts and totalPaid and adjust currencyFormatter to include the currency code. No other instances were found.

🤖 Prompt for AI Agents
In
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-stats.tsx
around lines 114-116, remove the manual ' + " USD"' concatenations on the two
Tooltip span outputs for eligiblePendingPayouts and totalPaid and instead call
currencyFormatter with an option or parameter to include the currency code
(e.g., pass { currency: 'USD', display: 'code' } or equivalent supported
option), and update the currencyFormatter call sites accordingly so the
formatted string already contains "USD" without manual concatenation.

Comment on lines 159 to 169
"Commissions",
!partner.totalCommissions
? "-"
: currencyFormatter(partner.totalCommissions / 100, {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}),
: currencyFormatter(partner.totalCommissions / 100),
],
[
"Net revenue",
!partner.netRevenue
? "-"
: currencyFormatter(partner.netRevenue / 100, {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}),
: currencyFormatter(partner.netRevenue / 100),
],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Don’t hide zero monetary values

Using !value will render "-" for 0. Prefer a nullish check so $0.00 displays correctly.

-                [
-                  "Commissions",
-                  !partner.totalCommissions
-                    ? "-"
-                    : currencyFormatter(partner.totalCommissions / 100),
-                ],
+                [
+                  "Commissions",
+                  partner.totalCommissions == null
+                    ? "-"
+                    : currencyFormatter(partner.totalCommissions / 100),
+                ],
-                [
-                  "Net revenue",
-                  !partner.netRevenue
-                    ? "-"
-                    : currencyFormatter(partner.netRevenue / 100),
-                ],
+                [
+                  "Net revenue",
+                  partner.netRevenue == null
+                    ? "-"
+                    : currencyFormatter(partner.netRevenue / 100),
+                ],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"Commissions",
!partner.totalCommissions
? "-"
: currencyFormatter(partner.totalCommissions / 100, {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}),
: currencyFormatter(partner.totalCommissions / 100),
],
[
"Net revenue",
!partner.netRevenue
? "-"
: currencyFormatter(partner.netRevenue / 100, {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}),
: currencyFormatter(partner.netRevenue / 100),
],
[
"Commissions",
partner.totalCommissions == null
? "-"
: currencyFormatter(partner.totalCommissions / 100),
],
[
"Net revenue",
partner.netRevenue == null
? "-"
: currencyFormatter(partner.netRevenue / 100),
],
🤖 Prompt for AI Agents
In apps/web/ui/partners/partner-details-sheet.tsx around lines 159–169, the code
uses !partner.totalCommissions and !partner.netRevenue which treats 0 as falsy
and renders "-" incorrectly; change those checks to nullish checks (e.g.,
partner.totalCommissions == null ? "-" :
currencyFormatter(partner.totalCommissions / 100)) and similarly for
partner.netRevenue so actual $0.00 is displayed while null/undefined still shows
"-".

Comment on lines +273 to 274
currencyFormatter(fee / 100)
) : (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Round to integer cents before formatting and submitting.

fee and total are computed as floating-point cents (percentage of amount). To avoid fractional-cent drift between display and backend, round to integer cents when formatting and when sending to confirmPayouts.

Apply rounding in the formatted displays:

-            currencyFormatter(fee / 100)
+            currencyFormatter(Math.round(fee) / 100)
-            currencyFormatter(total / 100)
+            currencyFormatter(Math.round(total) / 100)
-              ? `Hold to confirm ${currencyFormatter(amount / 100)} payout`
+              ? `Hold to confirm ${currencyFormatter(Math.round(amount) / 100)} payout`

Also update the action payload to send integer cents (outside the changed lines; shown here for completeness):

// inside onClick
const amountCents = Math.round(amount ?? 0);
const feeCents = Math.round(fee ?? 0);
const totalCents = Math.round(total ?? 0);

const result = await confirmPayouts({
  workspaceId,
  paymentMethodId: selectedPaymentMethod.id,
  cutoffPeriod,
  excludedPayoutIds,
  amount: amountCents,
  fee: feeCents,
  total: totalCents,
});

Also applies to: 299-301, 483-485

🤖 Prompt for AI Agents
In apps/web/ui/partners/payout-invoice-sheet.tsx around lines 273-274 (also
apply same change at 299-301 and 483-485), the fee/total are floating-point
cents and must be rounded to integer cents before formatting and before sending
to confirmPayouts; update the formatted displays to call Math.round on fee/total
(and amount where shown) before passing to currencyFormatter, and update the
onClick action payload (outside these lines) to compute const amountCents =
Math.round(amount ?? 0); const feeCents = Math.round(fee ?? 0); const totalCents
= Math.round(total ?? 0); and send those integer cents in the confirmPayouts
call.

Comment on lines +69 to 75
const saleAmountInDollars = currencyFormatter(sale.amount / 100);

const earningsInDollars = currencyFormatter(sale.earnings / 100, {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
const earningsInDollars = currencyFormatter(sale.earnings / 100);

const profitInDollars = currencyFormatter(
(sale.amount - sale.earnings) / 100,
{
minimumFractionDigits: 2,
maximumFractionDigits: 2,
},
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove duplicate “USD” suffix and format negatives via formatter.

currencyFormatter already returns a USD-formatted string (e.g., “$13.30”). Appending “ USD” renders “$13.30 USD”. Also, prefer passing a negative value to the formatter rather than prepending “-”.

Apply:

- const earningsInDollars = currencyFormatter(sale.earnings / 100);
+ const earningsInDollars = currencyFormatter(-sale.earnings / 100);
-                      {saleAmountInDollars} USD
+                      {saleAmountInDollars}
-                      -{earningsInDollars} USD
+                      {earningsInDollars}
-                      {profitInDollars} USD
+                      {profitInDollars}

Also applies to: 134-136, 146-148, 160-163

🤖 Prompt for AI Agents
In packages/email/src/templates/new-sale-alert-program-owner.tsx around lines
69-75 (and likewise at 134-136, 146-148, 160-163), the code appends a literal "
USD" to values returned by currencyFormatter and prefixes negatives manually;
remove the extra " USD" concatenation and instead pass the numeric value
(positive or negative) into currencyFormatter so it produces properly formatted
negative values (e.g., use currencyFormatter((sale.amount - sale.earnings) /
100) for profit, and for any negative display pass a negative numeric value
rather than adding a '-' string).

Comment on lines +1 to +3
interface CurrencyFormatterOptions extends Intl.NumberFormatOptions {
trailingZeroDisplay?: "auto" | "stripIfInteger";
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Export the options type to avoid d.ts build errors.

currencyFormatter is exported but its parameter references a non-exported interface; this breaks builds when declaration: true.

Apply:

-interface CurrencyFormatterOptions extends Intl.NumberFormatOptions {
+export interface CurrencyFormatterOptions extends Intl.NumberFormatOptions {
   trailingZeroDisplay?: "auto" | "stripIfInteger";
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
interface CurrencyFormatterOptions extends Intl.NumberFormatOptions {
trailingZeroDisplay?: "auto" | "stripIfInteger";
}
export interface CurrencyFormatterOptions extends Intl.NumberFormatOptions {
trailingZeroDisplay?: "auto" | "stripIfInteger";
}
🤖 Prompt for AI Agents
packages/utils/src/functions/currency-formatter.ts lines 1-3: the
CurrencyFormatterOptions interface is not exported but is referenced by the
exported currencyFormatter API which breaks declaration builds; fix by exporting
the type (add the export keyword before the interface or convert it to an
exported type alias) so the options type is part of the public surface, and run
the d.ts build to verify no other internal-only types are referenced.

@steven-tey steven-tey merged commit de5e558 into main Sep 1, 2025
9 of 10 checks passed
@steven-tey steven-tey deleted the currency-formatter branch September 1, 2025 22:39
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