/** * Application data client. * * The reference app talks to a Base44 backend through this module. The demo * keeps the module path, the export name, and the full method contract, and * swaps the transport for the local store in `store.js` and the local AI engine * in `aiEngine.js`. Nothing downstream — hooks, pages, components — knows or * cares, which is exactly the point: the seam stays where it was. */ import { createEntity, initStore, resetStore } from './store'; import { invokeLLM, uploadFile } from './aiEngine'; import { DEMO_USER, seedData } from './seed'; initStore(seedData); const ENTITY_NAMES = [ 'JobPosting', 'JobApplication', 'AIInterview', 'Staff', 'WorkerProfile', 'Course', 'Badge', 'LearningPath', 'Certification', 'RoleCategory', 'UserActivity', 'Evidence', 'User', /* Who is on which position, and for how long. The record that turns "hired" into workforce allocation: without it a position knows its demand and its applicants but not who is actually covering it. */ 'Assignment', /* Shifts worked, missed and overrun. The operational record behind attendance and overtime analysis — see `api/attendanceSeed.js`. */ 'ShiftRecord', ]; const entities = Object.fromEntries( ENTITY_NAMES.map((name) => [name, createEntity(name)]) ); /* ── Auth ──────────────────────────────────────────────────────────────── */ const SESSION_KEY = 'krow_demo_user'; function loadUser() { try { const raw = localStorage.getItem(SESSION_KEY); return raw ? { ...DEMO_USER, ...JSON.parse(raw) } : { ...DEMO_USER }; } catch { return { ...DEMO_USER }; } } let currentUser = loadUser(); /** * Writes the session user, and says whether it actually landed. * * The old version was `try { setItem } catch {}` — a swallowed * `QuotaExceededError` or a private-browsing refusal, and the caller was handed * a user object indistinguishable from a successful write. For preferences that * is invisible; for `customSkills`, which is where every account-authored skill * definition lives, it is the whole "I saved it and it was gone" report: the * toast said added, the list showed it, the reload did not. * * The read-back matters as much as the catch. A write can be accepted and then * evicted, and a serialisation can land truncated; comparing what came back * with what went in is the only way to know the record is really there. */ function persistUser() { const payload = JSON.stringify(currentUser); try { localStorage.setItem(SESSION_KEY, payload); } catch (error) { return { persisted: false, error }; } try { if (localStorage.getItem(SESSION_KEY) !== payload) { return { persisted: false, error: new Error('The session record did not survive the write.') }; } } catch (error) { return { persisted: false, error }; } return { persisted: true, error: null }; } const auth = { /** The demo is always signed in as the seeded employer/admin user. */ async me() { return { ...currentUser }; }, async updateMe(patch) { currentUser = { ...currentUser, ...patch }; persistUser(); return { ...currentUser }; }, /** * Preferences, read synchronously. * * `me()` is async because the real client fetches, but the panel provider has * to decide whether Owliver starts open during its first render — one tick * later is a visible flash of the wrong workspace. The session user is already * hydrated from localStorage at module load, so this is a plain read of the * same record `me()` returns, not a second copy of the state. */ preferences() { return { ...DEMO_USER.preferences, ...(currentUser.preferences || {}) }; }, /** * Merges into the stored preferences and persists with the rest of the user. * * Returns the write's outcome alongside the record, rather than the record * alone. Preferences are where account-authored skills live, so "did this * survive the reload" is a question the caller has to be able to answer — * see `persistUser`. */ async updatePreferences(patch) { currentUser = { ...currentUser, preferences: { ...auth.preferences(), ...patch } }; const write = persistUser(); return { user: { ...currentUser }, ...write }; }, isAuthenticated() { return true; }, /** * There is no identity provider to sign out of, so this clears the local * session and returns to the requested page. */ logout(redirectTo = '/') { try { localStorage.removeItem(SESSION_KEY); } catch { // Ignore. } currentUser = { ...DEMO_USER }; window.location.href = typeof redirectTo === 'string' ? redirectTo : '/'; }, redirectToLogin() { window.location.href = '/'; }, }; /* ── Integrations & analytics ───────────────────────────────────────────── */ const integrations = { Core: { InvokeLLM: invokeLLM, UploadFile: uploadFile, }, }; const analytics = { track({ eventName, properties } = {}) { if (import.meta.env.DEV) { console.debug('[krow-demo] analytics', eventName, properties || {}); } }, }; export const base44 = { entities, auth, integrations, analytics }; /** Restores the shipped demo data, discarding local edits. */ export function resetDemoData() { resetStore(seedData); window.location.reload(); }