Chores, Family page, genre search, child accounts, wider Search and Notifications center
build-container / build (push) Successful in 4m6s
build-container / build (push) Successful in 4m6s
- Chores tab with repeats, points and a week/month board - Family page: calendar, chores, shopping, notes, chat and tasks on one start page - Genre search in the Store, Free Books and LibriVox - Child accounts: safe content, daily limit and bedtime, ask a parent - Search now covers notes, chat, contacts, chores, recipes and your books - Notifications center: bell with a per-person list of chat, reminders and announcements - Roadmap, README and tests updated Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cf292e3bc7
commit
0383da87f5
38 files changed
+720
-79
No files matched your search
+13
-2
@@ -43,7 +43,13 @@ async function addPersonSheet() {
|
||||
function personSheet(u) {
|
||||
const self = u.id === ME.id, admin = u.role === 'admin';
|
||||
const el = openSheet(u.name, admin ? 'Admin' : u.disabled ? 'Turned off' : 'User', `<form id="uf"><label for="un">Name</label><input type="text" id="un" value="${esc(u.name)}" autocomplete="off" required>
|
||||
${admin ? '<p class="sub">Admins can use everything.</p>' : `<label>What can they use?</label>${tabBoxes(u.tabsChosen)}`}
|
||||
${admin ? '<p class="sub">Admins can use everything.</p>' : `<label>What can they use?</label>${tabBoxes(u.tabsChosen)}
|
||||
<h2 style="margin-top:14px">Child account</h2><label class="row" style="cursor:pointer"><span class="grow"><b>This is a child</b><small style="white-space:normal">Adds a daily time limit and a bedtime, and a safer Store and podcast search.</small></span><input type="checkbox" id="kon"${u.kid && u.kid.on ? ' checked' : ''}></label>
|
||||
<div id="kopt" ${u.kid && u.kid.on ? '' : 'hidden'}><label for="klim">Time per day, in minutes (empty for no limit)</label><input type="number" id="klim" min="0" max="720" value="${u.kid && u.kid.limitMin ? u.kid.limitMin : ''}" placeholder="120">
|
||||
<label class="row" style="cursor:pointer"><span class="grow"><b>Bedtime</b><small>The app is locked in these hours</small></span><input type="checkbox" id="kbo"${u.kid && u.kid.bedtime && u.kid.bedtime.on ? ' checked' : ''}></label>
|
||||
<div class="bar" style="gap:10px;flex-wrap:nowrap"><div style="flex:1"><label for="kbf">From</label><input type="time" id="kbf" value="${esc((u.kid && u.kid.bedtime && u.kid.bedtime.from) || '20:30')}"></div><div style="flex:1"><label for="kbt">To</label><input type="time" id="kbt" value="${esc((u.kid && u.kid.bedtime && u.kid.bedtime.to) || '07:00')}"></div></div>
|
||||
<label class="row" style="cursor:pointer"><span class="grow"><b>Safer Store and podcast search</b><small style="white-space:normal">Books and audiobooks from a few kid-friendly sources and genres, and no podcasts marked explicit</small></span><input type="checkbox" id="ksf"${!u.kid || u.kid.safe !== false ? ' checked' : ''}></label>
|
||||
<div class="row" id="kuse"><span class="grow"><small>Today</small><b id="kuset">…</b></span><button type="button" class="btn ghost" data-m="15">+15 min</button><button type="button" class="btn ghost" data-m="30">+30 min</button><button type="button" class="btn ghost" data-m="60">+1 h</button></div></div>`}
|
||||
<div class="bar"><button class="btn ghost" type="button" id="sx" style="flex:1;justify-content:center">Close</button><button class="btn" style="flex:1;justify-content:center">Save</button></div><div class="err" id="ue"></div></form>
|
||||
<div class="list" style="background:var(--panel2);border:0;margin-top:6px">
|
||||
<button class="row" id="u-inv">${icon('copy')}<span class="grow"><b>Invite code for a new device</b><small>Also the way back in if they lose their device</small></span></button>
|
||||
@@ -54,7 +60,12 @@ function personSheet(u) {
|
||||
el.querySelector('.sheet').classList.add('wide'); el.querySelector('#sx').onclick = closeSheet;
|
||||
const act = async (fn, msg) => { try { await fn(); if (msg) toast(msg); closeSheet(); loadPeople(); } catch (err) { toast(err.message); } };
|
||||
const put = body => jfetch('/api/users/' + encodeURIComponent(u.id), { method: 'PUT', body: JSON.stringify(body) });
|
||||
el.querySelector('#uf').onsubmit = e => { e.preventDefault(); act(() => put({ name: el.querySelector('#un').value, ...(admin ? {} : { tabs: pickedTabs(el) }) }), 'Saved'); };
|
||||
const kon = el.querySelector('#kon'); if (kon) {
|
||||
const fillUse = async () => { try { const r = await jfetch('/api/kid/usage?user=' + encodeURIComponent(u.id)); el.querySelector('#kuset').textContent = r.on ? r.used + ' min used' + (r.limit ? ' of ' + r.limit : ' (no limit)') + (r.locked ? ' · locked: ' + (r.locked === 'bedtime' ? 'bedtime' : 'time is up') : '') : 'Not saved as a child account yet'; } catch { el.querySelector('#kuset').textContent = ''; } };
|
||||
kon.onchange = () => { el.querySelector('#kopt').hidden = !kon.checked; }; fillUse();
|
||||
el.querySelectorAll('#kuse [data-m]').forEach(b => b.onclick = async () => { try { await jfetch('/api/kid/extra', { method: 'POST', body: JSON.stringify({ user: u.id, minutes: +b.dataset.m }) }); toast('Added ' + b.dataset.m + ' minutes for today'); fillUse(); } catch (err) { toast(err.message); } });
|
||||
}
|
||||
el.querySelector('#uf').onsubmit = e => { e.preventDefault(); act(() => put({ name: el.querySelector('#un').value, ...(admin ? {} : { tabs: pickedTabs(el), kid: { on: el.querySelector('#kon').checked, limitMin: +el.querySelector('#klim').value || 0, bedtime: { on: el.querySelector('#kbo').checked, from: el.querySelector('#kbf').value, to: el.querySelector('#kbt').value }, safe: el.querySelector('#ksf').checked } }) }), 'Saved'); };
|
||||
el.querySelector('#u-inv').onclick = async () => { try { const r = await jfetch('/api/users/' + encodeURIComponent(u.id) + '/invite', { method: 'POST', body: '{}' }); codeSheet(u.name, r.code, r.minutes); } catch (err) { toast(err.message); } };
|
||||
el.querySelector('#u-out').onclick = () => act(() => put({ signOutEverywhere: true }), u.name + ' was signed out everywhere');
|
||||
const off = el.querySelector('#u-off'); if (off) off.onclick = () => act(() => put({ disabled: !u.disabled }), u.disabled ? 'Turned back on' : 'Turned off');
|
||||
|
||||
Reference in new issue
Block a user