// ============================================================================ // ChurchStore — talks to api/index.php on the same domain. // If the backend isn't reachable (opened from a file path, or api/ not yet // uploaded), every method fails softly and the app keeps using localStorage, // so nothing breaks during setup. // ============================================================================ (function () { // Folder that index.html lives in -> api/ sits next to it. const baseDir = window.location.href.replace(/[?#].*$/, '').replace(/[^/]*$/, ''); const API = baseDir + 'api/index.php'; let available = null; // null = unknown, true/false once we've tried async function call(action, opts) { const res = await fetch(API + '?action=' + action, opts); if (!res.ok) throw new Error('HTTP ' + res.status); return res.json(); } // Slides are stored on the server as files (URLs). Strip any legacy base64 // data: images before sending config up — they're device-local and huge. function slimConfig(config) { if (!config) return config; const c = { ...config }; if (Array.isArray(c.slideImages)) { c.slideImages = c.slideImages.filter(s => s && typeof s.url === 'string' && !s.url.startsWith('data:')); } return c; } const Store = { get available() { return available; }, async fetchState() { try { const j = await call('state', { method: 'GET', headers: { Accept: 'application/json' } }); available = true; return { accounts: j.accounts || null, config: j.config || null }; } catch (e) { available = false; return null; } }, async putAccounts(accounts) { try { await call('accounts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(accounts) }); available = true; return true; } catch (e) { available = false; return false; } }, _cfgTimer: null, putConfig(config) { // debounce — settings/prayer edits can fire rapidly clearTimeout(this._cfgTimer); const payload = JSON.stringify(slimConfig(config)); this._cfgTimer = setTimeout(async () => { try { await call('config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: payload }); available = true; } catch (e) { available = false; } }, 500); }, async uploadSlides(fileList) { const fd = new FormData(); for (const f of fileList) fd.append('files[]', f); const j = await call('slides', { method: 'POST', body: fd }); available = true; return j; // { ok, files:[...], errors:[...], limits:{...} } } }; window.ChurchStore = Store; })();