feat(billing): add top up credits button - #1169
Conversation
- Add Credits card showing current balance with status - Add Top Up Credits button for easy access - Create CreditsBalance component with visual indicators - Add "use client" directive to top-up dialog - Display warnings for low/no credits
WalkthroughIntroduces a new CreditsBalance component for displaying organization credits with status-based styling (no credits, low credits, healthy balance). Integrates this component into the Billing page alongside the TopUpCreditsButton. Adds the "use client" directive to an existing dialog component. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes
Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
apps/ui/src/app/dashboard/[orgId]/org/billing/credits-balance.tsx (1)
24-25: Consider extracting hardcoded thresholds to constants.The low credits threshold (
< 1) is hardcoded. Consider defining it as a named constant for easier maintenance and potential future configuration.+const LOW_CREDITS_THRESHOLD = 1; +const NO_CREDITS_THRESHOLD = 0; + export function CreditsBalance() { const { selectedOrganization } = useDashboardState(); // ... - const isLowCredits = creditsBalance < 1; - const hasNoCredits = creditsBalance <= 0; + const isLowCredits = creditsBalance < LOW_CREDITS_THRESHOLD; + const hasNoCredits = creditsBalance <= NO_CREDITS_THRESHOLD;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
apps/ui/src/app/dashboard/[orgId]/org/billing/credits-balance.tsx(1 hunks)apps/ui/src/app/dashboard/[orgId]/org/billing/page.tsx(3 hunks)apps/ui/src/components/credits/top-up-credits-dialog.tsx(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/ui/src/app/dashboard/[orgId]/org/billing/page.tsx (2)
apps/ui/src/components/credits/top-up-credits-dialog.tsx (1)
TopUpCreditsButton(33-42)apps/ui/src/app/dashboard/[orgId]/org/billing/credits-balance.tsx (1)
CreditsBalance(7-98)
🪛 ESLint
apps/ui/src/app/dashboard/[orgId]/org/billing/credits-balance.tsx
[error] 1-1: Resolve error: EACCES: permission denied, open '/hdGZOpoqrH'
at Object.writeFileSync (node:fs:2409:20)
at l (/home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.cjs:7:13685)
at createFilesMatcher (/home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.cjs:7:14437)
at Object.resolve (/home/jailuser/git/node_modules/.pnpm/eslint-import-resolver-typescript@4.4.4_eslint-plugin-import@2.32.0_eslint@9.34.0_jiti@2.6.1_/node_modules/eslint-import-resolver-typescript/lib/index.cjs:298:107)
at withResolver (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.6.1__t_eb0afb446ca3f59399f5d681f0059e64/node_modules/eslint-module-utils/resolve.js:180:23)
at fullResolve (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.6.1__t_eb0afb446ca3f59399f5d681f0059e64/node_modules/eslint-module-utils/resolve.js:201:22)
at relative (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.6.1__t_eb0afb446ca3f59399f5d681f0059e64/node_modules/eslint-module-utils/resolve.js:217:10)
at resolve (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.6.1__t_eb0afb446ca3f59399f5d681f0059e64/node_modules/eslint-module-utils/resolve.js:233:12)
at resolveImportType (/home/jailuser/git/node_modules/.pnpm/eslint-plugin-import@2.32.0_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.6.1___986ec7d736a20dae59d4d473ff8a6f0d/node_modules/eslint-plugin-import/lib/core/importType.js:126:2822)
at computeRank (/home/jailuser/git/node_modules/.pnpm/eslint-plugin-import@2.32.0_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.6.1___986ec7d736a20dae59d4d473ff8a6f0d/node_modules/eslint-plugin-import/lib/rules/order.js:529:43)
(import/order)
apps/ui/src/components/credits/top-up-credits-dialog.tsx
[error] 1-1: Resolve error: EACCES: permission denied, open '/NKwIdkHICO'
at Object.writeFileSync (node:fs:2409:20)
at l (/home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.cjs:7:13685)
at createFilesMatcher (/home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.cjs:7:14437)
at Object.resolve (/home/jailuser/git/node_modules/.pnpm/eslint-import-resolver-typescript@4.4.4_eslint-plugin-import@2.32.0_eslint@9.34.0_jiti@2.6.1_/node_modules/eslint-import-resolver-typescript/lib/index.cjs:298:107)
at withResolver (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.6.1__t_eb0afb446ca3f59399f5d681f0059e64/node_modules/eslint-module-utils/resolve.js:180:23)
at fullResolve (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.6.1__t_eb0afb446ca3f59399f5d681f0059e64/node_modules/eslint-module-utils/resolve.js:201:22)
at relative (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.6.1__t_eb0afb446ca3f59399f5d681f0059e64/node_modules/eslint-module-utils/resolve.js:217:10)
at resolve (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.6.1__t_eb0afb446ca3f59399f5d681f0059e64/node_modules/eslint-module-utils/resolve.js:233:12)
at resolveImportType (/home/jailuser/git/node_modules/.pnpm/eslint-plugin-import@2.32.0_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.6.1___986ec7d736a20dae59d4d473ff8a6f0d/node_modules/eslint-plugin-import/lib/core/importType.js:126:2822)
at computeRank (/home/jailuser/git/node_modules/.pnpm/eslint-plugin-import@2.32.0_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.6.1___986ec7d736a20dae59d4d473ff8a6f0d/node_modules/eslint-plugin-import/lib/rules/order.js:529:43)
(import/order)
⏰ 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). (4)
- GitHub Check: test / run
- GitHub Check: build / run
- GitHub Check: lint / run
- GitHub Check: autofix
🔇 Additional comments (6)
apps/ui/src/components/credits/top-up-credits-dialog.tsx (1)
1-1: LGTM! Correct addition of "use client" directive.This directive is appropriate for this component given its use of React hooks (useState, useEffect), Stripe client-side elements, and interactive form handling.
apps/ui/src/app/dashboard/[orgId]/org/billing/credits-balance.tsx (3)
10-19: LGTM! Clean loading state handling.The loading state provides clear feedback while organization data is being fetched.
27-97: LGTM! Well-structured status display with clear visual hierarchy.The conditional rendering logic correctly prioritizes status messages (no credits → low credits → healthy), and the color-coded UI provides clear visual feedback to users.
21-25: Verify credits field type from external "@llmgateway/db" package before dismissing null safety concerns.The original review flags a real risk: if
selectedOrganization.creditsis undefined, null, or an invalid string,Number()returnsNaN, which displays as "$NaN" and incorrectly shows "Healthy balance" status (sinceNaN < 1andNaN <= 0both evaluate to false).While the code has an early guard checking
if (!selectedOrganization), this only validates the object exists—not that thecreditsfield is a valid value. With"strict": trueenabled in tsconfig.json, TypeScript should catch optional fields, but the type definition forSerializedOrganizationis in the external package@llmgateway/dband is not accessible.Developer action: Check whether the
creditsfield inSerializedOrganization(from@llmgateway/db) is guaranteed to be a non-null, valid number string. If the field can be undefined or non-numeric, implement the defensive handling suggested in the original review.apps/ui/src/app/dashboard/[orgId]/org/billing/page.tsx (2)
4-4: LGTM! Clean imports for new credit management components.Import paths are correct and the components are properly utilized in the billing page.
Also applies to: 13-13
39-52: LGTM! Well-integrated Credits card with appropriate placement.The Credits card is positioned prominently at the top of the billing page, providing immediate visibility of the credit balance and easy access to the top-up action. The structure is consistent with other cards on the page.
Summary by CodeRabbit