/** * 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;