Skip to content

Conversation

@sean-brydon
Copy link
Member

@sean-brydon sean-brydon commented Sep 18, 2025

What does this PR do?

PR Updates to the new designs for billing + credits

Image Demo (if applicable):

CleanShot 2025-09-18 at 10 42 41

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 18, 2025

Walkthrough

Updates the organizations Billing settings UI and server-side credit model. UI changes: removes the shell header border on the billing page; replaces CtaRow sections with nested/rounded cards; rewrites BillingCredits into a CreditRow-driven layout with org-aware labels/links, a remaining-credits progress bar, revised buy/add-credits input, expense-log download UI, and a new "Credit Worth" section plus matching skeleton; updates translations. Server changes: getAllCredits/_getAllCreditsForTeam now expose totalCreditsUsedThisMonth (and team responses include additionalCredits), and tests were updated to exercise the new fields.

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title "feat: Billing page redesign plus credits" clearly and succinctly captures the primary work in this PR — a billing page redesign and added/changed credits functionality — which matches the file-level changes (billing page, BillingCredits components, translations, and billing service updates). It is concise, on-topic, and suitable for scanable git history.
Description Check ✅ Passed The PR description states the intent ("Updates to the new designs for billing + credits") and includes a demo image, which is directly related to the changeset; although brief, it is on-topic and satisfies the lenient description check.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/billing-redesign-settings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@keithwillcode keithwillcode added consumer core area: core, team members only labels Sep 18, 2025
@dosubot dosubot bot added billing area: billing, stripe, payments, paypal, get paid ✨ feature New feature or request labels Sep 18, 2025
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: 4

Caution

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

⚠️ Outside diff range comments (1)
packages/features/ee/billing/credit-service.test.ts (1)

406-424: Test no longer validates intended policy; passes accidentally.

This asserts “0 when not active,” but passes only because STRIPE_TEAM_MONTHLY_PRICE_ID isn’t set—not because of subscription gating (now removed). Either restore gating (preferred) or update the test to reflect the new policy by explicitly setting a priceId and expecting non‑zero for “trialing,” or delete the test.

Example (if policy is to allow credits while trialing):

