118 lines
3.5 KiB
JavaScript
118 lines
3.5 KiB
JavaScript
/**
|
|
* Local user — replaces the hosted login/session that used to gate the app.
|
|
*
|
|
* There is no sign-in step and no session to check: the app opens straight into
|
|
* the KROW UI as a single local operator. The user is a row in the local `User`
|
|
* collection, so profile edits (`updateMe`) and the User Management page write
|
|
* to the same place, and both survive a reload via localStorage.
|
|
*
|
|
* `logout()` stays because the menu item does — locally it resets the profile to
|
|
* its defaults and keeps the user in the app.
|
|
*/
|
|
|
|
import { entities } from './store';
|
|
import defaultAvatar from '@/assets/krow-logo.webp';
|
|
|
|
export const LOCAL_USER_ID = 'local-user';
|
|
|
|
/** The default operator profile: enough for every UI state that reads a user. */
|
|
export const DEFAULT_USER = {
|
|
id: LOCAL_USER_ID,
|
|
full_name: 'KROW Operator',
|
|
email: 'operator@krow.local',
|
|
// `role` is the platform-level flag the UI checks for admin-only affordances;
|
|
// `user_role` drives which navigation set the sidebar renders.
|
|
role: 'admin',
|
|
user_role: 'admin',
|
|
company_name: 'KROW',
|
|
phone: '(415) 555-0100',
|
|
profile_picture: null,
|
|
is_active: true,
|
|
};
|
|
|
|
/**
|
|
* Kept in sync with the User collection so anything reading the user
|
|
* synchronously (created_by stamping, for instance) sees current values.
|
|
*/
|
|
let currentUser = { ...DEFAULT_USER };
|
|
/**
|
|
* Memoised, not a boolean flag: several components ask for the user at once on
|
|
* first paint, and two concurrent hydrations would each miss the row and create
|
|
* a duplicate.
|
|
*/
|
|
let hydration = null;
|
|
|
|
function hydrate() {
|
|
if (!hydration) {
|
|
hydration = (async () => {
|
|
const rows = await entities.User.filter({ id: LOCAL_USER_ID });
|
|
let user = rows.length
|
|
? { ...DEFAULT_USER, ...rows[0] }
|
|
: await entities.User.create({ ...DEFAULT_USER });
|
|
|
|
if (user.profile_picture === defaultAvatar) {
|
|
user = await entities.User.update(LOCAL_USER_ID, { profile_picture: null });
|
|
}
|
|
|
|
currentUser = user;
|
|
return currentUser;
|
|
})();
|
|
}
|
|
return hydration;
|
|
}
|
|
|
|
export const getCurrentUser = () => currentUser;
|
|
|
|
/**
|
|
* Forget the cached profile — used after local data is reset, so the next read
|
|
* re-creates the operator row instead of writing to one that is gone.
|
|
*/
|
|
export function resetLocalUser() {
|
|
hydration = null;
|
|
currentUser = { ...DEFAULT_USER };
|
|
}
|
|
|
|
/** Write a patch to the profile row, re-creating it if it has been wiped. */
|
|
async function writeProfile(patch) {
|
|
await hydrate();
|
|
try {
|
|
currentUser = await entities.User.update(LOCAL_USER_ID, patch);
|
|
} catch {
|
|
currentUser = await entities.User.create({ ...DEFAULT_USER, ...patch, id: LOCAL_USER_ID });
|
|
}
|
|
return { ...currentUser };
|
|
}
|
|
|
|
export const auth = {
|
|
/** The current (local) user. Never throws and never hits the network. */
|
|
async me() {
|
|
return { ...(await hydrate()) };
|
|
},
|
|
|
|
/** Same value as `me()`, kept because some call sites use this name. */
|
|
async currentUser() {
|
|
return { ...(await hydrate()) };
|
|
},
|
|
|
|
/** Always true — the app has no unauthenticated state any more. */
|
|
async isAuthenticated() {
|
|
return true;
|
|
},
|
|
|
|
/** Patch the local profile. Persisted with the rest of the local data. */
|
|
async updateMe(data = {}) {
|
|
return writeProfile(data);
|
|
},
|
|
|
|
/**
|
|
* There is no session to end. Reset the profile back to its defaults and stay
|
|
* in the app, so the "Sign out" menu item does something honest instead of
|
|
* bouncing the user to an external login page.
|
|
*/
|
|
async logout() {
|
|
return writeProfile({ ...DEFAULT_USER });
|
|
},
|
|
};
|
|
|
|
export default auth;
|