Skip to content

fix(users): refetch billing subscription on auth state change#4125

Merged
PierreBrisorgueil merged 2 commits into
masterfrom
fix-billing-refetch-on-auth
May 11, 2026
Merged

fix(users): refetch billing subscription on auth state change#4125
PierreBrisorgueil merged 2 commits into
masterfrom
fix-billing-refetch-on-auth

Conversation

@PierreBrisorgueil
Copy link
Copy Markdown
Collaborator

@PierreBrisorgueil PierreBrisorgueil commented May 11, 2026

Summary

Implements Task 4 of docs/superpowers/plans/2026-05-11-pricing-followup-7-items.md — fixes lazy Subscriptions tab on /users after fresh login.

  • Adds watch(() => authStore.isLoggedIn, ...) with { immediate: true } in user.view.vue
  • Triggers billingStore.fetchSubscription() on auth state change (silent catch — UI must work without sub)
  • Tab list re-evaluates once the store populates, no page refresh needed

Why

Previously the tabs array was computed once at mount with an empty billing store. Users had to refresh /users after login to see the Subscriptions tab.

Test plan

  • Unit test added (84 lines) — covers watcher fires on login, swallows errors silently, no redundant call when already logged in
  • Manual : sign out, clear TOKEN, sign in → Subscriptions tab appears WITHOUT refresh

Reviews

  • Phase 0 critical-review via DeepSeek : OK with nits (1 medium = by-spec silent catch, 2 low, 1 nit). No blockers.

Summary by CodeRabbit

  • New Features

    • Billing subscription information now automatically refetches whenever your authentication state changes, ensuring current subscription details are always available.
  • Bug Fixes

    • Improved error handling to keep the user interface functional when billing data fails to load or during service disruptions.
  • Tests

    • Comprehensive test coverage added for billing subscription refetch behavior across login transitions and error scenarios.

Review Change Stack

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.
Copilot AI review requested due to automatic review settings May 11, 2026 19:09
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 11, 2026

Warning

Rate limit exceeded

@PierreBrisorgueil has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 52 minutes and 19 seconds before requesting another review.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: dbf4fd7e-69ea-4700-b8d3-0ce9c99a6faf

📥 Commits

Reviewing files that changed from the base of the PR and between d249c4c and e679517.

📒 Files selected for processing (1)
  • src/modules/users/views/user.view.vue

Walkthrough

UserView now integrates the billing store via useBillingStore() and exposes it from setup(). A new watcher on authStore.isLoggedIn automatically refetches billing subscription data when the user logs in, catching errors silently to maintain UI stability. Tests validate the refetch behavior across mount states, login transitions, and error conditions.

Changes

Billing Subscription Refetch on Auth Change

Layer / File(s) Summary
Component Setup and Store Wiring
src/modules/users/views/user.view.vue
UserView imports useBillingStore and initializes billingStore in setup(), exposing it alongside existing auth/org stores and isPlanActive.
Auth State Watcher for Billing Refetch
src/modules/users/views/user.view.vue
Watcher on authStore.isLoggedIn calls billingStore.fetchSubscription() asynchronously when the user logs in, with empty catch to ignore fetch errors.
Tests for Billing Refetch Behavior
src/modules/users/tests/user.view.unit.tests.js
New test suite covers immediate fetch on authenticated mount, no fetch on unauthenticated mount, fetch triggered by login transition, and silent error handling during subscription fetch.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • pierreb-devkit/Vue#3725: Introduces the billing store (useBillingStore and fetchSubscription) that this PR wires into UserView.
  • pierreb-devkit/Vue#3726: Modifies billing store fetching behavior that UserView now triggers on auth state changes.
  • pierreb-devkit/Vue#4059: Also modifies UserView to wire in billing store and manage subscriptions tab visibility.

Suggested labels

Fix

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The description covers key sections from the template including summary, why, test plan, and reviews, but omits several required Scope, Validation, and Guardrails sections from the template. Complete the missing template sections: Scope (modules impacted, cross-module impact, risk level), Validation (checklist items), and Guardrails check (secrets, renames, mergeability, tests confirmation).
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding billing subscription refetch on auth state change to fix a lazy tab visibility issue after login.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-billing-refetch-on-auth

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.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 11, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.54%. Comparing base (d862fbb) to head (e679517).

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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 billingStore into UserView and adds an immediate watcher on authStore.isLoggedIn to trigger billingStore.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.

Comment thread src/modules/users/views/user.view.vue Outdated
Comment on lines +277 to +278
* Without this, the Subscriptions tab condition re-evaluates from a stale
* empty billingStore after login and only appears on full page refresh.
Comment on lines +311 to +313
// Flush microtasks so the immediate watcher handler resolves
await new Promise((r) => setTimeout(r, 0));
expect(billingStore.fetchSubscription).toHaveBeenCalledTimes(1);
Comment on lines +287 to +300
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 () => {
Comment thread src/modules/users/views/user.view.vue Outdated
Comment on lines +281 to +286
'authStore.isLoggedIn': {
immediate: true,
async handler(loggedIn) {
if (loggedIn) {
await this.billingStore.fetchSubscription().catch(() => {});
}
coderabbitai[bot]
coderabbitai Bot previously requested changes May 11, 2026
Copy link
Copy Markdown

@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: 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 lift

Move the billing self-registration out of the users module.

src/modules/users/** is a core module, but this change adds a new direct billing store dependency to it. That hard-couples users to 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 into UserView.

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

📥 Commits

Reviewing files that changed from the base of the PR and between d862fbb and d249c4c.

📒 Files selected for processing (2)
  • src/modules/users/tests/user.view.unit.tests.js
  • src/modules/users/views/user.view.vue

Comment on lines +287 to +367
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();
});
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ 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.

Comment thread src/modules/users/views/user.view.vue
@PierreBrisorgueil PierreBrisorgueil dismissed coderabbitai[bot]’s stale review May 11, 2026 19:17

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.

@PierreBrisorgueil PierreBrisorgueil merged commit 5a3425a into master May 11, 2026
6 checks passed
@PierreBrisorgueil PierreBrisorgueil deleted the fix-billing-refetch-on-auth branch May 11, 2026 19:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants