Public Access
Visitor sign in kiosk: multi-site, badge printing, WWCC expiry warnings, admin accounts with 2FA
This commit is contained in:
+54
-14
@@ -150,6 +150,9 @@ function resetState() {
|
||||
$$('#app input').forEach((i) => {
|
||||
i.value = '';
|
||||
});
|
||||
$$('#app select').forEach((sel) => {
|
||||
sel.selectedIndex = 0;
|
||||
});
|
||||
$$('.choice').forEach((b) => b.setAttribute('aria-pressed', 'false'));
|
||||
$('#check-number-field').hidden = true;
|
||||
$('#check-continue').hidden = true;
|
||||
@@ -182,16 +185,51 @@ tickClock();
|
||||
|
||||
/* --------------------------------------------------------------- hosts */
|
||||
|
||||
function renderHosts(listEl, searchValue, onPick) {
|
||||
const term = searchValue.trim().toLowerCase();
|
||||
/**
|
||||
* Two ways to say who you are visiting, because neither suits everyone: a dropdown
|
||||
* for someone who wants to browse a short list, and a filter box for someone who
|
||||
* knows the name and would rather type it. The filter narrows both, so the two
|
||||
* never disagree about what is on offer.
|
||||
*/
|
||||
function renderHosts(listEl, searchValue, onPick, selectEl = null) {
|
||||
const term = String(searchValue || '').trim().toLowerCase();
|
||||
const matches = term
|
||||
? hosts.filter((h) => h.name.toLowerCase().includes(term) || (h.area || '').toLowerCase().includes(term))
|
||||
? hosts.filter(
|
||||
(h) => h.name.toLowerCase().includes(term) || (h.area || '').toLowerCase().includes(term)
|
||||
)
|
||||
: hosts;
|
||||
|
||||
const choose = (id, name) => {
|
||||
state.hostId = Number(id);
|
||||
state.hostName = name;
|
||||
onPick();
|
||||
};
|
||||
|
||||
if (selectEl) {
|
||||
const placeholder =
|
||||
matches.length === hosts.length
|
||||
? `${hosts.length} ${hosts.length === 1 ? 'person' : 'people'} — choose a name`
|
||||
: `${matches.length} ${matches.length === 1 ? 'match' : 'matches'}`;
|
||||
selectEl.innerHTML =
|
||||
`<option value="">${escapeHtml(placeholder)}</option>` +
|
||||
matches
|
||||
.map(
|
||||
(h) =>
|
||||
`<option value="${h.id}">${escapeHtml(h.name)}${h.area ? ` — ${escapeHtml(h.area)}` : ''}</option>`
|
||||
)
|
||||
.join('');
|
||||
selectEl.disabled = matches.length === 0;
|
||||
selectEl.onchange = () => {
|
||||
const picked = hosts.find((h) => h.id === Number(selectEl.value));
|
||||
if (picked) choose(picked.id, picked.name);
|
||||
};
|
||||
}
|
||||
|
||||
if (!matches.length) {
|
||||
listEl.innerHTML = `<p class="host-empty">No one matches that. Check the spelling, or ask the front desk.</p>`;
|
||||
return;
|
||||
}
|
||||
|
||||
listEl.innerHTML = matches
|
||||
.slice(0, 60)
|
||||
.map(
|
||||
@@ -202,11 +240,7 @@ function renderHosts(listEl, searchValue, onPick) {
|
||||
)
|
||||
.join('');
|
||||
listEl.querySelectorAll('[data-host-id]').forEach((btn) => {
|
||||
btn.addEventListener('click', () => {
|
||||
state.hostId = Number(btn.dataset.hostId);
|
||||
state.hostName = btn.dataset.hostName;
|
||||
onPick();
|
||||
});
|
||||
btn.addEventListener('click', () => choose(btn.dataset.hostId, btn.dataset.hostName));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -413,7 +447,7 @@ async function loadSiteContext() {
|
||||
change.textContent = siteConfig.site ? `Site: ${siteConfig.site.name} — change` : 'Choose site';
|
||||
|
||||
hosts = await api('/api/hosts');
|
||||
renderHosts($('#host-list'), '', () => show('guest-contact'));
|
||||
renderHosts($('#host-list'), '', () => show('guest-contact'), $('#host-select'));
|
||||
}
|
||||
|
||||
$('#change-site').addEventListener('click', chooseSite);
|
||||
@@ -448,7 +482,7 @@ $('[data-next="guest-name"]').addEventListener('click', () => {
|
||||
});
|
||||
|
||||
$('#in-host-search').addEventListener('input', (e) =>
|
||||
renderHosts($('#host-list'), e.target.value, () => show('guest-contact'))
|
||||
renderHosts($('#host-list'), e.target.value, () => show('guest-contact'), $('#host-select'))
|
||||
);
|
||||
|
||||
$('[data-next="guest-contact"]').addEventListener('click', () => {
|
||||
@@ -522,8 +556,11 @@ $('#do-freq-auth').addEventListener('click', async () => {
|
||||
state.hasStoredPhoto = Boolean(person.hasPhoto);
|
||||
$('#freq-greeting').textContent = `Hi ${person.firstName}. Who are you here to see?`;
|
||||
$('#in-freq-host-search').value = '';
|
||||
renderHosts($('#freq-host-list'), '', () =>
|
||||
state.hasStoredPhoto ? submitSignIn() : show('photo')
|
||||
renderHosts(
|
||||
$('#freq-host-list'),
|
||||
'',
|
||||
() => (state.hasStoredPhoto ? submitSignIn() : show('photo')),
|
||||
$('#freq-host-select')
|
||||
);
|
||||
show('freq-host');
|
||||
} catch (err) {
|
||||
@@ -535,8 +572,11 @@ $('#do-freq-auth').addEventListener('click', async () => {
|
||||
});
|
||||
|
||||
$('#in-freq-host-search').addEventListener('input', (e) =>
|
||||
renderHosts($('#freq-host-list'), e.target.value, () =>
|
||||
state.hasStoredPhoto ? submitSignIn() : show('photo')
|
||||
renderHosts(
|
||||
$('#freq-host-list'),
|
||||
e.target.value,
|
||||
() => (state.hasStoredPhoto ? submitSignIn() : show('photo')),
|
||||
$('#freq-host-select')
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user