6.3 KiB
task, cycles, context, private, started, finished
| task | cycles | context | private | started | finished |
|---|---|---|---|---|---|
| Fix homepage fragment persisting from previous login when switching account types | 5 | true | false | 2026-05-17T16:22:34Z | 2026-05-17T16:24:00Z |
files
- resources/js/stores/user.js [lines 63-95] — fetchCurrentUser() has a guard that skips role re-fetch when acctType is already set; this allows stale sessionStorage role to persist
- resources/js/Pages/Home.vue [lines 1-21] — renders role fragments before userStore loading is complete, causing flash of wrong fragment
- resources/js/composables/Core/useAuth.js [lines 7-53] — module-level globalRole ref; module-level fetchRole() guard prevents re-fetch when sessionStorage already has a non-public role
- resources/js/Pages/Auth/Login.vue [lines 13-38] — sessionStorage.clear() in onMounted runs synchronously, but app.js fetchCurrentUser() is async and can set sessionStorage AFTER the clear
steps
Step 1 — Fix stale-role guard in fetchCurrentUser() (user.js:78-84)
The guard if (!this.acctType || this.acctType === 'public') prevents re-fetching the real server-side role when acctType is already populated from sessionStorage. This is the primary bug: after login, if sessionStorage has the old user's role (e.g. 'ultimate'), acctType initialises to 'ultimate' from the store state factory, the guard is false, and the role is never corrected against the server — even though the session now belongs to a completely different user.
Remove the guard entirely so that fetchCurrentUser() always re-fetches acct_type from /get/user/acct-type after successfully loading the user object.
// user.js – fetchCurrentUser(), after setting this.user:
// REMOVE the `if (!this.acctType || this.acctType === 'public')` wrapper.
// Always fetch and overwrite acctType from the server.
const resRole = await axios.get('/get/user/acct-type')
if (resRole.data && resRole.data.acct_type) {
this.acctType = resRole.data.acct_type
sessionStorage.setItem('user_acct_type', this.acctType)
}
Keep the 401/419 catch path unchanged — it already sets acctType = 'public'.
Step 2 — Add loading guard to Home.vue before rendering role fragments
While userStore.loading is true (fetchCurrentUser in flight), userStore.acctType may still hold the stale sessionStorage value. Guard all role-specific <template> blocks with a top-level loading state so no fragment renders until auth is confirmed.
In Home.vue add a userStore import and wrap the inner content:
import { useUserStore } from '../stores/user.js';
const userStore = useUserStore();
In the template, wrap the role switch inside a v-if="!userStore.loading || userStore.user" block. While loading and no user yet, show a minimal centered spinner (reuse the existing spinner pattern in the codebase — <div class="spinner-border text-primary">). Once userStore.user is resolved (non-null), the correct fragment renders reactively.
Step 3 — Reset globalRole in useAuth.js when Login.vue mounts
Export a resetRole() function from useAuth.js that sets globalRole.value = UserTypes.PUBLIC and removes sessionStorage.user_acct_type. Call it from Login.vue's onMounted AFTER sessionStorage.clear().
This ensures the module-level globalRole is synchronously reset before any async work sets it again, eliminating any window where the stale in-memory value could be read.
// useAuth.js – add export:
export function resetRole() {
globalRole.value = UserTypes.PUBLIC;
isFetching.value = false;
sessionStorage.removeItem('user_acct_type');
}
// Login.vue – onMounted, after sessionStorage.clear():
import { resetRole } from '../../composables/Core/useAuth.js';
import { useUserStore } from '../../stores/user.js';
onMounted(() => {
sessionStorage.clear();
localStorage.clear();
resetRole();
useUserStore().resetCurrentUser(); // clears user + acctType
// existing SW unregistration code...
});
Step 4 — Extend resetCurrentUser() in user.js to also clear acctType
Currently resetCurrentUser() only sets this.user = null. Extend it to also reset acctType and remove the sessionStorage key so the role computed falls back to PUBLIC on next render.
resetCurrentUser() {
this.user = null;
this.acctType = null;
sessionStorage.removeItem('user_acct_type');
},
context
Root cause trace
On a fresh page load at / after logging in as a different user:
user.jsPinia store state factory runs:acctType = sessionStorage.getItem('user_acct_type') || null- If sessionStorage still has the PREVIOUS user's type (e.g. 'ultimate') — possible because
fetchCurrentUser()on the login page ran AFTERLogin.vuecleared sessionStorage and re-set it —acctType = 'ultimate' app.jscallsuserStore.fetchCurrentUser()(async)- Guard at user.js:78 fires:
!this.acctType= false,this.acctType !== 'public'= true → guard is false → role NOT re-fetched from server userStore.useris updated to the NEW user's data butacctTypestays 'ultimate'rolecomputed inuseAuth.jsreturnsuserStore.acctType = 'ultimate'- Home.vue renders
<HomeUltimate />for the new STORE_OWNER user
Key reactive chain
role (computed) = userStore.acctType || currentUser.value?.acct_type || globalRole.value
↑ populated from sessionStorage on store init → stale
Affected files key refs
user.js:12—acctType: sessionStorage.getItem('user_acct_type') || nulluser.js:78-84— the stale guard (primary bug)user.js:190—resetCurrentUser()only clearsuser, notacctTypeuseAuth.js:7—globalRolemodule-level refuseAuth.js:51-53— module-level fetchRole() call (won't fire if sessionStorage is non-public)useAuth.js:76-86—rolecomputed priority: userStore.acctType firstHome.vue:15-20— destructures fromuseAuth()with no loading guard
notes
- dictionary: ai-docs/dictionary.md
- linters: eslint, tsc (via vite)
- constraints: Hypervel SPA — full page reloads happen after login/logout; sessionStorage persists within a tab across reloads. The guard removal in step 1 adds one extra request per page load but is required for correctness. The loading guard in step 2 may briefly show a spinner before the dashboard; this is acceptable and far better than showing the wrong dashboard.