Add This Device tab to portal admin settings

This commit is contained in:
jtricerolph 2026-07-13 12:48:06 +00:00
parent af810f14c7
commit 63b6240c22

View file

@ -47,7 +47,7 @@ interface AppRow {
}
export function AdminSettings({ user }: { user: User }) {
const [tab, setTab] = useState<'integrations' | 'rooms' | 'apps'>('integrations')
const [tab, setTab] = useState<'integrations' | 'rooms' | 'apps' | 'device'>('integrations')
const [integrations, setIntegrations] = useState<Integration[]>([])
const [loading, setLoading] = useState(true)
@ -68,7 +68,7 @@ export function AdminSettings({ user }: { user: User }) {
</p>
<div style={{ display: 'flex', gap: '0.25rem', marginBottom: '1.75rem', borderBottom: '1px solid var(--card-border)', paddingBottom: '0' }}>
{(['integrations', 'rooms', 'apps'] as const).map(t => (
{(['integrations', 'rooms', 'apps', 'device'] as const).map(t => (
<button key={t} onClick={() => setTab(t)} style={{
background: 'none', border: 'none', padding: '0.5rem 1rem',
fontSize: '0.85rem', fontWeight: 600, cursor: 'pointer',
@ -76,7 +76,7 @@ export function AdminSettings({ user }: { user: User }) {
borderBottom: tab === t ? '2px solid var(--navy)' : '2px solid transparent',
marginBottom: '-1px',
}}>
{t === 'integrations' ? 'Integrations' : t === 'rooms' ? 'Rooms & Sites' : 'App Settings'}
{t === 'integrations' ? 'Integrations' : t === 'rooms' ? 'Rooms & Sites' : t === 'apps' ? 'App Settings' : 'This Device'}
</button>
))}
</div>
@ -95,6 +95,7 @@ export function AdminSettings({ user }: { user: User }) {
{tab === 'rooms' && <RoomsTab />}
{tab === 'apps' && <AppsTab />}
{tab === 'device' && <DeviceTab />}
</main>
</div>
)
@ -554,6 +555,77 @@ function AppsTab() {
)
}
const TIMEOUT_OPTIONS = [
{ label: 'Off', value: 0 },
{ label: '5 min', value: 5 },
{ label: '10 min', value: 10 },
{ label: '15 min', value: 15 },
{ label: '30 min', value: 30 },
]
function getInactivityMins(): number {
const c = document.cookie.split(';').map(s => s.trim()).find(s => s.startsWith('hnf_inactivity_mins='))
return c ? parseInt(c.split('=')[1]) || 0 : 0
}
function setInactivityMins(mins: number) {
document.cookie = mins > 0
? `hnf_inactivity_mins=${mins}; max-age=31536000; path=/; SameSite=Strict`
: 'hnf_inactivity_mins=; max-age=0; path=/; SameSite=Strict'
}
function DeviceTab() {
const [inactivityMins, setInactivityMinsState] = useState(getInactivityMins)
const [saved, setSaved] = useState(false)
const isPwa = window.matchMedia('(display-mode: standalone)').matches
return (
<div style={{ maxWidth: '480px' }}>
<p style={{ fontSize: '0.83rem', color: 'var(--text-mid)', marginBottom: '1.5rem' }}>
{isPwa
? 'This device is running as an installed PWA — inactivity timeout is automatically disabled. Sessions last until the 30-day sign-in limit.'
: 'Set an inactivity timeout for this device. Users will be signed out automatically after the chosen period of no activity. Use on shared or front-desk computers. Ignored when running as an installed PWA.'}
</p>
<p style={{ fontSize: '0.72rem', fontWeight: 700, color: 'var(--text-mid)',
textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: '0.75rem' }}>
Inactivity timeout
</p>
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap', marginBottom: '1.25rem' }}>
{TIMEOUT_OPTIONS.map(opt => (
<button
key={opt.value}
disabled={isPwa}
onClick={() => {
setInactivityMins(opt.value)
setInactivityMinsState(opt.value)
setSaved(true)
setTimeout(() => setSaved(false), 2000)
}}
style={{
padding: '0.4rem 1rem', borderRadius: '6px', fontSize: '0.85rem', fontWeight: 600,
cursor: isPwa ? 'not-allowed' : 'pointer', opacity: isPwa ? 0.4 : 1,
border: `1px solid ${inactivityMins === opt.value ? 'var(--navy)' : 'var(--card-border)'}`,
background: inactivityMins === opt.value ? 'var(--navy)' : 'var(--body-bg)',
color: inactivityMins === opt.value ? '#fff' : 'var(--text-dark)',
transition: 'all 0.15s',
}}
>
{opt.label}
</button>
))}
</div>
{saved && (
<p style={{ fontSize: '0.82rem', color: '#16a34a' }}>
Saved takes effect immediately
</p>
)}
</div>
)
}
const inputStyle: React.CSSProperties = {
background: 'var(--body-bg)', border: '1px solid var(--card-border)',
borderRadius: '6px', padding: '0.45rem 0.75rem', fontSize: '0.85rem',