const form = document.getElementById('device-settings-form'); const saveButton = document.getElementById('device-settings-save'); const message = document.getElementById('device-settings-message'); const configVersion = document.getElementById('config-version'); const avatarPreview = document.getElementById('avatar-preview'); const previewCaption = document.getElementById('preview-caption'); const fields = { owner_name: document.getElementById('owner_name'), car_model: document.getElementById('car_model'), device_name: document.getElementById('device_name'), avatar_path: document.getElementById('avatar_path'), }; let initialState = null; function showMessage(text, kind) { message.className = `alert alert-${kind}`; message.textContent = text; message.hidden = false; } function hideMessage() { message.hidden = true; } function currentState() { return { owner_name: fields.owner_name.value.trim(), car_model: fields.car_model.value.trim(), device_name: fields.device_name.value.trim(), avatar_path: fields.avatar_path.value.trim(), }; } function avatarUrl(path) { if (!path) return '/static/img/yaris.png'; if (/^https?:\/\//.test(path)) return path; return `/static/${path.replace(/^\/+/, '')}`; } function updatePreview() { const state = currentState(); avatarPreview.src = avatarUrl(state.avatar_path); previewCaption.textContent = state.owner_name && state.car_model ? `${state.owner_name} • ${state.car_model}` : 'Preview updates as you type.'; } function updateDirtyState() { if (!initialState) { saveButton.disabled = true; return; } saveButton.disabled = JSON.stringify(currentState()) === JSON.stringify(initialState); } function populate(config) { fields.owner_name.value = config.owner_name || ''; fields.car_model.value = config.car_model || ''; fields.device_name.value = config.device_name || ''; fields.avatar_path.value = config.avatar_path || ''; configVersion.textContent = config.config_version ?? '—'; initialState = currentState(); updatePreview(); updateDirtyState(); } async function loadConfig() { try { const response = await fetch('/api/v1/portal/device-config'); const payload = await response.json(); if (!response.ok) { throw new Error(payload.error || 'Could not load device config.'); } populate(payload); } catch (error) { showMessage(error.message, 'danger'); } } async function saveConfig(event) { event.preventDefault(); hideMessage(); saveButton.disabled = true; try { const response = await fetch('/api/device-config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(currentState()), }); const payload = await response.json(); if (!response.ok) { const detail = payload.fields ? Object.values(payload.fields).join(' ') : (payload.error || 'Could not save device config.'); throw new Error(detail); } populate(payload); showMessage('Device settings saved. The Pi will pick them up on its next sync.', 'success'); } catch (error) { showMessage(error.message, 'danger'); updateDirtyState(); } } for (const field of Object.values(fields)) { field.addEventListener('input', () => { hideMessage(); updatePreview(); updateDirtyState(); }); } avatarPreview.addEventListener('error', () => { previewCaption.textContent = 'Preview image not found on the server static path.'; }); form.addEventListener('submit', saveConfig); loadConfig();