fix(users): refetch billing subscription on auth state change#4125
Conversation
Add an immediate watcher on authStore.isLoggedIn in UserView that calls billingStore.fetchSubscription() on login, so the Subscriptions tab appears without a page refresh. Errors are silently caught so the UI still works on billing-disabled servers.
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughUserView now integrates the billing store via ChangesBilling Subscription Refetch on Auth Change
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ 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 |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #4125 +/- ##
=======================================
Coverage 99.54% 99.54%
=======================================
Files 31 31
Lines 1089 1089
Branches 302 302
=======================================
Hits 1084 1084
Misses 5 5 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR addresses a post-login stale billing state on /users by refetching the billing subscription when authentication status changes, so the billing UI can populate without requiring a full page refresh.
Changes:
- Injects
billingStoreintoUserViewand adds animmediatewatcher onauthStore.isLoggedInto triggerbillingStore.fetchSubscription()after login. - Updates the
setup()JSDoc to reflect the additional wiring. - Adds unit tests covering the watcher behavior (mount logged-in/logged-out, false→true transition, and error swallowing).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/modules/users/views/user.view.vue | Adds billingStore and an auth-state watcher that refetches subscription data post-login. |
| src/modules/users/tests/user.view.unit.tests.js | Adds a new test block validating the watcher triggers and that errors are swallowed. |
| * Without this, the Subscriptions tab condition re-evaluates from a stale | ||
| * empty billingStore after login and only appears on full page refresh. |
| // Flush microtasks so the immediate watcher handler resolves | ||
| await new Promise((r) => setTimeout(r, 0)); | ||
| expect(billingStore.fetchSubscription).toHaveBeenCalledTimes(1); |
| describe('UserView – billing refetch on auth state change', () => { | ||
| let authStore; | ||
| let billingStore; | ||
| let organizationsStore; | ||
|
|
||
| beforeEach(() => { | ||
| setActivePinia(createPinia()); | ||
| authStore = useAuthStore(); | ||
| billingStore = useBillingStore(); | ||
| organizationsStore = useOrganizationsStore(); | ||
| organizationsStore.fetchOrganizations = vi.fn().mockResolvedValue([]); | ||
| }); | ||
|
|
||
| it('calls fetchSubscription immediately when isLoggedIn is true at mount', async () => { |
| 'authStore.isLoggedIn': { | ||
| immediate: true, | ||
| async handler(loggedIn) { | ||
| if (loggedIn) { | ||
| await this.billingStore.fetchSubscription().catch(() => {}); | ||
| } |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/modules/users/views/user.view.vue (1)
180-208:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftMove the billing self-registration out of the users module.
src/modules/users/**is a core module, but this change adds a new directbillingstore dependency to it. That hard-couplesusersto an optional module and breaks the module boundary the repo enforces. The auth-change refetch should be registered from the billing module/store itself rather than pulled intoUserView.As per coding guidelines "Core modules (
core,auth,users,home,app) must not reference optional module names in code (imports, string maps, route names, store references, config keys). Optional modules self-register behavior".🤖 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 `@src/modules/users/views/user.view.vue` around lines 180 - 208, This file introduces a direct dependency on the billing module (importing useBillingStore, BillingSubscriptionsComponent, billingStore and using isPlanActive/useBilling) inside the users core module; remove those billing imports, component references and billingStore usage from UserView (remove useBilling(), useBillingStore(), BillingSubscriptionsComponent from components and from setup return) and instead wire the auth-change refetch and any self-registration logic inside the billing module/store (e.g., register an auth-change listener inside the billing store or a billing module setup function so the optional billing behavior self-registers without the users module referencing billing symbols).
🤖 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 `@src/modules/users/tests/user.view.unit.tests.js`:
- Around line 287-367: The tests only verify fetchSubscription calls but not the
UI change; add a test in the UserView suite that mounts while
authStore.cookieExpire = 0, then toggles authStore.cookieExpire to a future time
and waits, and assert that wrapper.vm.showSubscriptionsTab === true (or assert
the subscriptions tab DOM is rendered via
wrapper.find(`[data-test="subscriptions-tab"]`).exists()). Ensure you set any
required organization/admin state on organizationsStore so showSubscriptionsTab
can become true and reuse the existing shallowMount setup and microtask flush
(await new Promise(r => setTimeout(r,0))) to let the watcher run.
In `@src/modules/users/views/user.view.vue`:
- Around line 275-288: Add a JSDoc header above the async watcher handler for
'authStore.isLoggedIn' describing its purpose (refetch billing subscription on
auth change), include a `@param` {boolean} loggedIn description, and add a
`@returns` {Promise<void>} annotation because handler is async; keep the existing
behavior calling this.billingStore.fetchSubscription().catch(() => {}) so the
silent catch is preserved in the documented function.
- Around line 275-288: The watcher for 'authStore.isLoggedIn' currently only
refetches billingStore.fetchSubscription(), but the Subscriptions tab visibility
(showSubscriptionsTab) depends on hasOwnerOrAdminRole which reads
organizationsStore.organizations, so update the watcher to also refresh
organizations data on login: call/await organizationsStore.fetchOrganizations()
(or the existing organizations-fetch method) alongside
billingStore.fetchSubscription(), handling errors silently as done for
billingStore.fetchSubscription(), so organizationsStore.organizations is
repopulated and hasOwnerOrAdminRole recalculates.
---
Outside diff comments:
In `@src/modules/users/views/user.view.vue`:
- Around line 180-208: This file introduces a direct dependency on the billing
module (importing useBillingStore, BillingSubscriptionsComponent, billingStore
and using isPlanActive/useBilling) inside the users core module; remove those
billing imports, component references and billingStore usage from UserView
(remove useBilling(), useBillingStore(), BillingSubscriptionsComponent from
components and from setup return) and instead wire the auth-change refetch and
any self-registration logic inside the billing module/store (e.g., register an
auth-change listener inside the billing store or a billing module setup function
so the optional billing behavior self-registers without the users module
referencing billing symbols).
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: a9f1e7c3-6883-4cf0-bb19-a889462713c2
📒 Files selected for processing (2)
src/modules/users/tests/user.view.unit.tests.jssrc/modules/users/views/user.view.vue
| describe('UserView – billing refetch on auth state change', () => { | ||
| let authStore; | ||
| let billingStore; | ||
| let organizationsStore; | ||
|
|
||
| beforeEach(() => { | ||
| setActivePinia(createPinia()); | ||
| authStore = useAuthStore(); | ||
| billingStore = useBillingStore(); | ||
| organizationsStore = useOrganizationsStore(); | ||
| organizationsStore.fetchOrganizations = vi.fn().mockResolvedValue([]); | ||
| }); | ||
|
|
||
| it('calls fetchSubscription immediately when isLoggedIn is true at mount', async () => { | ||
| authStore.cookieExpire = Date.now() + 3600000; // logged in | ||
| billingStore.fetchSubscription = vi.fn().mockResolvedValue(null); | ||
|
|
||
| shallowMount(UserView, { | ||
| global: { | ||
| mocks: sharedMocks(), | ||
| stubs: sharedStubs, | ||
| }, | ||
| }); | ||
|
|
||
| // Flush microtasks so the immediate watcher handler resolves | ||
| await new Promise((r) => setTimeout(r, 0)); | ||
| expect(billingStore.fetchSubscription).toHaveBeenCalledTimes(1); | ||
| }); | ||
|
|
||
| it('does not call fetchSubscription when isLoggedIn is false at mount', async () => { | ||
| authStore.cookieExpire = 0; // logged out | ||
| billingStore.fetchSubscription = vi.fn().mockResolvedValue(null); | ||
|
|
||
| shallowMount(UserView, { | ||
| global: { | ||
| mocks: sharedMocks(), | ||
| stubs: sharedStubs, | ||
| }, | ||
| }); | ||
|
|
||
| await new Promise((r) => setTimeout(r, 0)); | ||
| expect(billingStore.fetchSubscription).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('calls fetchSubscription when isLoggedIn transitions from false to true', async () => { | ||
| authStore.cookieExpire = 0; // start logged out | ||
| billingStore.fetchSubscription = vi.fn().mockResolvedValue(null); | ||
|
|
||
| shallowMount(UserView, { | ||
| global: { | ||
| mocks: sharedMocks(), | ||
| stubs: sharedStubs, | ||
| }, | ||
| }); | ||
|
|
||
| await new Promise((r) => setTimeout(r, 0)); | ||
| expect(billingStore.fetchSubscription).not.toHaveBeenCalled(); | ||
|
|
||
| // Simulate login | ||
| authStore.cookieExpire = Date.now() + 3600000; | ||
| await new Promise((r) => setTimeout(r, 0)); | ||
| expect(billingStore.fetchSubscription).toHaveBeenCalledTimes(1); | ||
| }); | ||
|
|
||
| it('swallows fetchSubscription errors silently (no UI hang)', async () => { | ||
| authStore.cookieExpire = Date.now() + 3600000; // logged in | ||
| billingStore.fetchSubscription = vi.fn().mockRejectedValue(new Error('Network error')); | ||
|
|
||
| // Should not throw | ||
| const wrapper = shallowMount(UserView, { | ||
| global: { | ||
| mocks: sharedMocks(), | ||
| stubs: sharedStubs, | ||
| }, | ||
| }); | ||
|
|
||
| await new Promise((r) => setTimeout(r, 0)); | ||
| // Component is still alive — no uncaught error | ||
| expect(wrapper.vm).toBeTruthy(); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
These tests miss the user-visible regression the PR claims to fix.
The suite only proves that fetchSubscription() runs; it never asserts that the Subscriptions tab becomes visible after a logged-out mount followed by login. Since showSubscriptionsTab is computed from organization/admin state rather than billing state, all of these tests can pass while the tab still never appears. Please add a logged-out → logged-in test that asserts wrapper.vm.showSubscriptionsTab or the rendered subscriptions tab itself.
🤖 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 `@src/modules/users/tests/user.view.unit.tests.js` around lines 287 - 367, The
tests only verify fetchSubscription calls but not the UI change; add a test in
the UserView suite that mounts while authStore.cookieExpire = 0, then toggles
authStore.cookieExpire to a future time and waits, and assert that
wrapper.vm.showSubscriptionsTab === true (or assert the subscriptions tab DOM is
rendered via wrapper.find(`[data-test="subscriptions-tab"]`).exists()). Ensure
you set any required organization/admin state on organizationsStore so
showSubscriptionsTab can become true and reuse the existing shallowMount setup
and microtask flush (await new Promise(r => setTimeout(r,0))) to let the watcher
run.
user.view.vue already imports billing pre-PR (useBilling composable + BillingSubscriptionsComponent). My commit adds useBillingStore consistent with the existing pattern. The Major architectural refactor (move ALL billing references out of users module) is out of scope for this fix-PR — should be tracked as a separate tech-debt issue if the boundary rule is genuinely enforced. The 2 actionable minor flags (refetch organizations on login + JSDoc on watcher) ARE addressed in commit follow-up.
Summary
Implements Task 4 of
docs/superpowers/plans/2026-05-11-pricing-followup-7-items.md— fixes lazy Subscriptions tab on/usersafter fresh login.watch(() => authStore.isLoggedIn, ...)with{ immediate: true }inuser.view.vuebillingStore.fetchSubscription()on auth state change (silent catch — UI must work without sub)Why
Previously the tabs array was computed once at mount with an empty billing store. Users had to refresh
/usersafter login to see the Subscriptions tab.Test plan
Reviews
Summary by CodeRabbit
New Features
Bug Fixes
Tests