feat(frontend): prompt top-up when out of automation credits - #13208
Conversation
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…dits useCredits fetches the balance and the auto-top-up config independently. If credits (=0) resolved before the config loaded, isOutOfCredits briefly flipped true, mounting DailyTopUpAutoOpener and firing the daily modal (and stamping localStorage) for users who actually have auto-refill on. Require autoTopUpConfig to be loaded before treating the user as out. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds a low-credit top-up system: local-storage date helpers, TopUpPrompt context/provider, TopUpForm and hook, LowCreditBanner and TopUpDialog UI, daily auto-opener, layout/page wiring (Copilot, Library), Wallet one-time top-up refactor, comprehensive Vitest tests using MSW and localStorage isolation, and a backend change removing null-credential normalization. ChangesTop-Up Prompt Infrastructure and Integration
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 |
🔍 PR Overlap DetectionThis check compares your PR against all other open PRs targeting the same branch to detect potential merge conflicts early. 🔴 Merge Conflicts DetectedThe following PRs have been tested and will have merge conflicts if merged after this PR. Consider coordinating with the authors.
🟡 Medium Risk — Some Line OverlapThese PRs have some overlapping changes:
🟢 Low Risk — File Overlap OnlyThese PRs touch the same files but different sections (click to expand)
Summary: 2 conflict(s), 1 medium risk, 2 low risk (out of 5 PRs with file overlap) Auto-generated on push. Ignores: |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## dev #13208 +/- ##
==========================================
+ Coverage 72.38% 72.42% +0.03%
==========================================
Files 2304 2313 +9
Lines 173347 173356 +9
Branches 17560 17566 +6
==========================================
+ Hits 125484 125551 +67
+ Misses 44140 44076 -64
- Partials 3723 3729 +6
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/low-credit-banner.test.tsx (1)
2-2: ⚡ Quick winRemove redundant
cleanup()from integration test teardown.Line 125 duplicates global RTL cleanup handled by the shared Vitest setup; keeping both adds noise and can obscure real teardown needs.
Proposed diff
-import { render, screen, cleanup } from "`@/tests/integrations/test-utils`"; +import { render, screen } from "`@/tests/integrations/test-utils`"; @@ afterEach(() => { - cleanup(); localStorage.clear(); });Based on learnings:
testing-library/reactcleanup is already handled globally after each test viasrc/tests/integrations/vitest.setup.tsx, so redundantafterEach(() => cleanup())should not be added in integration tests.Also applies to: 124-126
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@autogpt_platform/frontend/src/app/`(platform)/copilot/__tests__/low-credit-banner.test.tsx at line 2, The test imports and calls cleanup manually even though global RTL cleanup is configured; remove the redundant cleanup usage by deleting cleanup from the import list (leave render and screen) and remove the afterEach(() => cleanup()) teardown in the low-credit-banner.test.tsx test file so the global vitest.setup.tsx handles cleanup automatically.autogpt_platform/frontend/src/components/layout/TopUpPrompt/__tests__/TopUpPromptProvider.test.tsx (1)
33-49: ⚡ Quick winGate suppression assertions on both backend reads, not only credits fetch.
On Line 45-Line 48,
waitForCreditsFetchonly confirms/user-creditswas hit. These tests assert behavior derived from both credits and auto-top-up config; if auto-top-up fetch regresses, the test can still pass. Please wait for both handlers before absence assertions.Proposed diff
function setupCredits(args: { credits: number; amount: number; threshold: number; }) { let creditsRequested = false; + let autoTopUpRequested = false; server.use( getGetV1GetUserCreditsMockHandler(() => { creditsRequested = true; return { credits: args.credits }; }), - getGetV1GetAutoTopUpMockHandler({ - amount: args.amount, - threshold: args.threshold, - }), + getGetV1GetAutoTopUpMockHandler(() => { + autoTopUpRequested = true; + return { + amount: args.amount, + threshold: args.threshold, + }; + }), ); return { - waitForCreditsFetch: () => + waitForConfigFetches: () => waitFor(() => { expect(creditsRequested).toBe(true); + expect(autoTopUpRequested).toBe(true); }), }; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@autogpt_platform/frontend/src/components/layout/TopUpPrompt/__tests__/TopUpPromptProvider.test.tsx` around lines 33 - 49, The test only gates on the /user-credits handler; add a second flag (e.g. autoTopUpRequested) tied to getGetV1GetAutoTopUpMockHandler and update the returned waitForCreditsFetch to waitFor both flags instead of just creditsRequested so the test awaits both backend reads (reference getGetV1GetUserCreditsMockHandler, getGetV1GetAutoTopUpMockHandler and the waitForCreditsFetch helper).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpDialog/TopUpDialog.tsx`:
- Around line 20-22: Replace the inline arrow assigned to controlled.set with a
named handler function: create a function declaration (e.g., function
handleControlledSet(open: boolean) { if (!open) onClose(); }) and pass that
function reference to controlled.set instead of the inline arrow; update any
existing references to controlled.set to use the new handler name and keep the
existing behavior (call onClose() when open is false) in TopUpDialog's component
scope.
In
`@autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/useTopUpForm.ts`:
- Around line 24-28: The submitTopUp function currently sets isLoading true,
awaits requestTopUp(...).catch(...), then sets isLoading false, but if an
unexpected error escapes the catch the loading state can remain true; wrap the
await in a try/finally so setIsLoading(false) always runs. Update the
submitTopUp implementation (function submitTopUp) to call
requestTopUp(data.amount * 100) inside a try block and move setIsLoading(false)
into a finally block, keeping the existing toastOnFail("request top-up") error
handling around the requestTopUp call.
In
`@autogpt_platform/frontend/src/components/layout/TopUpPrompt/useTopUpPrompt.ts`:
- Around line 12-16: The failing tests are caused by useTopUpPrompt throwing
when not wrapped with TopUpPromptProvider; update the shared test render helpers
(or a central mock) so components rendered in tests—especially those that render
LowCreditBanner—are wrapped with TopUpPromptProvider (or provide a mocked
context value). Locate useTopUpPrompt and TopUpPromptProvider in the diff and
modify your test utility (the common renderWrapper/renderWithProviders helper)
to include TopUpPromptProvider around the tree or add a centralized mock for
useTopUpPrompt to return a safe default, ensuring tests no longer crash before
assertions.
---
Nitpick comments:
In
`@autogpt_platform/frontend/src/app/`(platform)/copilot/__tests__/low-credit-banner.test.tsx:
- Line 2: The test imports and calls cleanup manually even though global RTL
cleanup is configured; remove the redundant cleanup usage by deleting cleanup
from the import list (leave render and screen) and remove the afterEach(() =>
cleanup()) teardown in the low-credit-banner.test.tsx test file so the global
vitest.setup.tsx handles cleanup automatically.
In
`@autogpt_platform/frontend/src/components/layout/TopUpPrompt/__tests__/TopUpPromptProvider.test.tsx`:
- Around line 33-49: The test only gates on the /user-credits handler; add a
second flag (e.g. autoTopUpRequested) tied to getGetV1GetAutoTopUpMockHandler
and update the returned waitForCreditsFetch to waitFor both flags instead of
just creditsRequested so the test awaits both backend reads (reference
getGetV1GetUserCreditsMockHandler, getGetV1GetAutoTopUpMockHandler and the
waitForCreditsFetch helper).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a2dc8993-bb0c-49e5-9939-635aabad5e8a
📒 Files selected for processing (18)
autogpt_platform/frontend/src/app/(platform)/copilot/CopilotPage.tsxautogpt_platform/frontend/src/app/(platform)/copilot/__tests__/low-credit-banner.test.tsxautogpt_platform/frontend/src/app/(platform)/layout.tsxautogpt_platform/frontend/src/app/(platform)/library/__tests__/low-credit-banner.test.tsxautogpt_platform/frontend/src/app/(platform)/library/page.tsxautogpt_platform/frontend/src/components/layout/Navbar/components/Wallet/components/WalletRefill.tsxautogpt_platform/frontend/src/components/layout/TopUpPrompt/DailyTopUpAutoOpener.tsxautogpt_platform/frontend/src/components/layout/TopUpPrompt/LowCreditBanner/LowCreditBanner.tsxautogpt_platform/frontend/src/components/layout/TopUpPrompt/LowCreditBanner/useLowCreditBanner.tsautogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpDialog/TopUpDialog.tsxautogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/TopUpForm.tsxautogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/useTopUpForm.tsautogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpPromptProvider.tsxautogpt_platform/frontend/src/components/layout/TopUpPrompt/__tests__/TopUpPromptProvider.test.tsxautogpt_platform/frontend/src/components/layout/TopUpPrompt/__tests__/helpers.test.tsautogpt_platform/frontend/src/components/layout/TopUpPrompt/helpers.tsautogpt_platform/frontend/src/components/layout/TopUpPrompt/useTopUpPrompt.tsautogpt_platform/frontend/src/services/storage/local-storage.ts
useTopUpPrompt threw when no TopUpPromptProvider was an ancestor, which crashed every existing Library/Autopilot page test (they render the page without the layout) and would white-screen the page in prod if the provider were ever absent. The prompt is an optional enhancement, so fall back to an inert value instead. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- TopUpDialog: named handler instead of inline arrow for controlled.set - useTopUpForm: reset isLoading via try/finally on submit - LowCreditBanner: wrap the action row so it stays usable at 375px - tests: drop redundant cleanup(); gate the suppression waiter on both the credits and auto-top-up reads Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
/review |
There was a problem hiding this comment.
📋 Automated Review — PR #13208
PR #13208 — feat(frontend): prompt top-up when out of automation credits
Author: 0ubbe | Files: 18
🎯 Verdict: REQUEST_CHANGES
PR Description Quality
✅ Has Why + What + How — clear description of the three surfaces (daily modal, Library banner, Copilot banner), gating logic, and acknowledged tech debt (legacy form imports). PR checklist items are present but all unchecked.
What This PR Does
When a user runs out of automation credits and doesn't have auto-refill enabled, there's currently no in-app prompt to top up. This PR adds three coordinated UI surfaces — a once-per-day modal dialog, a persistent banner on the Library page, and a persistent banner on the Copilot page — all gated behind the ENABLE_PLATFORM_PAYMENT feature flag and the user's credit balance. A shared TopUpPromptProvider context coordinates state, and the top-up form was extracted from WalletRefill into a reusable TopUpForm component that reuses the existing Stripe checkout flow.
Specialist Findings
🛡️ Security ✅ — No new attack surface. This is a UI-only change reusing the existing server-validated Stripe checkout path. No new API endpoints, no secrets in code, no XSS vectors. The localStorage-based daily gate controls UX only, not authorization.
- 🟡 Legacy
Formimport from@/components/__legacy__/ui/formextends reliance on unmaintained code (TopUpForm.tsx:1) — acknowledged by author as follow-up.
🏗️ Architecture ✅ — Well-structured context + provider + consumer pattern. Clean extraction of TopUpForm from WalletRefill eliminates duplication. File organization follows the ComponentName/ComponentName.tsx + useComponentName.ts + helpers.ts convention throughout.
- 🟠
useCreditsmay be called twice per platform page load — once byTopUpPromptProvider(TopUpPromptProvider.tsx:19) and once by the Navbar Wallet. IfuseCreditsdoesn't deduplicate via React Query, this doubles credit-fetch API traffic for every authenticated user. (Flagged by: architect, performance — 2) - 🟡 Context only exposes
openTopUpbut notcloseTopUp(useTopUpPrompt.ts:4) — minor extensibility gap for future consumers.
⚡ Performance ✅ — All operations are O(1). No unbounded loops, no memory leaks. Main concern is the potential duplicate credit fetch noted above. localStorage reads are synchronous but occur only on mount — negligible impact.
🧪 Testing
- 🔴 The extracted
TopUpForm/useTopUpForm— which callsrequestTopUp(amount * 100)to initiate Stripe checkout — has zero tests (useTopUpForm.ts:24-30). The refactoring introduced atry/finallypattern that differs from the originalWalletRefillbehavior. This is payment-touching code. (Flagged by: testing — 1) - 🟠 No test for the banner dismiss button (
LowCreditBanner.tsx:27-33) — clicking X should write to localStorage and hide the banner. The Library test clicks the CTA but never dismiss. (Flagged by: testing — 1) - 🟠 No regression test for
WalletRefillafter the extraction (WalletRefill.tsx:108) — the one-time top-up tab was refactored to use<TopUpForm>but nothing verifies it still works. (Flagged by: testing — 1) - 🟡
useTopUpPromptinert fallback (useTopUpPrompt.ts:15-18) is documented but untested.
📖 Quality ✅ — Clean, well-named code. Function declarations throughout, no barrel files, Phosphor icons, proper separation of concerns.
- 🔵 Mixed error handling in
useTopUpForm.ts:25-30:try/finallywrapping.catch()is functionally correct but reads confusingly — the.catch()swallows the rejection sotrynever throws. - 🔵 Long inline comment at
TopUpPromptProvider.tsx:24(130+ chars).
📦 Product ✅ — Feature delivers exactly what's described: three coordinated payment-gated surfaces with proper suppression logic. Daily-limit prevents annoyance, Stripe flow is reused.
- 🟠 No loading feedback on the top-up form (
TopUpForm.tsx:30) — the button stays as "Top up" whileisLoadingis true. On slow connections, users won't know a Stripe redirect is in progress. (Flagged by: product — 1) - 🟡 Dialog doesn't suggest auto-refill as an alternative (
TopUpDialog.tsx:27).
📬 Discussion
- 🟠 Sentry bot's concern about fail-closed behavior (
TopUpPromptProvider.tsx:28-33) is unacknowledged — no reply and no documenting code comment. - 🟠 PR checklist manual test items are all unchecked — author hasn't confirmed manual verification of the payment flow.
- No human reviewer has approved yet.
🔎 QA ✅ — All 12 integration tests pass. Feature correctly gated behind ENABLE_PLATFORM_PAYMENT flag — invisible in local mode. Negative auth tests confirm endpoint protection. No regressions in service logs.
QA Screenshots
| Screenshot | Description |
|---|---|
![]() |
Library page with payment flag off + 100 credits — no banner visible ✅ |
![]() |
Copilot page with payment flag off — no banner or modal visible ✅ |
🔴 Blockers
- Untested payment-touching code (
TopUpForm/useTopUpForm.ts:24-30) — The extracteduseTopUpFormhook callsrequestTopUp(amount * 100)to initiate Stripe checkout and has zero tests. The refactoring also introduced atry/finallyerror-recovery pattern that differs from the originalWalletRefill. Add tests for: (a) valid submission callsrequestTopUpwith cents, (b)$5 minimumvalidation rejects lower amounts, (c) button disabled during loading. (Flagged by: testing — 1)
🟠 Should Fix
- Banner dismiss flow untested (
LowCreditBanner.tsx:27-33) — No test verifies the dismiss X button writes to localStorage and hides the banner. (Flagged by: testing — 1) - WalletRefill regression coverage missing (
WalletRefill.tsx:108) — The one-time top-up tab was refactored to use<TopUpForm>but has no smoke test after extraction. (Flagged by: testing — 1) - No loading feedback on top-up submit (
TopUpForm.tsx:30) — Button label stays "Top up" during Stripe redirect. Show a spinner or "Redirecting…" text. (Flagged by: product — 1) - Verify
useCreditsdeduplication (TopUpPromptProvider.tsx:19) — IfuseCreditsisn't backed by React Query or similar, every platform page load now fires duplicate credit-fetch requests. Confirm and document. (Flagged by: architect, performance — 2) - Acknowledge Sentry bot feedback (
TopUpPromptProvider.tsx:28-33) — Reply to the thread and add a code comment explaining the intentional fail-closed design. (Flagged by: discussion — 1) - Complete PR checklist — All manual test items are unchecked. Confirm manual verification before requesting merge. (Flagged by: discussion — 1)
🟡 Nice to Have
- Add
aria-live="polite"to LowCreditBanner (LowCreditBanner.tsx:17) — Screen readers won't announce the dynamically-appearing banner without it. (quality) - Suggest auto-refill in dialog (
TopUpDialog.tsx:27) — A secondary link like "Or enable auto-refill" could reduce future prompt occurrences. (product) - Test
useTopUpPromptinert fallback (useTopUpPrompt.ts:15-18) — Prevents crashes if provider is accidentally removed from the tree. (testing) - Plan legacy form migration (
TopUpForm.tsx:1) — File a follow-up to migrate bothTopUpFormandWalletRefilloff@/components/__legacy__/ui/form. (security, architect, quality)
🔵 Nits
- Mixed error-handling pattern (
useTopUpForm.ts:25-30) —try/finallywrapping.catch()is confusing. Use eithertry/catch/finallyor.catch().finally(), not both. - Long inline comment (
TopUpPromptProvider.tsx:24) — 130+ character line; split to a block comment above.
Human Review Needed
YES — This is a payment-related feature touching Stripe checkout flows. While the backend is unchanged, the extracted TopUpForm is the entry point for real-money transactions, and 18 files were modified including layout-level provider changes. A human reviewer should verify the useCredits deduplication behavior and confirm the suppression logic works correctly in the deployed environment.
Risk Assessment
Merge risk: LOW | Rollback: EASY
Feature is entirely frontend, gated behind a LaunchDarkly flag (ENABLE_PLATFORM_PAYMENT), and reuses existing Stripe infrastructure. Can be disabled instantly via flag toggle. No database migrations, no backend changes. 4 other open PRs may conflict if merged after this one — coordinate with @ntindle.
CI Status
- ✅ Frontend lint, Backend lint
- ❌ Frontend typecheck, Backend tests, Frontend unit tests, Frontend build
Note: Backend test failure appears to be an environment issue (0s runtime), not related to this PR. Frontend failures should be investigated — they may indicate issues introduced by this PR or pre-existing CI flakiness.
UI Testing — Variant Results
✅ local: All 12 integration tests pass, feature correctly gated behind payment flag (invisible in local mode), no regressions or payment UI leakage detected.
✅ hosted: All 12 integration tests pass, pages render correctly with positive credits (banners properly suppressed), auth protection verified, no regressions in logs
- Migrate TopUpForm off the legacy __legacy__/ui/form import to the design-system molecules/Form primitives. - Show "Redirecting…" on the submit button while a checkout is in flight. - Replace the misleading try/finally in useTopUpForm with a promise .finally() chain. - Document the fail-closed auto-top-up-config behaviour in the provider and add a symmetric closeTopUp to the context. - Add aria-live and a dismiss title to the LowCreditBanner for a11y. - Offer an "enable auto-refill" link in the top-up dialog. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lback - TopUpForm: valid submission posts the amount in cents; sub-$5 amounts are rejected without hitting the API. - WalletRefill: regression smoke test that the one-time top-up tab still renders the extracted TopUpForm. - LowCreditBanner: dismissing hides it for the day and records the date. - useTopUpPrompt: consumers without a provider fall back to an inert value instead of throwing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ight Completes the payment-form coverage by holding the request open and verifying the submit button shows "Redirecting…" and is disabled. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx (1)
69-69: ⚡ Quick winConsider using the jest-dom
toBeDisabled()matcher.The type assertion and property access work correctly, but jest-dom's
toBeDisabled()matcher is more idiomatic and provides clearer error messages.♻️ Suggested refinement
- const button = await screen.findByRole("button", { name: /redirecting/i }); - expect((button as HTMLButtonElement).disabled).toBe(true); + const button = await screen.findByRole("button", { name: /redirecting/i }); + expect(button).toBeDisabled();🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx` at line 69, Replace the explicit type assertion and property check in TopUpForm.test.tsx—expect((button as HTMLButtonElement).disabled).toBe(true)—with the jest-dom matcher expect(button).toBeDisabled() to be more idiomatic; ensure the test file (or your test setup like setupTests.ts) imports jest-dom matchers (e.g., import '`@testing-library/jest-dom/extend-expect`') so toBeDisabled() is available and confirm the queried element stored in button is a DOM element returned by RTL (e.g., getByRole/getByTestId) so the matcher can operate on it.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In
`@autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx`:
- Line 69: Replace the explicit type assertion and property check in
TopUpForm.test.tsx—expect((button as
HTMLButtonElement).disabled).toBe(true)—with the jest-dom matcher
expect(button).toBeDisabled() to be more idiomatic; ensure the test file (or
your test setup like setupTests.ts) imports jest-dom matchers (e.g., import
'`@testing-library/jest-dom/extend-expect`') so toBeDisabled() is available and
confirm the queried element stored in button is a DOM element returned by RTL
(e.g., getByRole/getByTestId) so the matcher can operate on it.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 5f8d9141-f2a3-42c1-8806-8dacbe677863
📒 Files selected for processing (1)
autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx
📜 Review details
⏰ 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: Seer Code Review
🧰 Additional context used
📓 Path-based instructions (15)
autogpt_platform/frontend/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
autogpt_platform/frontend/**/*.{ts,tsx,js,jsx}: Use Node.js 21+ with pnpm package manager for frontend development
Always run 'pnpm format' for formatting and linting code in frontend developmentFormat frontend code using
pnpm format
autogpt_platform/frontend/**/*.{ts,tsx,js,jsx}: Fully capitalize acronyms in symbols, e.g.graphID,useBackendAPI
No linter suppressors (//@ts-ignore``,// eslint-disable) — fix the actual issue
Files:
autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx
autogpt_platform/frontend/**/*.{tsx,ts}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
autogpt_platform/frontend/**/*.{tsx,ts}: Use function declarations for components and handlers (not arrow functions) in React components
Only use arrow functions for small inline lambdas (map, filter, etc.) in React components
Use PascalCase for component names and camelCase with 'use' prefix for hook names in React
Use Tailwind CSS utilities only for styling in frontend components
Use design system components from 'src/components/' (atoms, molecules, organisms) in frontend development
Never use 'src/components/legacy/' in frontend code
Only use Phosphor Icons (@phosphor-icons/react) for icons in frontend components
Use generated API hooks from '@/app/api/__generated__/endpoints/' instead of deprecated 'BackendAPI' or 'src/lib/autogpt-server-api/'
Use React Query for server state (via generated hooks) in frontend development
Default to client components ('use client') in Next.js; only use server components for SEO or extreme TTFB needs
Use '' component for rendering errors in frontend UI; use toast notifications for mutation errors; use 'Sentry.captureException()' for manual exceptions
Separate render logic from data/behavior in React components; keep comments minimal (code should be self-documenting)
Files:
autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx
autogpt_platform/frontend/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
autogpt_platform/frontend/**/*.{ts,tsx}: No barrel files or 'index.ts' re-exports in frontend code
Regenerate API hooks with 'pnpm generate:api' after backend OpenAPI spec changes in frontend development
autogpt_platform/frontend/**/*.{ts,tsx}: Use function declarations (not arrow functions) for components/handlers
Noanytypes unless the value genuinely can be anything
Keep render functions and hooks under ~50 lines; extract named helpers or sub-components when they grow longer
Files:
autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx
autogpt_platform/frontend/src/components/**/*.{tsx,ts}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Structure React components as: ComponentName/ComponentName.tsx + useComponentName.ts + helpers.ts (exception: small 3-4 line components can be inline; render-only components can be direct files)
Files:
autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx
autogpt_platform/frontend/src/components/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Structure components as
ComponentName/ComponentName.tsx+useComponentName.ts+helpers.ts, use design system components fromsrc/components/(atoms, molecules, organisms), and never usesrc/components/__legacy__/*
Files:
autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx
autogpt_platform/frontend/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
autogpt_platform/frontend/src/**/*.{ts,tsx}: Use generated API hooks from@/app/api/__generated__/endpoints/following the patternuse{Method}{Version}{OperationName}, and regenerate withpnpm generate:api
Separate render logic from business logic using component.tsx + useComponent.ts + helpers.ts pattern, colocate state when possible and avoid creating large components, use sub-components in local/componentsfolder
Use function declarations for components and handlers, use arrow functions only for callbacks
Do not useuseCallbackoruseMemounless asked to optimise a given function
autogpt_platform/frontend/src/**/*.{ts,tsx}: Keep files under ~200 lines; extract sub-components or hooks into their own files when a file grows beyond this
Use generated API hooks from@/app/api/__generated__/endpoints/with patternuse{Method}{Version}{OperationName}
Always import the-Icon-suffixed alias from@phosphor-icons/react(e.g.TrashIcon,PlusIcon,SquareIcon) — bare exports are deprecated
Do not useuseCallbackoruseMemounless asked to optimize a given function
Never usesrc/components/__legacy__/*— use design system components fromsrc/components/
Files:
autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx
autogpt_platform/frontend/**/*.{tsx,css}
📄 CodeRabbit inference engine (AGENTS.md)
Use Tailwind CSS only for styling, use design tokens, and use Phosphor Icons only
Files:
autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx
autogpt_platform/frontend/src/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
Component props should use
interface Props { ... }(not exported) unless the interface needs to be used outside the component
Files:
autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx
autogpt_platform/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Never type with
any, if no types available useunknown
Files:
autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx
autogpt_platform/frontend/**/*.{test,spec}.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
autogpt_platform/frontend/**/*.{test,spec}.{ts,tsx}: Use Vitest + RTL + MSW for integration tests as the primary testing approach (~90%, page-level), use Playwright for E2E critical flows, and use Storybook for design system components
Run frontend integration tests withpnpm test:unit(Vitest + RTL + MSW)
Files:
autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx
autogpt_platform/frontend/**/*.{tsx,jsx}
📄 CodeRabbit inference engine (autogpt_platform/frontend/AGENTS.md)
autogpt_platform/frontend/**/*.{tsx,jsx}: Nodark:Tailwind classes — the design system handles dark mode
Use Next.js<Link>for internal navigation — never raw<a>tags
Use Tailwind CSS only for styling with design tokens and Phosphor Icons only
Files:
autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx
autogpt_platform/frontend/src/**/components/**/*.{tsx,jsx}
📄 CodeRabbit inference engine (autogpt_platform/frontend/AGENTS.md)
Put sub-components in local
components/folder; component props should betype Props = { ... }(not exported) unless used outside the component
Files:
autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx
autogpt_platform/frontend/src/**/components/**/*.{ts,tsx}
📄 CodeRabbit inference engine (autogpt_platform/frontend/AGENTS.md)
Structure components as
ComponentName/ComponentName.tsx+useComponentName.ts+helpers.ts
Files:
autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx
autogpt_platform/frontend/src/**/__tests__/**/*.{test,spec}.{ts,tsx}
📄 CodeRabbit inference engine (autogpt_platform/frontend/AGENTS.md)
Use Orval-generated MSW handlers from
@/app/api/__generated__/endpoints/{tag}/{tag}.msw.tsfor API mocking
Files:
autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx
autogpt_platform/frontend/src/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (autogpt_platform/frontend/AGENTS.md)
Avoid index and barrel files
Files:
autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx
🧠 Learnings (8)
📚 Learning: 2026-03-24T02:05:04.672Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12526
File: autogpt_platform/frontend/src/app/(platform)/copilot/CopilotPage.tsx:0-0
Timestamp: 2026-03-24T02:05:04.672Z
Learning: When gating React component logic on a React Query result (e.g., hooks like `useQuery` / `useGetV2GetCopilotUsage`), prefer destructuring and checking `isSuccess` (or aliasing it to a meaningful boolean like `isSuccess: hasUsage`) instead of relying on `!isLoading`. Reason: `isLoading` can be `false` in error/idle states where `data` may still be `undefined`, while `isSuccess` indicates the query completed successfully and `data` is populated.
Applied to files:
autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx
📚 Learning: 2026-04-01T18:54:16.035Z
Learnt from: Bentlybro
Repo: Significant-Gravitas/AutoGPT PR: 12633
File: autogpt_platform/frontend/src/app/(platform)/library/components/AgentFilterMenu/AgentFilterMenu.tsx:3-10
Timestamp: 2026-04-01T18:54:16.035Z
Learning: In the frontend, the legacy Select component at `@/components/__legacy__/ui/select` is an intentional, codebase-wide visual-consistency pattern. During code reviews, do not flag or block PRs merely for continuing to use this legacy Select. If a migration to the newer design-system Select is desired, bundle it into a single dedicated cleanup/migration PR that updates all Select usages together (e.g., avoid piecemeal replacements).
Applied to files:
autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx
📚 Learning: 2026-04-07T09:24:16.582Z
Learnt from: 0ubbe
Repo: Significant-Gravitas/AutoGPT PR: 12686
File: autogpt_platform/frontend/src/app/(no-navbar)/onboarding/steps/__tests__/PainPointsStep.test.tsx:1-19
Timestamp: 2026-04-07T09:24:16.582Z
Learning: In Significant-Gravitas/AutoGPT’s `autogpt_platform/frontend` (Vite + `vitejs/plugin-react` with the automatic JSX transform), do not flag usages of React types/components (e.g., `React.ReactNode`) in `.ts`/`.tsx` files as missing `React` imports. Since the React namespace is made available by the project’s TS/Vite setup, an explicit `import React from 'react'` or `import type { ReactNode } ...` is not required; only treat it as missing if typechecking (e.g., `pnpm types`) would actually fail.
Applied to files:
autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx
📚 Learning: 2026-04-02T05:43:49.128Z
Learnt from: 0ubbe
Repo: Significant-Gravitas/AutoGPT PR: 12640
File: autogpt_platform/frontend/src/app/(no-navbar)/onboarding/steps/WelcomeStep.tsx:13-13
Timestamp: 2026-04-02T05:43:49.128Z
Learning: Do not flag `import { Question } from "phosphor-icons/react"` as an invalid import. `Question` is a valid named export from `phosphor-icons/react` (as reflected in the package’s generated `.d.ts` files and re-exports via `dist/index.d.ts`), so it should be treated as a supported named export during code reviews.
Applied to files:
autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx
📚 Learning: 2026-04-13T13:11:07.445Z
Learnt from: 0ubbe
Repo: Significant-Gravitas/AutoGPT PR: 12764
File: autogpt_platform/frontend/src/app/(platform)/library/components/SitrepItem/SitrepItem.tsx:143-145
Timestamp: 2026-04-13T13:11:07.445Z
Learning: In `autogpt_platform/frontend`, do not flag direct interpolation of `executionID` UUID strings into URL query parameters (e.g., `activeItem=${executionID}` in JSX/Next links). If the value is a UUID string matching `[0-9a-f-]`, it contains no reserved URL characters, so additional `encodeURIComponent` or Next.js object-based `href` encoding is unnecessary. Only treat it as an encoding issue if the query-param value is not guaranteed to be UUID-formatted (i.e., may include characters outside `[0-9a-f-]`).
Applied to files:
autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx
📚 Learning: 2026-04-15T22:49:06.896Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 11235
File: autogpt_platform/frontend/src/app/(platform)/admin/diagnostics/components/ExecutionsTable.tsx:0-0
Timestamp: 2026-04-15T22:49:06.896Z
Learning: In the AutoGPT frontend (React Query + toast/ErrorCard patterns), do not require `Sentry.captureException` in React Query mutation `catch` blocks. React Query handles error propagation for mutation paths, so follow the established pattern: show toast notifications for mutation errors and use `ErrorCard` for render/fetch errors. Only add `Sentry.captureException` for truly manual/unexpected exception paths that are outside React Query’s control (e.g., standalone async utilities or event handlers not wired through React Query).
Applied to files:
autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx
📚 Learning: 2026-04-20T13:17:39.951Z
Learnt from: 0ubbe
Repo: Significant-Gravitas/AutoGPT PR: 12854
File: autogpt_platform/frontend/src/app/(platform)/library/__tests__/briefing.test.tsx:84-84
Timestamp: 2026-04-20T13:17:39.951Z
Learning: In the AutoGPT frontend, `testing-library/react` cleanup is already handled globally after each test via `src/tests/integrations/vitest.setup.tsx`. Therefore, for integration test files under `__tests__/`, do NOT add redundant `afterEach(() => cleanup())`. Only add local `afterEach` teardown for resources that are not covered globally—specifically, when using fake timers, add `afterEach(() => vi.useRealTimers())` (or equivalent) to restore real timers and prevent cross-test interference.
Applied to files:
autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx
📚 Learning: 2026-04-20T20:07:22.981Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 11235
File: autogpt_platform/frontend/src/app/(platform)/admin/diagnostics/__tests__/ExecutionsTable.test.tsx:27-76
Timestamp: 2026-04-20T20:07:22.981Z
Learning: In this codebase, Orval-generated API modules under `src/app/api/__generated__/` are not committed to git and must be generated via `pnpm generate:api` (requires a running backend). In integration tests, it’s acceptable—and expected—to stub generated hooks/modules by mocking them with `vi.mock("`@/app/api/__generated__/endpoints/`{tag}/{tag}")`. Do not treat `vi.mock` of these generated hook modules as a violation of the MSW handler guideline, since the corresponding MSW handlers cannot be imported at test time when generated files are absent.
Applied to files:
autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx
🔇 Additional comments (1)
autogpt_platform/frontend/src/components/layout/TopUpPrompt/TopUpForm/__tests__/TopUpForm.test.tsx (1)
53-70: LGTM!
Move the low-credit banner into the Library "All" tab (above the agent grid) instead of stacking at the top of the page, and align the copilot placement via an optional wrapper className on the banner so each surface controls its own padding. Banner: warm-orange "Top up" CTA, ghost dismiss button with a peach-tinted hover that blends with the warning surface, copy reads as two sentences without an em dash. Top-up dialog: lucide warning icon (matching the alert) prefixes the heading, body and billing-link copy merged into a single text-base paragraph, input and submit button use the design system's default sizes.
Add an optional `size` prop to TopUpForm so the wallet refill popover can render small input + button (matching the surrounding auto-refill controls), while the top-up modal keeps the design system's default sizes.
…commit LLMs sometimes pass `"credentials": null` instead of omitting the field. Reinstates the strip-on-null guard and its tests from #13185 that were accidentally removed by an earlier "chore: changes" commit on this branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The component dropped the `visible` boolean returned by `useLowCreditBanner`, so the banner ignored both the credit balance and the daily-dismissal state. Destructure `visible` and early-return null when it is false. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`getUserCredit` swallowed errors and returned `{ credits: 0 }`, which
the new TopUpPromptProvider treated as out-of-credits. A flaky GET
/credits then nudged users to top up unnecessarily.
Return `{ credits: null }` on error so `credits !== null` in the
provider short-circuits, and cover the regression with an integration
test that mocks the credits endpoint to 500.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The pre-merge branch rendered LowCreditBanner at the top of the page, and the dev merge added a second copy inside LibraryAgentList (the intended placement per PR #13208). Keep only the LibraryAgentList one. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>


Why / What / How
Why: When a user runs out of automation credits in prod, there's no call-to-action anywhere in the app to top up. Agents started manually or via Autopilot still launch and then fail, and the failure messaging doesn't make the cause clear. This adds the missing top-up prompts so users can recover in-place. (Origin: eng-general discussion.)
What: Three coordinated, payment-flag-gated surfaces that appear only when the automation-credit balance is at
$0and auto-refill is not enabled:All three open the same inline top-up dialog, which reuses the existing
requestTopUp→ Stripe checkout flow (no new payment paths).How: A single
TopUpPromptProvideris mounted in(platform)/layout.tsx. It reads the balance + auto-refill config via the existinguseCreditshook and derives one source of truth,isOutOfCredits(ENABLE_PLATFORM_PAYMENTon,credits <= 0, auto-refill config loaded, and auto-refill not active). It owns the sharedTopUpDialogand exposes{ isOutOfCredits, openTopUp }via context. ADailyTopUpAutoOpener— mounted only when out of credits — opens the dialog once per day (guarded by a localStorage date stamp, via the shareduseMountEffect; no rawuseEffect). The two banners are thin context consumers that dismiss-for-the-day. The inline amount form was extracted fromWalletRefill's one-time top-up tab into a sharedTopUpFormso both reuse identical Stripe logic.Out of scope (intentionally separate): clarifying the run/task failure message for insufficient credits — that needs a new backend error code and is better as its own PR.
Changes 🏗️
src/components/layout/TopUpPrompt/:TopUpPromptProvider.tsx+useTopUpPrompt.ts— context,isOutOfCreditsderivation, shared dialog state.DailyTopUpAutoOpener.tsx— once-per-day modal auto-open (conditional-mount +useMountEffect).TopUpDialog/TopUpDialog.tsx— controlled, responsive dialog (drawer on mobile).TopUpForm/(TopUpForm.tsx+useTopUpForm.ts) — inline amount entry, extracted fromWalletRefill.LowCreditBanner/(LowCreditBanner.tsx+useLowCreditBanner.ts) — dismissable warning banner.helpers.ts—wasShownToday/markShownTodaydate helpers.src/app/(platform)/layout.tsx— mount the provider around page content.src/app/(platform)/library/page.tsxandcopilot/CopilotPage.tsx— render the banner.WalletRefill.tsx— consume the sharedTopUpForm(behavior-preserving).src/services/storage/local-storage.ts— two new keys (modal-shown / banner-dismissed).Known follow-ups (non-blocking):
GET /credits/auto-top-uperrors, the prompt fails closed (no nudge); intentional, worth a documenting comment later.TopUpForminherited a__legacy__Form/FormFieldimport fromWalletRefill(pre-existing); migrate both off legacy form primitives in a dedicated cleanup.Checklist 📋
For code changes:
credits=0, CTA opens the dialog, hidden when credits remain; Autopilot banner shows atcredits=0, hidden otherwise; provider auto-opens the modal once/day, stays closed when already shown today, and suppresses everything when auto-refill is enabled or the payment flag is off; date helpers.ENABLE_PLATFORM_PAYMENTon and a$0account (no auto-refill), load the app → daily modal appears once; reload same day → no modal; visit Library and Autopilot → banner + "Top up" CTA; click → dialog → enter amount → redirected to Stripe checkout.$0; with a positive balance → nothing shows.For configuration changes:
.env.defaultis updated or already compatible with my changesdocker-compose.ymlis updated or already compatible with my changes