118 lines
6.3 KiB
Markdown
118 lines
6.3 KiB
Markdown
---
|
||
task: Fix homepage fragment persisting from previous login when switching account types
|
||
cycles: 5
|
||
context: true
|
||
private: false
|
||
started: 2026-05-17T16:22:34Z
|
||
finished: 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.
|
||
|
||
```js
|
||
// 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:
|
||
```js
|
||
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.
|
||
|
||
```js
|
||
// useAuth.js – add export:
|
||
export function resetRole() {
|
||
globalRole.value = UserTypes.PUBLIC;
|
||
isFetching.value = false;
|
||
sessionStorage.removeItem('user_acct_type');
|
||
}
|
||
```
|
||
|
||
```js
|
||
// 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.
|
||
|
||
```js
|
||
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:
|
||
|
||
1. `user.js` Pinia store state factory runs: `acctType = sessionStorage.getItem('user_acct_type') || null`
|
||
2. If sessionStorage still has the PREVIOUS user's type (e.g. 'ultimate') — possible because `fetchCurrentUser()` on the login page ran AFTER `Login.vue` cleared sessionStorage and re-set it — `acctType = 'ultimate'`
|
||
3. `app.js` calls `userStore.fetchCurrentUser()` (async)
|
||
4. Guard at user.js:78 fires: `!this.acctType` = false, `this.acctType !== 'public'` = true → **guard is false → role NOT re-fetched from server**
|
||
5. `userStore.user` is updated to the NEW user's data but `acctType` stays 'ultimate'
|
||
6. `role` computed in `useAuth.js` returns `userStore.acctType = 'ultimate'`
|
||
7. 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') || null`
|
||
- `user.js:78-84` — the stale guard (primary bug)
|
||
- `user.js:190` — `resetCurrentUser()` only clears `user`, not `acctType`
|
||
- `useAuth.js:7` — `globalRole` module-level ref
|
||
- `useAuth.js:51-53` — module-level fetchRole() call (won't fire if sessionStorage is non-public)
|
||
- `useAuth.js:76-86` — `role` computed priority: userStore.acctType first
|
||
- `Home.vue:15-20` — destructures from `useAuth()` 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.
|