Visitor sign in kiosk: multi-site, badge printing, WWCC expiry warnings, admin accounts with 2FA

This commit is contained in:
2026-09-03 14:43:03 +10:00
parent 8c97c314e0
commit e6731dbdfa
11 changed files with 169 additions and 31 deletions
+20 -3
View File
@@ -175,7 +175,8 @@ async function loadOnsite() {
(v) => `<tr>
<td>${v.hasPhoto ? `<img class="thumb" src="/admin/api/photo/${v.id}" alt="">` : ''}</td>
<td><strong>${esc(v.firstName)} ${esc(v.lastName)}</strong>
${v.visitorType === 'frequent' ? '<span class="pill">Recurring</span>' : ''}</td>
${v.visitorType === 'frequent' ? '<span class="pill">Recurring</span>' : ''}
${v.company ? `<small>${esc(v.company)}</small>` : ''}</td>
${showSite ? `<td>${esc(v.siteName || '—')}</td>` : ''}
<td>${esc(v.hostName)}</td>
<td>${v.checkType === 'NONE' ? '<span class="pill off">None</span>' : `${esc(v.checkType)} ${esc(v.checkNumber || '')}`}</td>
@@ -220,7 +221,8 @@ async function loadLog() {
rows.map(
(v) => `<tr>
${showSite ? `<td>${esc(v.siteName || '—')}</td>` : ''}
<td>${esc(v.firstName)} ${esc(v.lastName)}</td>
<td>${esc(v.firstName)} ${esc(v.lastName)}
${v.company ? `<small>${esc(v.company)}</small>` : ''}</td>
<td>${esc(v.hostName)}</td>
<td>${v.checkType === 'NONE' ? '—' : `${esc(v.checkType)} ${esc(v.checkNumber || '')}`}</td>
<td>${esc(v.phone || v.email || '—')}</td>
@@ -258,6 +260,7 @@ async function loadRecurring() {
rows.map(
(p) => `<tr class="${p.expiry.status === 'expired' ? 'row-bad' : p.expiry.status === 'expiring' ? 'row-warn' : ''}">
<td><strong>${esc(p.firstName)} ${esc(p.lastName)}</strong>
${p.company ? `<small>${esc(p.company)}</small>` : ''}
${p.email ? `<small>${esc(p.email)}</small>` : ''}</td>
<td class="mono">${esc(p.phone)}</td>
<td>${p.siteId ? esc(siteName(p.siteId)) : '<span class="pill off">Any site</span>'}</td>
@@ -470,6 +473,7 @@ function openRecurringModal(person = null) {
`
${field('First name', 'firstName', person?.firstName)}
${field('Last name', 'lastName', person?.lastName)}
${field('Company or organisation (optional)', 'company', person?.company)}
${field('Mobile number (their username)', 'phone', person?.phone, 'tel')}
${field('Email address', 'email', person?.email, 'email')}
<label class="modal-field"><span>Check held</span>
@@ -1155,7 +1159,20 @@ async function loadSystem() {
s.sheets.enabled
? `<dt>On the sheet</dt><dd>${
s.sheets.onSiteCount === null ? 'Not written yet' : `${s.sheets.onSiteCount} on site`
}, last written ${stamp(s.sheets.lastOk)}</dd>`
}, last written ${stamp(s.sheets.lastOk)}</dd>
<dt>Service account</dt>
<dd>${
s.sheets.serviceAccount
? `<code>${esc(s.sheets.serviceAccount)}</code>
<br><span class="hint">The spreadsheet must be shared with this address, with Editor access.</span>`
: '<span class="pill bad">No key file could be read</span>'
}</dd>
<dt>Spreadsheet</dt>
<dd>${
s.sheets.spreadsheetId
? `<a href="https://docs.google.com/spreadsheets/d/${esc(s.sheets.spreadsheetId)}/edit" target="_blank" rel="noopener">open the sheet</a>`
: '<span class="pill warn">SHEETS_SPREADSHEET_ID is not set</span>'
}</dd>`
: ''
}
</dl>
+5
View File
@@ -10,6 +10,7 @@ const state = {
mode: 'guest',
firstName: '',
lastName: '',
company: '',
hostId: null,
hostName: '',
phone: '',
@@ -136,6 +137,7 @@ function resetState() {
mode: 'guest',
firstName: '',
lastName: '',
company: '',
hostId: null,
hostName: '',
phone: '',
@@ -338,6 +340,7 @@ function afterPhoto() {
function buildReview() {
const rows = [
['Name', `${state.firstName} ${state.lastName}`],
...(state.company ? [['From', state.company]] : []),
['Visiting', state.hostName],
['Mobile', state.phone || '—'],
['Email', state.email || '—'],
@@ -369,6 +372,7 @@ async function submitSignIn() {
frequentVisitorId: state.frequentVisitorId,
firstName: state.firstName,
lastName: state.lastName,
company: state.company,
hostId: state.hostId,
phone: state.phone,
email: state.email,
@@ -520,6 +524,7 @@ $('[data-next="guest-name"]').addEventListener('click', () => {
if (!last) return say('Enter your last name.');
state.firstName = first;
state.lastName = last;
state.company = $('#in-company').value.trim();
show('guest-host');
});