Frontend: structured error handling, state fixes, accessibility, i18n
- Enhance fetchWithAuth with auto-401, retry w/ exponential backoff, timeout - Remove ~40 manual 401 checks across 10 feature files - Fix state: brightness cache setter, manual edit flag resets, static import - Add ARIA: role=dialog/tablist, aria-modal, aria-labelledby, aria-selected - Add focus trapping in Modal base class, aria-expanded on hint toggles - Fix WCAG AA color contrast with --primary-text-color variable - Add i18n pluralization (CLDR rules for en/ru), getCurrentLocale export - Replace hardcoded strings in dashboard.js and profiles.js - Add data-i18n-aria-label support, 20 new keys in en.json and ru.json Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,11 +3,11 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
_deviceBrightnessCache,
|
||||
_deviceBrightnessCache, updateDeviceBrightness,
|
||||
} from '../core/state.js';
|
||||
import { API_BASE, getHeaders, escapeHtml, isSerialDevice, handle401Error } from '../core/api.js';
|
||||
import { API_BASE, getHeaders, fetchWithAuth, escapeHtml, isSerialDevice } from '../core/api.js';
|
||||
import { t } from '../core/i18n.js';
|
||||
import { showToast } from '../core/ui.js';
|
||||
import { showToast, showConfirm } from '../core/ui.js';
|
||||
import { Modal } from '../core/modal.js';
|
||||
|
||||
class DeviceSettingsModal extends Modal {
|
||||
@@ -110,18 +110,15 @@ export function createDeviceCard(device) {
|
||||
|
||||
export async function toggleDevicePower(deviceId) {
|
||||
try {
|
||||
const getResp = await fetch(`${API_BASE}/devices/${deviceId}/power`, { headers: getHeaders() });
|
||||
if (getResp.status === 401) { handle401Error(); return; }
|
||||
const getResp = await fetchWithAuth(`/devices/${deviceId}/power`);
|
||||
if (!getResp.ok) { showToast('Failed to get power state', 'error'); return; }
|
||||
const current = await getResp.json();
|
||||
const newState = !current.on;
|
||||
|
||||
const setResp = await fetch(`${API_BASE}/devices/${deviceId}/power`, {
|
||||
const setResp = await fetchWithAuth(`/devices/${deviceId}/power`, {
|
||||
method: 'PUT',
|
||||
headers: { ...getHeaders(), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ on: newState })
|
||||
});
|
||||
if (setResp.status === 401) { handle401Error(); return; }
|
||||
if (setResp.ok) {
|
||||
showToast(t(newState ? 'device.power.on_success' : 'device.power.off_success'), 'success');
|
||||
} else {
|
||||
@@ -129,6 +126,7 @@ export async function toggleDevicePower(deviceId) {
|
||||
showToast(error.detail || 'Failed', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.isAuth) return;
|
||||
showToast('Failed to toggle power', 'error');
|
||||
}
|
||||
}
|
||||
@@ -142,11 +140,9 @@ export async function removeDevice(deviceId) {
|
||||
if (!confirmed) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/devices/${deviceId}`, {
|
||||
const response = await fetchWithAuth(`/devices/${deviceId}`, {
|
||||
method: 'DELETE',
|
||||
headers: getHeaders()
|
||||
});
|
||||
if (response.status === 401) { handle401Error(); return; }
|
||||
if (response.ok) {
|
||||
showToast('Device removed', 'success');
|
||||
window.loadDevices();
|
||||
@@ -155,6 +151,7 @@ export async function removeDevice(deviceId) {
|
||||
showToast(`Failed to remove: ${error.detail}`, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.isAuth) return;
|
||||
console.error('Failed to remove device:', error);
|
||||
showToast('Failed to remove device', 'error');
|
||||
}
|
||||
@@ -162,8 +159,7 @@ export async function removeDevice(deviceId) {
|
||||
|
||||
export async function showSettings(deviceId) {
|
||||
try {
|
||||
const deviceResponse = await fetch(`${API_BASE}/devices/${deviceId}`, { headers: getHeaders() });
|
||||
if (deviceResponse.status === 401) { handle401Error(); return; }
|
||||
const deviceResponse = await fetchWithAuth(`/devices/${deviceId}`);
|
||||
if (!deviceResponse.ok) { showToast('Failed to load device settings', 'error'); return; }
|
||||
|
||||
const device = await deviceResponse.json();
|
||||
@@ -222,6 +218,7 @@ export async function showSettings(deviceId) {
|
||||
}, 100);
|
||||
|
||||
} catch (error) {
|
||||
if (error.isAuth) return;
|
||||
console.error('Failed to load device settings:', error);
|
||||
showToast('Failed to load device settings', 'error');
|
||||
}
|
||||
@@ -251,14 +248,11 @@ export async function saveDeviceSettings() {
|
||||
const baudVal = document.getElementById('settings-baud-rate').value;
|
||||
if (baudVal) body.baud_rate = parseInt(baudVal, 10);
|
||||
}
|
||||
const deviceResponse = await fetch(`${API_BASE}/devices/${deviceId}`, {
|
||||
const deviceResponse = await fetchWithAuth(`/devices/${deviceId}`, {
|
||||
method: 'PUT',
|
||||
headers: getHeaders(),
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
|
||||
if (deviceResponse.status === 401) { handle401Error(); return; }
|
||||
|
||||
if (!deviceResponse.ok) {
|
||||
const errorData = await deviceResponse.json();
|
||||
settingsModal.showError(`Failed to update device: ${errorData.detail}`);
|
||||
@@ -269,6 +263,7 @@ export async function saveDeviceSettings() {
|
||||
settingsModal.forceClose();
|
||||
window.loadDevices();
|
||||
} catch (err) {
|
||||
if (err.isAuth) return;
|
||||
console.error('Failed to save device settings:', err);
|
||||
settingsModal.showError('Failed to save settings');
|
||||
}
|
||||
@@ -282,7 +277,7 @@ export function updateBrightnessLabel(deviceId, value) {
|
||||
|
||||
export async function saveCardBrightness(deviceId, value) {
|
||||
const bri = parseInt(value);
|
||||
_deviceBrightnessCache[deviceId] = bri;
|
||||
updateDeviceBrightness(deviceId, bri);
|
||||
try {
|
||||
await fetch(`${API_BASE}/devices/${deviceId}/brightness`, {
|
||||
method: 'PUT',
|
||||
@@ -302,7 +297,7 @@ export async function fetchDeviceBrightness(deviceId) {
|
||||
});
|
||||
if (!resp.ok) return;
|
||||
const data = await resp.json();
|
||||
_deviceBrightnessCache[deviceId] = data.brightness;
|
||||
updateDeviceBrightness(deviceId, data.brightness);
|
||||
const slider = document.querySelector(`[data-device-brightness="${deviceId}"]`);
|
||||
if (slider) {
|
||||
slider.value = data.brightness;
|
||||
|
||||
Reference in New Issue
Block a user