- it("should return 0 if subscription is not active", async () => {
+ it("should compute credits even when subscription is trialing (policy change)", async () => {
+   vi.stubEnv("STRIPE_TEAM_MONTHLY_PRICE_ID", "price_team_monthly");
    // ...mock team + 1 accepted member
-   // subscriptionStatus: "trialing"
-   expect(result).toBe(0);
+   // subscriptionStatus: "trialing"
+   // mock getPrice to 1000
+   expect(result).toBe(500);
 });
🧹 Nitpick comments (10)
packages/features/ee/billing/credit-service.ts (3)

676-680: Avoid the second round‑trip: fetch all logs once and split in memory.

Two nearly identical queries per call add latency and DB load in a frequently used path. Fetch once (no creditType filter) and compute monthly/additional usages locally.

Example:

-    const creditBalance = await CreditsRepository.findCreditBalanceWithExpenseLogs(
-      { teamId, creditType: CreditType.MONTHLY },
-      tx
-    );
-
-    const additionalCreditBalance = await CreditsRepository.findCreditBalanceWithExpenseLogs(
-      { teamId, creditType: CreditType.ADDITIONAL },
-      tx
-    );
+    const creditBalance = await CreditsRepository.findCreditBalanceWithExpenseLogs({ teamId }, tx);

Then:

const monthlyLogs = creditBalance?.expenseLogs.filter(l => l.creditType === CreditType.MONTHLY) ?? [];
const additionalLogs = creditBalance?.expenseLogs.filter(l => l.creditType === CreditType.ADDITIONAL) ?? [];
const totalMonthlyCreditsUsed = monthlyLogs.reduce((s, l) => s + (l.credits ?? 0), 0);
const totalAdditionalCreditsUsed = additionalLogs.reduce((s, l) => s + (l.credits ?? 0), 0);
const additionalCredits = creditBalance?.additionalCredits ?? 0;

685-691: Nit: keep source of additionalCredits consistent with usage-scope.

You read additionalCredits off the MONTHLY-scoped query variable. While it currently works (field lives on creditBalance), it’s clearer to read it from the same object used for logs (or after the single-query refactor above).

-    const additionalCredits = creditBalance?.additionalCredits ?? 0;
+    const additionalCredits = (creditBalance /* or additionalCreditBalance */)?.additionalCredits ?? 0;

412-431: Low-balance threshold still based only on monthly credits. Confirm behavior.

Now that availability and remaining computations combine monthly+additional, consider whether warningLimit should also be derived from the combined pool to avoid premature/late warnings.

Would you like me to prototype using totalMonthlyCredits + additionalCredits (or a tunable mix) here?

apps/web/public/static/locales/en/common.json (1)

3238-3243: Make team credits tip dynamic to avoid mismatch with backend.

Backend computes team credits per seat as 50% of Stripe price (unit_amount) in credits. Hardcoding “750” risks drift if price changes. Prefer a variable placeholder.

-  "credits_per_tip_teams": "You receive 750 credits per month, per team member",
+  "credits_per_tip_teams": "You receive {{creditsPerSeat}} credits per month, per team member"

Then pass creditsPerSeat from UI based on StripeBillingService.getPrice/env.

apps/web/modules/settings/billing/billing-view.tsx (2)

68-71: Encode query params in billingHref to avoid malformed/unsafe URLs

returnTo (and teamId) should be URL-encoded.

Apply:

-  const billingHref = teamId
-    ? `/api/integrations/stripepayment/portal?teamId=${teamId}&returnTo=${WEBAPP_URL}${returnTo}`
-    : `/api/integrations/stripepayment/portal?returnTo=${WEBAPP_URL}${returnTo}`;
+  const billingHref = teamId
+    ? `/api/integrations/stripepayment/portal?teamId=${encodeURIComponent(
+        teamId
+      )}&returnTo=${encodeURIComponent(`${WEBAPP_URL}${returnTo}`)}`
+    : `/api/integrations/stripepayment/portal?returnTo=${encodeURIComponent(
+        `${WEBAPP_URL}${returnTo}`
+      )}`;

78-79: Remove commented props noise

Inline commented props add churn with no effect.

-  // title={t("view_and_manage_billing_details")}
-  // description={t("view_and_edit_billing_details")}>
apps/web/modules/settings/billing/components/BillingCreditsSkeleton.tsx (1)

31-33: Use theme token for skeleton bar color

Hard-coded bg-gray-200 may not match themes. Prefer a tokened class used elsewhere for skeletons.

-                <div className="h-2 w-full rounded-md bg-gray-200" />
+                <div className="h-2 w-full rounded-md bg-subtle" />
apps/web/modules/settings/billing/components/BillingCredits.tsx (3)

194-198: Use correct remaining field with fallback

Display remaining for the same “forMonth” model you use above, falling back for older data.

-                <CreditRow
-                  label={t("remaining")}
-                  value={creditsData.credits.totalRemainingMonthlyCredits}
-                  isDashed={creditsData.credits.additionalCredits > 0}
-                />
+                <CreditRow
+                  label={t("remaining")}
+                  value={
+                    creditsData.credits.totalRemainingCreditsForMonth ??
+                    creditsData.credits.totalRemainingMonthlyCredits ??
+                    0
+                  }
+                  isDashed={creditsData.credits.additionalCredits > 0}
+                />

141-143: Sanitize filename for spaces and multiple whitespace

Replace all spaces, not just the first.

-        const filename = `credit-expense-log-${selectedMonth.value.toLowerCase().replace(" ", "-")}.csv`;
+        const filename = `credit-expense-log-${selectedMonth.value
+          .toLowerCase()
+          .replace(/\s+/g, "-")}.csv`;

253-255: Remove target on submit button

target has no effect with JS submit + router navigation and can confuse. Drop it.

-                <Button color="secondary" target="_blank" size="sm" type="submit" data-testid="buy-credits">
+                <Button color="secondary" size="sm" type="submit" data-testid="buy-credits">
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • 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 d70fa46 and ee5a999.

📒 Files selected for processing (7)
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/(org-admin-only)/billing/page.tsx (1 hunks)
  • apps/web/modules/settings/billing/billing-view.tsx (1 hunks)
  • apps/web/modules/settings/billing/components/BillingCredits.tsx (4 hunks)
  • apps/web/modules/settings/billing/components/BillingCreditsSkeleton.tsx (1 hunks)
  • apps/web/public/static/locales/en/common.json (3 hunks)
  • packages/features/ee/billing/credit-service.test.ts (10 hunks)
  • packages/features/ee/billing/credit-service.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Always use t() for text localization in frontend code; direct text embedding should trigger a warning

Files:

  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/(org-admin-only)/billing/page.tsx
  • apps/web/modules/settings/billing/components/BillingCreditsSkeleton.tsx
  • apps/web/modules/settings/billing/billing-view.tsx
  • apps/web/modules/settings/billing/components/BillingCredits.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/(org-admin-only)/billing/page.tsx
  • packages/features/ee/billing/credit-service.test.ts
  • apps/web/modules/settings/billing/components/BillingCreditsSkeleton.tsx
  • packages/features/ee/billing/credit-service.ts
  • apps/web/modules/settings/billing/billing-view.tsx
  • apps/web/modules/settings/billing/components/BillingCredits.tsx
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/(org-admin-only)/billing/page.tsx
  • packages/features/ee/billing/credit-service.test.ts
  • apps/web/modules/settings/billing/components/BillingCreditsSkeleton.tsx
  • packages/features/ee/billing/credit-service.ts
  • apps/web/modules/settings/billing/billing-view.tsx
  • apps/web/modules/settings/billing/components/BillingCredits.tsx
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/features/ee/billing/credit-service.test.ts
  • packages/features/ee/billing/credit-service.ts
🧠 Learnings (2)
📚 Learning: 2025-08-26T20:20:48.989Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22995
File: packages/features/ee/workflows/components/AgentConfigurationSheet.tsx:589-597
Timestamp: 2025-08-26T20:20:48.989Z
Learning: In calcom/cal.com PR #22995, the test call functionality in CallService already has proper credits validation via validateCreditsForTestCall method which uses CreditService.hasAvailableCredits and throws 403 HttpError when credits are insufficient. The credits enforcement is properly implemented on the backend for test calls.

Applied to files:

  • packages/features/ee/billing/credit-service.test.ts
📚 Learning: 2025-08-26T20:20:48.989Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22995
File: packages/features/ee/workflows/components/AgentConfigurationSheet.tsx:589-597
Timestamp: 2025-08-26T20:20:48.989Z
Learning: In calcom/cal.com PR #22995, the test call functionality already has proper credits validation implemented in CallService.validateCreditsForTestCall() method. This method is called early in createTestCall flow and uses CreditService.hasAvailableCredits() to check credits, throwing HttpError 403 when insufficient. The credits enforcement for test calls is already correctly implemented on the backend.

Applied to files:

  • packages/features/ee/billing/credit-service.test.ts
🧬 Code graph analysis (3)
packages/features/ee/billing/credit-service.test.ts (2)
packages/lib/server/repository/credits.ts (1)
  • CreditsRepository (6-242)
packages/features/ee/billing/credit-service.ts (1)
  • CreditService (55-701)
apps/web/modules/settings/billing/billing-view.tsx (2)
packages/ui/components/button/Button.tsx (1)
  • Button (221-349)
apps/web/modules/settings/billing/components/BillingCredits.tsx (1)
  • BillingCredits (78-305)
apps/web/modules/settings/billing/components/BillingCredits.tsx (3)
packages/features/auth/lib/next-auth-options.ts (1)
  • session (746-771)
packages/ui/components/progress-bar/ProgressBar.tsx (1)
  • ProgressBar (27-38)
packages/ui/components/button/Button.tsx (1)
  • Button (221-349)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Install dependencies / Yarn install & cache
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (10)
packages/features/ee/billing/credit-service.test.ts (3)

500-527: LGTM: Combined totals math is correct and well covered.

Aggregation of monthly/additional usages and remaining totals matches production logic and covers edge cases.


531-552: LGTM: Handles no-expense scenarios cleanly (zeros).

Good defaults and expectations.


554-607: LGTM: Additional scenarios (non‑zero/zero additional credits) are covered.

Nice coverage for varying additional balances.

apps/web/public/static/locales/en/common.json (2)

3428-3429: LGTM: New generic labels.

“Remaining” and “Total” keys look good and reusable.


903-904: LGTM: Copy tweak without ellipsis.

Key name and value are clear.

apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/(org-admin-only)/billing/page.tsx (1)

29-29: LGTM: Removing the shell header border fits the redesign.

No functional impact; translations are used correctly.

apps/web/modules/settings/billing/components/BillingCreditsSkeleton.tsx (1)

6-77: Skeleton structure aligns with new credits UI

Layout and sections mirror the live component well. Looks good.

apps/web/modules/settings/billing/components/BillingCredits.tsx (2)

56-76: Month options generation looks good

Uses dayjs.utc and caps at 12 months; non-hot path. No issues.


292-299: Localize “Learn more” and add rel for external link

Replace the hardcoded string with t("learn_more") and add rel="noopener noreferrer" (the learn_more key already exists under apps/web/public/static/locales/*/common.json).

-              <Link
+              <Link
                 key="Credit System"
                 className="underline underline-offset-2"
-                target="_blank"
+                target="_blank"
+                rel="noopener noreferrer"
                 href="https://cal.com/help/billing-and-usage/messaging-credits">
-                Learn more
+                {t("learn_more")}
               </Link>,
apps/web/modules/settings/billing/billing-view.tsx (1)

29-41: Do not remove CtaRow — it's imported and used

Imported at apps/web/modules/settings/platform/billing/billing-view.tsx:19 and used at lines 91, 101, 111, 123; keep it (or refactor to a shared component if desired).

Likely an incorrect or invalid review comment.

Comment on lines +90 to 92
<Button color="primary" href={billingHref} target="_blank" size="sm" EndIcon="external-link">
{t("billing_portal")}
</Button>
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

Add rel="noopener noreferrer" to external link

Prevents tabnabbing and drops referrer.

-          <Button color="primary" href={billingHref} target="_blank" size="sm" EndIcon="external-link">
+          <Button color="primary" href={billingHref} target="_blank" rel="noopener noreferrer" size="sm" EndIcon="external-link">
📝 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
<Button color="primary" href={billingHref} target="_blank" size="sm" EndIcon="external-link">
{t("billing_portal")}
</Button>
<Button color="primary" href={billingHref} target="_blank" rel="noopener noreferrer" size="sm" EndIcon="external-link">
{t("billing_portal")}
</Button>
🤖 Prompt for AI Agents
In apps/web/modules/settings/billing/billing-view.tsx around lines 90 to 92, the
external link Button is missing rel="noopener noreferrer"; add rel="noopener
noreferrer" to the Button so the rendered anchor includes it (if the Button
component doesn't forward rel, replace it with an <a> tag or set component="a"
and pass rel, href and target accordingly) to prevent tabnabbing and drop the
referrer.

Comment on lines 594 to 597
// if (subscriptionStatus !== "active" && subscriptionStatus !== "past_due") {
// return 0;
// }

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

Re-introduce subscription gating or guard behind a feature flag.

By commenting out the subscription-status check, trialing/canceled teams will accrue monthly credits if STRIPE_TEAM_MONTHLY_PRICE_ID/ORG_MONTHLY_CREDITS are set. That’s a material policy change with revenue/abuse risk. If intentional, gate it behind an env flag and default to the previous behavior.

Apply:

-    // if (subscriptionStatus !== "active" && subscriptionStatus !== "past_due") {
-    //   return 0;
-    // }
+    const IGNORE_SUB_STATUS =
+      (process.env.CREDITS_IGNORE_SUBSCRIPTION_STATUS ?? "false").toLowerCase() === "true";
+    if (!IGNORE_SUB_STATUS && subscriptionStatus !== "active" && subscriptionStatus !== "past_due") {
+      return 0;
+    }
📝 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
// if (subscriptionStatus !== "active" && subscriptionStatus !== "past_due") {
// return 0;
// }
const IGNORE_SUB_STATUS =
(process.env.CREDITS_IGNORE_SUBSCRIPTION_STATUS ?? "false").toLowerCase() === "true";
if (!IGNORE_SUB_STATUS && subscriptionStatus !== "active" && subscriptionStatus !== "past_due") {
return 0;
}

@vercel
Copy link

vercel bot commented Sep 18, 2025

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

2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
cal Ignored Ignored Sep 19, 2025 3:35pm
cal-eu Ignored Ignored Sep 19, 2025 3:35pm

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

🧹 Nitpick comments (1)
packages/features/ee/billing/credit-service.ts (1)

676-679: Reduce DB round‑trips: fetch once and split in code.

Two calls to findCreditBalanceWithExpenseLogs hit the same row; only logs differ by creditType. Prefer a single call without creditType and partition logs in memory to cut one query per request.

If helpful, I can provide a concrete refactor that fetches all logs once and derives monthly/additional usage locally.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • 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 ee5a999 and da4d6cc.

📒 Files selected for processing (1)
  • packages/features/ee/billing/credit-service.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/features/ee/billing/credit-service.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/features/ee/billing/credit-service.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/features/ee/billing/credit-service.ts
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Type check / check-types
  • GitHub Check: Tests / Unit
  • GitHub Check: Linters / lint

Comment on lines 685 to 691
const totalAdditionalCreditsUsed =
additionalCreditBalance?.expenseLogs.reduce((sum, log) => sum + (log?.credits ?? 0), 0) || 0;

const additionalCredits = creditBalance?.additionalCredits ?? 0;
const totalCreditsForMonth = totalMonthlyCredits + additionalCredits;
const totalCreditsUsedThisMonth = totalMonthlyCreditsUsed + totalAdditionalCreditsUsed;

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

Fix combined totals: double-subtracts additional usage → wrong “remaining” and progress.

You add additionalCredits (a current balance) to total, and also subtract additional usage this month. Because the balance already reflects usage, this undercounts remaining. Include additional used in the total to keep: remaining = monthlyRemaining + additionalBalance, and progress = (monthlyUsed + additionalUsed)/total.

Apply:

-    const additionalCredits = creditBalance?.additionalCredits ?? 0;
-    const totalCreditsForMonth = totalMonthlyCredits + additionalCredits;
-    const totalCreditsUsedThisMonth = totalMonthlyCreditsUsed + totalAdditionalCreditsUsed;
+    const additionalCredits = additionalCreditBalance?.additionalCredits ?? 0;
+    // Include additional usage in the total to avoid double‑subtracting it in "used".
+    const totalCreditsForMonth = totalMonthlyCredits + additionalCredits + totalAdditionalCreditsUsed;
+    const totalCreditsUsedThisMonth = totalMonthlyCreditsUsed + totalAdditionalCreditsUsed;
 
     return {
       totalMonthlyCredits,
       totalRemainingMonthlyCredits: Math.max(totalMonthlyCredits - totalMonthlyCreditsUsed, 0),
       additionalCredits,
       totalCreditsForMonth,
       totalCreditsUsedThisMonth,
       totalRemainingCreditsForMonth: Math.max(totalCreditsForMonth - totalCreditsUsedThisMonth, 0),
     };

Also applies to: 695-699

🤖 Prompt for AI Agents
In packages/features/ee/billing/credit-service.ts around lines 685-691 (and
similarly lines 695-699), the combined totals double-subtract additional usage
by adding additionalCredits (current balance) to totalCreditsForMonth while also
subtracting totalAdditionalCreditsUsed when computing remaining/progress;
instead compute totals such that remaining = monthlyRemaining +
additionalCredits and progress uses numerator (monthlyUsed +
totalAdditionalCreditsUsed) and denominator (monthlyAllowance +
additionalCredits), i.e., stop subtracting additional usage twice—include
additionalUsed only in the used side and keep additionalCredits as the balance
side when calculating remaining and progress.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
packages/features/ee/billing/credit-service.ts (2)

594-596: Re-evaluate removing subscription status check or document the business decision.

The subscription status check is commented out but the code still includes past_due in the condition. This creates confusion about the intended behavior.

If allowing inactive subscriptions to accrue credits is intentional, document it clearly and consider using a feature flag. Otherwise, uncomment the check:

-    if (subscriptionStatus !== "active" && subscriptionStatus !== "past_due") {
-      return 0;
-    }
+    // Allow credits for active and past_due subscriptions only
+    if (subscriptionStatus !== "active" && subscriptionStatus !== "past_due") {
+      return 0;
+    }

694-696: Fix incorrect credit totals calculation causing wrong remaining balance.

The calculation double-counts usage from additional credits. The additionalCredits field represents the current balance (already reduced by usage), but you're also subtracting totalAdditionalCreditsUsed. This leads to incorrect remaining credits calculation.

The correct approach should be:

  • totalCreditsForMonth = total monthly allowance + (initial additional credits for the month)
  • totalCreditsUsedThisMonth = monthly usage + additional usage
  • totalRemainingCreditsForMonth = remaining monthly + current additional balance

Apply this fix:

-    const additionalCredits = creditBalance?.additionalCredits ?? 0;
-    const totalCreditsForMonth = totalMonthlyCredits + additionalCredits;
+    const additionalCredits = creditBalance?.additionalCredits ?? 0;
+    // To get the total credits available for the month, we need to add back the used additional credits
+    // since additionalCredits is the current balance (already reduced by usage)
+    const totalCreditsForMonth = totalMonthlyCredits + additionalCredits + totalAdditionalCreditsUsed;
     const totalCreditsUsedThisMonth = totalMonthlyCreditsUsed + totalAdditionalCreditsUsed;
🧹 Nitpick comments (1)
packages/features/ee/billing/credit-service.ts (1)

654-656: Initialize combined credit fields with actual values for users.

For user-level credits, the combined totals are hardcoded to 0, but additionalCredits has the actual balance. The totals should reflect the additional credits available.

Apply this diff to properly calculate user-level totals:

       return {
         totalMonthlyCredits: 0,
         totalRemainingMonthlyCredits: 0,
         additionalCredits: creditBalance?.additionalCredits ?? 0,
-        totalCreditsForMonth: 0,
+        totalCreditsForMonth: creditBalance?.additionalCredits ?? 0,
         totalCreditsUsedThisMonth: 0,
-        totalRemainingCreditsForMonth: 0,
+        totalRemainingCreditsForMonth: creditBalance?.additionalCredits ?? 0,
       };
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • 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 da4d6cc and 7586e87.

📒 Files selected for processing (3)
  • apps/web/modules/settings/billing/billing-view.tsx (1 hunks)
  • apps/web/modules/settings/billing/components/BillingCredits.tsx (4 hunks)
  • packages/features/ee/billing/credit-service.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/modules/settings/billing/components/BillingCredits.tsx
  • apps/web/modules/settings/billing/billing-view.tsx
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/features/ee/billing/credit-service.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/features/ee/billing/credit-service.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/features/ee/billing/credit-service.ts
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Tests / Unit
  • GitHub Check: Linters / lint
  • GitHub Check: Type check / check-types
🔇 Additional comments (1)
packages/features/ee/billing/credit-service.ts (1)

682-685: Incorrect — the fetch is fine; additionalCredits lives on the single CreditBalance row

The Prisma model shows additionalCredits is a field on CreditBalance and findCreditBalanceWithExpenseLogs({ creditType: ADDITIONAL }) is only fetching expenseLogs filtered by type — reading creditBalance?.additionalCredits is correct. Suggest optionally using additionalCreditBalance?.additionalCredits or renaming variables to avoid confusion.

Likely an incorrect or invalid review comment.

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

♻️ Duplicate comments (2)
apps/web/modules/settings/billing/components/BillingCredits.tsx (2)

33-38: Fix type mismatch for CreditRow (isDashed) and clean up class duplication

isDashed is used but not declared in props; also "mt-1" is duplicated in the class list.

-type CreditRowProps = {
-  label: string;
-  value: number;
-  isBold?: boolean;
-  className?: string;
-};
+type CreditRowProps = {
+  label: string;
+  value: number;
+  isBold?: boolean;
+  isDashed?: boolean;
+  className?: string;
+};

-const CreditRow = ({ label, value, isBold = false, isDashed, className = "" }: CreditRowProps) => {
+const CreditRow = ({ label, value, isBold = false, isDashed = false, className = "" }: CreditRowProps) => {
   const numberFormatter = new Intl.NumberFormat();
   return (
-    <div
-      className={classNames(
-        `mt-1 flex justify-between`,
-        isDashed ? "border-subtle border-t border-dashed" : "mt-1",
-        className
-      )}>
+    <div
+      className={classNames(
+        "flex justify-between",
+        isDashed ? "mt-2 border-t border-dashed border-subtle pt-1" : "mt-1",
+        className
+      )}>
       <span
         className={classNames("text-sm", isBold ? "font-semibold" : "text-subtle font-medium leading-tight")}>
         {label}
       </span>
       <span
         className={classNames(`text-sm`, isBold ? "font-semibold" : "text-subtle font-medium leading-tight")}>
         {numberFormatter.format(value)}
       </span>
     </div>
   );
 };

Optional nit: memoize the formatter to avoid recreating per row render:

-  const numberFormatter = new Intl.NumberFormat();
+  const numberFormatter = useMemo(() => new Intl.NumberFormat(), []);

Also applies to: 40-57


165-171: Respect zero values; remove boolean OR fallbacks

(x ?? 0) || y incorrectly treats 0 as falsy and falls back to y. Use nullish coalescing only.

-  const totalCredits =
-    (creditsData.credits.totalCreditsForMonth ?? 0) ||
-    creditsData.credits.totalMonthlyCredits + creditsData.credits.additionalCredits;
-  const totalUsed =
-    (creditsData.credits.totalCreditsUsedThisMonth ?? 0) ||
-    totalCredits - (creditsData.credits.totalRemainingCreditsForMonth ?? 0);
+  const totalCredits =
+    creditsData.credits.totalCreditsForMonth ??
+    ((creditsData.credits.totalMonthlyCredits ?? 0) + (creditsData.credits.additionalCredits ?? 0));
+  const totalUsed =
+    creditsData.credits.totalCreditsUsedThisMonth ??
+    (totalCredits - (creditsData.credits.totalRemainingCreditsForMonth ?? 0));
🧹 Nitpick comments (4)
apps/web/modules/settings/billing/components/BillingCredits.tsx (4)

146-147: Make filename sanitization robust (replace all whitespace)

Replace single-space replacement with global whitespace replacement.

-        const filename = `credit-expense-log-${selectedMonth.value.toLowerCase().replace(" ", "-")}.csv`;
+        const filename = `credit-expense-log-${selectedMonth.value.toLowerCase().replace(/\s+/g, "-")}.csv`;

200-203: Coalesce remaining credits to a number

Prevent NaN/undefined rendering and keep types consistent with CreditRowProps.

-                <CreditRow
-                  label={t("remaining")}
-                  value={creditsData.credits.totalRemainingCreditsForMonth}
-                  isDashed={creditsData.credits.additionalCredits > 0}
-                />
+                <CreditRow
+                  label={t("remaining")}
+                  value={creditsData.credits.totalRemainingCreditsForMonth ?? 0}
+                  isDashed={(creditsData.credits.additionalCredits ?? 0) > 0}
+                />

240-254: Avoid mixing RHF register with manual setValue on onChange

You already use valueAsNumber; RHF will parse to number. The extra onChange risks double state writes and validation quirks.

                   <TextField
                     required
                     type="number"
                     {...register("quantity", {
                       required: t("error_required_field"),
                       min: { value: 50, message: t("minimum_of_credits_required") },
                       valueAsNumber: true,
                     })}
                     label=""
                     containerClassName="w-full -mt-1"
                     size="sm"
-                    onChange={(e) => setValue("quantity", Number(e.target.value))}
                     min={50}
                     addOnSuffix={<>{t("credits")}</>}
                   />

258-260: Remove invalid target attribute on a button

target has no effect on a and adds noise.

-                <Button color="secondary" target="_blank" size="sm" type="submit" data-testid="buy-credits">
+                <Button color="secondary" size="sm" type="submit" data-testid="buy-credits">
                   {t("buy")}
                 </Button>

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • 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 7586e87 and a90a13b.

📒 Files selected for processing (1)
  • apps/web/modules/settings/billing/components/BillingCredits.tsx (4 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Always use t() for text localization in frontend code; direct text embedding should trigger a warning

Files:

  • apps/web/modules/settings/billing/components/BillingCredits.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • apps/web/modules/settings/billing/components/BillingCredits.tsx
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • apps/web/modules/settings/billing/components/BillingCredits.tsx
🧬 Code graph analysis (1)
apps/web/modules/settings/billing/components/BillingCredits.tsx (3)
packages/features/auth/lib/next-auth-options.ts (1)
  • session (746-771)
packages/ui/components/progress-bar/ProgressBar.tsx (1)
  • ProgressBar (27-38)
packages/ui/components/button/Button.tsx (1)
  • Button (221-349)
🔇 Additional comments (1)
apps/web/modules/settings/billing/components/BillingCredits.tsx (1)

119-123: No change required — teamId is optional and server handles undefined.
ZGetAllCreditsSchema declares teamId as optional and getAllCredits.handler enforces admin when teamId is provided and correctly handles the undefined case (membership check / nullable teamId), so calling useQuery({ teamId }, { enabled: shouldRender }) is safe.

Comment on lines +293 to +305
<ServerTrans
t={t}
i18nKey="credit_worth_description"
components={[
<Link
key="Credit System"
className="underline underline-offset-2"
target="_blank"
href="https://cal.com/help/billing-and-usage/messaging-credits">
Learn more
</Link>,
]}
/>
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

Add rel for external link security and localize link label

target="_blank" should include rel="noopener noreferrer". Also replace literal "Learn more" with a localized string.

           <ServerTrans
             t={t}
             i18nKey="credit_worth_description"
             components={[
               <Link
                 key="Credit System"
                 className="underline underline-offset-2"
-                target="_blank"
-                href="https://cal.com/help/billing-and-usage/messaging-credits">
-                Learn more
+                target="_blank"
+                rel="noopener noreferrer"
+                href="https://cal.com/help/billing-and-usage/messaging-credits">
+                {t("learn_more")}
               </Link>,
             ]}
           />
📝 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
<ServerTrans
t={t}
i18nKey="credit_worth_description"
components={[
<Link
key="Credit System"
className="underline underline-offset-2"
target="_blank"
href="https://cal.com/help/billing-and-usage/messaging-credits">
Learn more
</Link>,
]}
/>
<ServerTrans
t={t}
i18nKey="credit_worth_description"
components={[
<Link
key="Credit System"
className="underline underline-offset-2"
target="_blank"
rel="noopener noreferrer"
href="https://cal.com/help/billing-and-usage/messaging-credits">
{t("learn_more")}
</Link>,
]}
/>
🤖 Prompt for AI Agents
In apps/web/modules/settings/billing/components/BillingCredits.tsx around lines
293 to 305, the external Link opens in a new tab but is missing rel="noopener
noreferrer" and uses a hardcoded "Learn more" label; update the Link props to
include rel="noopener noreferrer" for security and replace the literal label
with a localized string via the existing i18n function (e.g. use t('learn_more')
or the appropriate translation key) so the link text is localized.

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

♻️ Duplicate comments (1)
apps/web/public/static/locales/en/common.json (1)

903-904: Typo fix applied; ensure cross-file consistency.

add_members_no_ellipsis looks correct and replaces the previous misspelling. Please confirm all usages and non‑EN locale keys were updated too.

#!/bin/bash
rg -nP '\badd_members_no_elipsis\b' -C2
rg -nP '\badd_members_no_ellipsis\b' -C2
fd common.json apps/**/public/static/locales | xargs -I{} rg -nP '"add_members_no_ellipsis"' {}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • 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 a7806cf and 2d8bcc8.

📒 Files selected for processing (2)
  • apps/web/modules/settings/billing/components/BillingCredits.tsx (4 hunks)
  • apps/web/public/static/locales/en/common.json (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/modules/settings/billing/components/BillingCredits.tsx
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Production builds / Build API v2
  • GitHub Check: Production builds / Build Web App
  • GitHub Check: Production builds / Build Atoms
  • GitHub Check: Tests / Unit
  • GitHub Check: Production builds / Build API v1
  • GitHub Check: Type check / check-types
🔇 Additional comments (1)
apps/web/public/static/locales/en/common.json (1)

3431-3432: Verify scope of generic keys "remaining" and "total".

Both keys are defined only in apps/web/public/static/locales/en/common.json (lines 3431–3432) and no usages or same keys were found elsewhere in the repo; confirm intended global use — if they’re credits-specific, namespace them (e.g., credits.remaining or total_credits_remaining).

Comment on lines +3238 to 3247
"credits_used": "Credits used",
"total_credits_remaining": "Total remaining",
"credits_per_tip_org": "You receive 1000 credits per month, per team member",
"credits_per_tip_teams": "You receive 750 credits per month, per team member",
"view_and_manage_credits": "View and manage credits for sending SMS messages",
"view_and_manage_credits_description": "View and manage credits for sending SMS messages. One credit is worth 1¢ (USD). <0>Learn more</0>",
"credit_worth_description": "One credit is worth 1¢ (USD). <0>Learn more</0>",
"buy_additional_credits": "Buy additional credits ($0.01 per credit)",
"view_additional_credits_expense_tip": "You can view Additional credit spending in your expense log",
"overview": "Overview",
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

🧩 Analysis chain

Credits copy: avoid hard-coded pricing and duplicated “credit worth” sentence.

  • Don’t hard-code USD amounts; use ICU currency placeholders.
  • credit_worth_description duplicates the “One credit is worth 1¢ …” sentence from view_and_manage_credits_description — keep it in one place.
  • Consider neutralizing “for sending SMS messages” if credits also cover web calls; otherwise, ignore this note.

Apply this diff to de-duplicate and parameterize currency, plus minor capitalization tweak:

-  "view_and_manage_credits_description": "View and manage credits for sending SMS messages. One credit is worth 1¢ (USD). <0>Learn more</0>",
+  "view_and_manage_credits_description": "View and manage credits for sending SMS messages. <0>Learn more</0>",
-  "credit_worth_description": "One credit is worth 1¢ (USD). <0>Learn more</0>",
+  "credit_worth_description": "One credit is worth {{pricePerCredit, currency}} (USD). <0>Learn more</0>",
-  "buy_additional_credits": "Buy additional credits ($0.01 per credit)",
+  "buy_additional_credits": "Buy additional credits ({{pricePerCredit, currency}} per credit)",
-  "view_additional_credits_expense_tip": "You can view Additional credit spending in your expense log",
+  "view_additional_credits_expense_tip": "You can view additional credit spending in your Expense Log"

Optional (future): replace the plan-specific strings

  • credits_per_tip_org
  • credits_per_tip_teams
    with a single parameterized string:
    "You receive {{creditsPerMember}} credits per month, per team member".

🏁 Script executed:

#!/bin/bash
# Check if credits are used for web calls and align copy if needed
rg -nP 'web_call_credits_info|web_call_no_credits|credits_required' -C2

Length of output: 72742


Credits copy: de-duplicate and parameterize per-credit price

  • Don’t hard-code USD amounts; use an ICU currency placeholder (e.g. {{pricePerCredit, currency}}).
  • Remove the duplicated “One credit is worth …” sentence from view_and_manage_credits_description and keep it only in credit_worth_description.
  • Neutralize “for sending SMS messages” because credits are also consumed by web calls (see web_call_credits_info keys).
  • Minor capitalization: unify "expense log" / "Expense Log" as product style requires.

Apply this diff to apps/web/public/static/locales/en/common.json:

-  "view_and_manage_credits_description": "View and manage credits for sending SMS messages. One credit is worth 1¢ (USD). <0>Learn more</0>",
+  "view_and_manage_credits_description": "View and manage credits for credit-consuming features. <0>Learn more</0>",
-  "credit_worth_description": "One credit is worth 1¢ (USD). <0>Learn more</0>",
+  "credit_worth_description": "One credit is worth {{pricePerCredit, currency}}. <0>Learn more</0>",
-  "buy_additional_credits": "Buy additional credits ($0.01 per credit)",
+  "buy_additional_credits": "Buy additional credits ({{pricePerCredit, currency}} per credit)",
-  "view_additional_credits_expense_tip": "You can view Additional credit spending in your expense log",
+  "view_additional_credits_expense_tip": "You can view additional credit spending in your Expense Log"

Optional: collapse credits_per_tip_org / credits_per_tip_teams into one parameterized string ("You receive {{creditsPerMember}} credits per month, per team member").

📝 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
"credits_used": "Credits used",
"total_credits_remaining": "Total remaining",
"credits_per_tip_org": "You receive 1000 credits per month, per team member",
"credits_per_tip_teams": "You receive 750 credits per month, per team member",
"view_and_manage_credits": "View and manage credits for sending SMS messages",
"view_and_manage_credits_description": "View and manage credits for sending SMS messages. One credit is worth 1¢ (USD). <0>Learn more</0>",
"credit_worth_description": "One credit is worth 1¢ (USD). <0>Learn more</0>",
"buy_additional_credits": "Buy additional credits ($0.01 per credit)",
"view_additional_credits_expense_tip": "You can view Additional credit spending in your expense log",
"overview": "Overview",
"credits_used": "Credits used",
"total_credits_remaining": "Total remaining",
"credits_per_tip_org": "You receive 1000 credits per month, per team member",
"credits_per_tip_teams": "You receive 750 credits per month, per team member",
"view_and_manage_credits": "View and manage credits for sending SMS messages",
"view_and_manage_credits_description": "View and manage credits for credit-consuming features. <0>Learn more</0>",
"credit_worth_description": "One credit is worth {{pricePerCredit, currency}}. <0>Learn more</0>",
"buy_additional_credits": "Buy additional credits ({{pricePerCredit, currency}} per credit)",
"view_additional_credits_expense_tip": "You can view additional credit spending in your Expense Log",
"overview": "Overview",
🤖 Prompt for AI Agents
In apps/web/public/static/locales/en/common.json around lines 3238 to 3247, the
credits copy hard-codes a USD amount, duplicates the "One credit is worth…"
sentence, and ties credits specifically to "sending SMS messages" plus
inconsistent capitalization of "expense log"; replace hard-coded "$0.01 per
credit" with an ICU currency placeholder like {{pricePerCredit, currency}},
remove the duplicated sentence from view_and_manage_credits_description so it
only remains in credit_worth_description, neutralize the SMS-specific phrase in
view_and_manage_credits (e.g., remove "for sending SMS messages" or replace with
a neutral phrase like "for certain actions"), normalize "expense log"
capitalization to the product style (use "expense log"), and optionally
consolidate credits_per_tip_org and credits_per_tip_teams into a single
parameterized key such as "You receive {{creditsPerMember}} credits per month,
per team member".

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
apps/web/modules/settings/billing/components/BillingCredits.tsx (2)

172-178: Respect 0 values; replace || with nullish coalescing and safe addends

Current logic wrongly falls back when totals are 0; also risks NaN if a part is undefined.

-  const totalCredits =
-    (creditsData.credits.totalCreditsForMonth ?? 0) ||
-    creditsData.credits.totalMonthlyCredits + creditsData.credits.additionalCredits;
-  const totalUsed =
-    (creditsData.credits.totalCreditsUsedThisMonth ?? 0) ||
-    totalCredits - (creditsData.credits.totalRemainingCreditsForMonth ?? 0);
+  const totalCredits =
+    creditsData.credits.totalCreditsForMonth ??
+    ((creditsData.credits.totalMonthlyCredits ?? 0) + (creditsData.credits.additionalCredits ?? 0));
+  const totalUsed =
+    creditsData.credits.totalCreditsUsedThisMonth ??
+    (totalCredits - (creditsData.credits.totalRemainingCreditsForMonth ?? 0));

303-318: Add rel to external link and localize label (per guidelines)

Security: add rel="noopener noreferrer". i18n: replace literal "Learn more" with t("learn_more").

               <Link
                 key="Credit System"
                 className="underline underline-offset-2"
-                target="_blank"
-                href="https://cal.com/help/billing-and-usage/messaging-credits">
-                Learn more
+                target="_blank"
+                rel="noopener noreferrer"
+                href="https://cal.com/help/billing-and-usage/messaging-credits">
+                {t("learn_more")}
               </Link>,
🧹 Nitpick comments (3)
apps/web/modules/settings/billing/components/BillingCredits.tsx (3)

43-66: Tighten CreditRow underline/margin classes

Avoid redundant margins (my-1 plus fallback mt-1). Simplify class logic.

-    <div
-      className={classNames(
-        `my-1 flex justify-between`,
-        underline === "dashed"
-          ? "border-subtle border-b border-dashed"
-          : underline === "solid"
-          ? "border-subtle border-b border-solid"
-          : "mt-1",
-        className
-      )}>
+    <div
+      className={classNames(
+        "flex justify-between",
+        underline ? "my-1 border-subtle border-b" : "my-1",
+        underline === "dashed" ? "border-dashed" : underline === "solid" ? "border-solid" : "",
+        className
+      )}>

200-204: Coalesce remaining credits to a number

Ensure formatter always receives a number.

-                  <CreditRow
-                    label={t("total_credits_remaining")}
-                    value={creditsData.credits.totalRemainingCreditsForMonth}
-                  />
+                  <CreditRow
+                    label={t("total_credits_remaining")}
+                    value={creditsData.credits.totalRemainingCreditsForMonth ?? 0}
+                  />

268-270: Remove target on a button-without-href

target has no effect on a and adds noise.

-                  <Button color="secondary" target="_blank" size="sm" type="submit" data-testid="buy-credits">
+                  <Button color="secondary" size="sm" type="submit" data-testid="buy-credits">

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • 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 2d8bcc8 and 76ad510.

📒 Files selected for processing (2)
  • apps/web/modules/settings/billing/components/BillingCredits.tsx (4 hunks)
  • apps/web/public/static/locales/en/common.json (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/public/static/locales/en/common.json
🧰 Additional context used
📓 Path-based instructions (3)
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Always use t() for text localization in frontend code; direct text embedding should trigger a warning

Files:

  • apps/web/modules/settings/billing/components/BillingCredits.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • apps/web/modules/settings/billing/components/BillingCredits.tsx
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • apps/web/modules/settings/billing/components/BillingCredits.tsx
🧬 Code graph analysis (1)
apps/web/modules/settings/billing/components/BillingCredits.tsx (3)
packages/features/auth/lib/next-auth-options.ts (1)
  • session (746-771)
packages/ui/components/progress-bar/ProgressBar.tsx (1)
  • ProgressBar (27-38)
packages/ui/components/button/Button.tsx (1)
  • Button (221-349)
🔇 Additional comments (1)
apps/web/modules/settings/billing/components/BillingCredits.tsx (1)

214-219: Guard teams route when teamId is undefined

Avoid generating /settings/teams/undefined/members.

-                          : `/settings/teams/${teamId}/members`
+                          : teamId
+                          ? `/settings/teams/${teamId}/members`
+                          : "/settings/teams"

<p className="text-subtle text-sm font-medium leading-tight">
{orgSlug ? t("credits_per_tip_org") : t("credits_per_tip_teams")}
</p>
<Button
Copy link
Member

Choose a reason for hiding this comment

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

I think this should open the dialog and not redirect to /members
Screenshot 2025-09-19 at 10 50 36 AM

Copy link
Member Author

Choose a reason for hiding this comment

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

@CarinaWolli I think this is out of scope right now - alright if i do this in a follow up?

<Label>{t("additional_credits")}</Label>
<div className="flex items-center gap-1">
<p className="text-sm font-semibold leading-none">
<span className="text-subtle font-medium">{t("current_balance")}</span>{" "}
Copy link
Member

Choose a reason for hiding this comment

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

Current balance should have a little more space below same as Additional Credits
Screenshot 2025-09-19 at 10 55 27 AM

<CreditRow label={t("credits_used")} value={totalUsed} underline="solid" />
<CreditRow
label={t("total_credits_remaining")}
value={creditsData.credits.totalRemainingCreditsForMonth}
Copy link
Member

Choose a reason for hiding this comment

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

This should only be monthly credits not monthly + additional

Screenshot 2025-09-19 at 11 02 06 AM

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (6)
packages/features/ee/billing/credit-service.ts (3)

654-657: User path: clarify that monthly fields are intentionally zero.

Returning zeros for totalCreditsForMonth/Used/Remaining on the user path is fine, but it’s easy to misinterpret downstream. Please document this behavior to prevent UI regressions.

Add a short JSDoc above _getAllCredits noting that for userId (no team), monthly fields are always 0 and only additionalCredits apply. If you prefer inline context, add a brief comment next to these properties.


664-667: Default (no userId/teamId): confirm null-object semantics.

Zeroing monthly fields here is OK as a null-object response. Consider a brief comment to make this explicit for callers.


686-689: Monthly-only totals: API naming may confuse consumers.

The new totals intentionally exclude additional credits (good: avoids double-subtract). To reduce ambiguity, consider renaming or documenting:

  • totalCreditsForMonth → totalMonthlyPlanCredits
  • totalCreditsUsedThisMonth → totalMonthlyUsed
  • totalRemainingCreditsForMonth → totalMonthlyRemaining

If renaming is too disruptive, add JSDoc on _getAllCreditsForTeam stating these are plan-only (exclude additionalCredits), while availableCredits elsewhere equals totalRemainingMonthlyCredits + additionalCredits.

Also applies to: 693-697

packages/features/ee/billing/credit-service.test.ts (3)

67-74: Top-level instance spy won’t affect new instances created in beforeEach.

You create a new CreditService in beforeEach, so this spy on the top-level instance is effectively dead. Switch to a prototype spy or move into beforeEach.

Apply:

-vi.spyOn(creditService, "_getAllCreditsForTeam").mockResolvedValue({
+vi.spyOn(CreditService.prototype, "_getAllCreditsForTeam").mockResolvedValue({
   totalMonthlyCredits: 10,
   totalRemainingMonthlyCredits: 5,
   additionalCredits: 0,
   totalCreditsForMonth: 10,
   totalCreditsUsedThisMonth: 5,
   totalRemainingCreditsForMonth: 5,
 });

502-513: Duplicate mockResolvedValueOnce calls add noise.

Each test path calls findCreditBalanceWithExpenseLogs once; the second mockResolvedValueOnce is unused. Simplify to a single mockResolvedValueOnce (or mockResolvedValue if reused).

Example:

-vi.mocked(CreditsRepository.findCreditBalanceWithExpenseLogs)
-  .mockResolvedValueOnce({ additionalCredits: 100, expenseLogs: [...] })
-  .mockResolvedValueOnce({ additionalCredits: 100, expenseLogs: [...] });
+vi.mocked(CreditsRepository.findCreditBalanceWithExpenseLogs)
+  .mockResolvedValueOnce({ additionalCredits: 100, expenseLogs: [...] });

Also applies to: 528-537, 553-567, 583-591


552-580: Test name no longer matches behavior.

This test asserts that monthly totals exclude additional credits. Rename for clarity.

-it("should calculate total credits including additional credits for the month", async () => {
+it("should keep additional credits separate from monthly totals", async () => {
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • 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 b69403c and e8158d4.

📒 Files selected for processing (3)
  • apps/web/modules/settings/billing/components/BillingCredits.tsx (4 hunks)
  • packages/features/ee/billing/credit-service.test.ts (10 hunks)
  • packages/features/ee/billing/credit-service.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/modules/settings/billing/components/BillingCredits.tsx
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/features/ee/billing/credit-service.test.ts
  • packages/features/ee/billing/credit-service.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/features/ee/billing/credit-service.test.ts
  • packages/features/ee/billing/credit-service.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/features/ee/billing/credit-service.test.ts
  • packages/features/ee/billing/credit-service.ts
🧠 Learnings (2)
📚 Learning: 2025-08-26T20:20:48.989Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22995
File: packages/features/ee/workflows/components/AgentConfigurationSheet.tsx:589-597
Timestamp: 2025-08-26T20:20:48.989Z
Learning: In calcom/cal.com PR #22995, the test call functionality in CallService already has proper credits validation via validateCreditsForTestCall method which uses CreditService.hasAvailableCredits and throws 403 HttpError when credits are insufficient. The credits enforcement is properly implemented on the backend for test calls.

Applied to files:

  • packages/features/ee/billing/credit-service.test.ts
📚 Learning: 2025-08-26T20:20:48.989Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22995
File: packages/features/ee/workflows/components/AgentConfigurationSheet.tsx:589-597
Timestamp: 2025-08-26T20:20:48.989Z
Learning: In calcom/cal.com PR #22995, the test call functionality already has proper credits validation implemented in CallService.validateCreditsForTestCall() method. This method is called early in createTestCall flow and uses CreditService.hasAvailableCredits() to check credits, throwing HttpError 403 when insufficient. The credits enforcement for test calls is already correctly implemented on the backend.

Applied to files:

  • packages/features/ee/billing/credit-service.test.ts
🧬 Code graph analysis (1)
packages/features/ee/billing/credit-service.test.ts (2)
packages/lib/server/repository/credits.ts (1)
  • CreditsRepository (6-242)
packages/features/ee/billing/credit-service.ts (1)
  • CreditService (55-699)
🔇 Additional comments (1)
packages/features/ee/billing/credit-service.test.ts (1)

240-243: Consistency check: new monthly fields in stubs.

Stubs include totalCreditsForMonth/Used/Remaining consistent with plan-only semantics—LGTM. Ensure any UI relying on these does not add additionalCredits again when computing progress.

Also applies to: 301-304, 334-337, 356-359, 793-796, 919-922

- Replace Button href with onClick handler to open MemberInvitationModal
- Add MemberInvitationModalWithoutMembers import and state management
- Maintain existing team/org context support
- Follow established modal usage patterns from other components
- Fix lint error by using undefined instead of empty arrow function

Co-Authored-By: [email protected] <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (6)
packages/features/ee/billing/credit-service.test.ts (6)

67-81: Shadowed instance spies — switch to prototype spies

These spies target a top-level CreditService instance that isn’t used (a new instance is created in beforeEach). Spy on the prototype to affect all instances.

Apply:

-vi.spyOn(creditService, "_getAllCreditsForTeam").mockResolvedValue({
+vi.spyOn(CreditService.prototype, "_getAllCreditsForTeam").mockResolvedValue({
   totalMonthlyCredits: 10,
   totalRemainingMonthlyCredits: 5,
   additionalCredits: 0,
   totalCreditsUsedThisMonth: 5,
 });

-vi.spyOn(creditService, "_getTeamWithAvailableCredits").mockResolvedValue({
+vi.spyOn(CreditService.prototype, "_getTeamWithAvailableCredits").mockResolvedValue({
   availableCredits: 3,
 });

-vi.spyOn(creditService, "_getAllCredits").mockResolvedValue({
+vi.spyOn(CreditService.prototype, "_getAllCredits").mockResolvedValue({
   additionalCredits: 1,
 });

293-299: Keep remaining monthly credits non-negative (mirror prod invariant)

prod clamps totalRemainingMonthlyCredits with Math.max(..., 0). Avoid returning -1 in tests to prevent misleading branches.

 vi.spyOn(CreditService.prototype, "_getAllCreditsForTeam").mockResolvedValue({
   totalMonthlyCredits: 500,
-  totalRemainingMonthlyCredits: -1,
+  totalRemainingMonthlyCredits: 0,
   additionalCredits: 0,
   totalCreditsUsedThisMonth: 501,
 });

492-504: Disambiguate batched logs by creditType in mocks

Two consecutive mocks likely represent MONTHLY then ADDITIONAL batches. Set creditType to reduce ambiguity and future breakage if aggregation changes.

 vi.mocked(CreditsRepository.findCreditBalanceWithExpenseLogs)
   .mockResolvedValueOnce({
     additionalCredits: 100,
     expenseLogs: [
-      { credits: 50, date: new Date() },
-      { credits: 30, date: new Date() },
+      { credits: 50, date: new Date(), creditType: CreditType.MONTHLY },
+      { credits: 30, date: new Date(), creditType: CreditType.MONTHLY },
     ],
   })
   .mockResolvedValueOnce({
     additionalCredits: 100,
-    expenseLogs: [{ credits: 80, date: new Date() }],
+    expenseLogs: [{ credits: 80, date: new Date(), creditType: CreditType.ADDITIONAL }],
   });

517-526: Type-complete empty-log mocks

Include creditType even when arrays are empty to keep mocks structurally consistent with repository select.

 .mockResolvedValueOnce({
   additionalCredits: 100,
-  expenseLogs: [],
+  expenseLogs: [],
 })
 .mockResolvedValueOnce({
   additionalCredits: 100,
-  expenseLogs: [],
+  expenseLogs: [],
 });
 // (Optional) if you keep a placeholder entry for structure:
-// expenseLogs: [{ credits: 0, date: new Date(), creditType: CreditType.MONTHLY }], 
+// expenseLogs: [{ credits: 0, date: new Date(), creditType: CreditType.MONTHLY }],

Also applies to: 535-536


538-565: Rename for intent + add creditType to logs

Name suggests “including additional credits”; assertions only reflect MONTHLY usage/remaining. Clarify name and annotate logs.

-it("should calculate total credits including additional credits for the month", async () => {
+it("should surface additionalCredits while monthly usage/remaining reflect MONTHLY logs", async () => {
   vi.mocked(CreditsRepository.findCreditBalanceWithExpenseLogs)
     .mockResolvedValueOnce({
       additionalCredits: 150,
       expenseLogs: [
-        { credits: 80, date: new Date() },
-        { credits: 40, date: new Date() },
+        { credits: 80, date: new Date(), creditType: CreditType.MONTHLY },
+        { credits: 40, date: new Date(), creditType: CreditType.MONTHLY },
       ],
     })
     .mockResolvedValueOnce({
       additionalCredits: 150,
       expenseLogs: [
-        { credits: 25, date: new Date() },
-        { credits: 15, date: new Date() },
+        { credits: 25, date: new Date(), creditType: CreditType.ADDITIONAL },
+        { credits: 15, date: new Date(), creditType: CreditType.ADDITIONAL },
       ],
     });

567-585: Add creditType to zero-additional-credits logs

Small consistency win for test readability.

 .mockResolvedValueOnce({
   additionalCredits: 0,
-  expenseLogs: [{ credits: 100, date: new Date() }],
+  expenseLogs: [{ credits: 100, date: new Date(), creditType: CreditType.MONTHLY }],
 })
 .mockResolvedValueOnce({
   additionalCredits: 0,
-  expenseLogs: [],
+  expenseLogs: [],
 });
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • 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 e8158d4 and 5f22b0c.

📒 Files selected for processing (3)
  • apps/web/modules/settings/billing/components/BillingCredits.tsx (5 hunks)
  • packages/features/ee/billing/credit-service.test.ts (10 hunks)
  • packages/features/ee/billing/credit-service.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/modules/settings/billing/components/BillingCredits.tsx
  • packages/features/ee/billing/credit-service.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/features/ee/billing/credit-service.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/features/ee/billing/credit-service.test.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/features/ee/billing/credit-service.test.ts
🧠 Learnings (2)
📚 Learning: 2025-08-26T20:20:48.989Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22995
File: packages/features/ee/workflows/components/AgentConfigurationSheet.tsx:589-597
Timestamp: 2025-08-26T20:20:48.989Z
Learning: In calcom/cal.com PR #22995, the test call functionality in CallService already has proper credits validation via validateCreditsForTestCall method which uses CreditService.hasAvailableCredits and throws 403 HttpError when credits are insufficient. The credits enforcement is properly implemented on the backend for test calls.

Applied to files:

  • packages/features/ee/billing/credit-service.test.ts
📚 Learning: 2025-08-26T20:20:48.989Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22995
File: packages/features/ee/workflows/components/AgentConfigurationSheet.tsx:589-597
Timestamp: 2025-08-26T20:20:48.989Z
Learning: In calcom/cal.com PR #22995, the test call functionality already has proper credits validation implemented in CallService.validateCreditsForTestCall() method. This method is called early in createTestCall flow and uses CreditService.hasAvailableCredits() to check credits, throwing HttpError 403 when insufficient. The credits enforcement for test calls is already correctly implemented on the backend.

Applied to files:

  • packages/features/ee/billing/credit-service.test.ts
🧬 Code graph analysis (1)
packages/features/ee/billing/credit-service.test.ts (2)
packages/features/ee/billing/credit-service.ts (1)
  • CreditService (55-692)
packages/lib/server/repository/credits.ts (1)
  • CreditsRepository (6-242)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Install dependencies / Yarn install & cache
🔇 Additional comments (3)
packages/features/ee/billing/credit-service.test.ts (3)

61-63: LGTM: reminder scheduler mock

Async mock resolves to undefined; safe and explicit.


771-776: LGTM: availableCredits aggregates remaining monthly + additional

300 matches 200 monthly remaining + 100 additional.


895-900: LGTM: idempotency path stays in MONTHLY lane

Monthly usage mock aligns with expected expense log creation.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
apps/web/modules/settings/billing/components/BillingCredits.tsx (1)

300-306: Add rel for external link and localize label

Security + i18n. This was flagged earlier; echoing to ensure it lands.

-  <Link
+  <Link
     key="Credit System"
     className="underline underline-offset-2"
     target="_blank"
+    rel="noopener noreferrer"
     href="https://cal.com/help/billing-and-usage/messaging-credits">
-    Learn more
+    {t("learn_more")}
   </Link>
🧹 Nitpick comments (6)
apps/web/modules/settings/billing/components/BillingCredits.tsx (6)

36-67: CreditRow: remove redundant margin class; confirm border side; optional formatter hoist

  • "my-1" is always applied and the else branch adds "mt-1" again; drop the else to avoid dup margins.
  • Design check: you’re drawing a bottom border; earlier specs/screens hinted at a top divider for dashed rows. Confirm if border should be top or bottom.
  • Minor: you create a new Intl.NumberFormat per row; consider hoisting to a module- or component-level constant to avoid churn.
-      className={classNames(
-        `my-1 flex justify-between`,
+      className={classNames(
+        "my-1 flex justify-between",
         underline === "dashed"
           ? "border-subtle border-b border-dashed"
           : underline === "solid"
           ? "border-subtle border-b border-solid"
-          : "mt-1",
+          : "",
         className
       )}

177-205: Clamp progress percentage to [0, 100]

Protect against rounding or API drift that could push percentage outside bounds.

-const teamCreditsPercentageUsed = totalCredits > 0 ? (totalUsed / totalCredits) * 100 : 0;
+const rawPctUsed = totalCredits > 0 ? (totalUsed / totalCredits) * 100 : 0;
+const teamCreditsPercentageUsed = Math.min(100, Math.max(0, rawPctUsed));

155-156: Make filename replacement global

.replace(" ", "-") only replaces the first space.

-const filename = `credit-expense-log-${selectedMonth.value.toLowerCase().replace(" ", "-")}.csv`;
+const filename = `credit-expense-log-${selectedMonth.value.toLowerCase().replace(/\s+/g, "-")}.csv`;

259-261: Remove invalid target on Button; decide tab behavior in onSuccess

target is not a valid attribute on a button element. If you intend a new tab for checkout, open it when sessionUrl arrives; otherwise just remove target.

-<Button color="secondary" target="_blank" size="sm" type="submit" data-testid="buy-credits">
+<Button color="secondary" size="sm" type="submit" data-testid="buy-credits">

Optional (if new tab desired instead of router.push):

- onSuccess: (data) => {
-   if (data.sessionUrl) {
-     router.push(data.sessionUrl);
-   }
- },
+ onSuccess: (data) => {
+   if (data.sessionUrl) {
+     window.open(data.sessionUrl, "_blank", "noopener,noreferrer");
+   }
+ },

244-256: react-hook-form: onChange override should trigger validation/dirty state

Overriding register’s onChange can bypass built-in flags. Ensure setValue updates form state.

- onChange={(e) => setValue("quantity", Number(e.target.value))}
+ onChange={(e) =>
+   setValue("quantity", Number(e.target.value), { shouldDirty: true, shouldValidate: true })
+ }

If TextField forwards register handlers, prefer composing the handler instead of overriding it.


239-240: A11y: hide decorative icon from SR

Mark the info icon as decorative; Tooltip content already conveys meaning.

-<Icon name="info" className="text-emphasis h-3 w-3" />
+<Icon name="info" aria-hidden="true" className="text-emphasis h-3 w-3" />
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • 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 5f22b0c and 65c8cfc.

📒 Files selected for processing (1)
  • apps/web/modules/settings/billing/components/BillingCredits.tsx (5 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Always use t() for text localization in frontend code; direct text embedding should trigger a warning

Files:

  • apps/web/modules/settings/billing/components/BillingCredits.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • apps/web/modules/settings/billing/components/BillingCredits.tsx
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • apps/web/modules/settings/billing/components/BillingCredits.tsx
🧬 Code graph analysis (1)
apps/web/modules/settings/billing/components/BillingCredits.tsx (2)
packages/features/auth/lib/next-auth-options.ts (1)
  • session (746-771)
packages/features/ee/teams/components/MemberInvitationModal.tsx (1)
  • MemberInvitationModalWithoutMembers (471-557)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Production builds / Build Atoms
  • GitHub Check: Production builds / Build API v2
  • GitHub Check: Production builds / Build Web App
  • GitHub Check: Production builds / Build API v1
  • GitHub Check: Tests / Unit
  • GitHub Check: Production builds / Build Docs
  • GitHub Check: Linters / lint
  • GitHub Check: Type check / check-types
🔇 Additional comments (4)
apps/web/modules/settings/billing/components/BillingCredits.tsx (4)

11-25: Imports look good and purposeful

Brings in the modal and UI deps needed for the new UX without excess.


174-176: Monthly-only totals align with product direction

Using only monthly credits (excluding additional) matches the latest guidance. Nice.


211-214: Nice UX: Add members opens modal instead of redirect

Addresses prior feedback and keeps users in context.


311-321: Modal gating by teamId looks correct

Props match the modal’s API; noop onSettingsOpen is acceptable here.

@CarinaWolli CarinaWolli enabled auto-merge (squash) September 19, 2025 13:47
@CarinaWolli CarinaWolli merged commit cfeb18d into main Sep 19, 2025
37 checks passed
@CarinaWolli CarinaWolli deleted the feat/billing-redesign-settings branch September 19, 2025 16:18
@coderabbitai coderabbitai bot mentioned this pull request Sep 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

billing area: billing, stripe, payments, paypal, get paid consumer core area: core, team members only ✨ feature New feature or request ready-for-e2e size/XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants