${new Date().toLocaleString()}
${backupCodesText}`
+ const w = window.open('', '_blank', 'width=900,height=700')
+ if (!w) return
+ w.document.open()
+ w.document.write(html)
+ w.document.close()
+ w.focus()
+ w.print()
+ }
+
+ const handleAvatarUpload = async (e: React.ChangeEvent{t('settings.mfa.requiredByPolicy')}
+{t('settings.mfa.description')}
+ {demoMode ? ( +{t('settings.mfa.demoBlocked')}
+ ) : ( + <> ++ {user?.mfa_enabled ? t('settings.mfa.enabled') : t('settings.mfa.disabled')} +
+ + {!user?.mfa_enabled && !mfaQr && ( + + )} + + {!user?.mfa_enabled && mfaQr && ( +{t('settings.mfa.scanQr')}
+{mfaSecret}
+ {t('settings.mfa.disableTitle')}
+{t('settings.mfa.disableHint')}
+ setMfaDisablePwd(e.target.value)} + placeholder={t('settings.currentPassword')} + className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm" + /> + setMfaDisableCode(e.target.value.replace(/\D/g, '').slice(0, 8))} + placeholder={t('settings.mfa.codePlaceholder')} + className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm" + /> + +{t('settings.mfa.backupTitle')}
+{t('settings.mfa.backupDescription')}
+{backupCodesText}
+ {t('settings.mfa.backupWarning')}
++ {t('settings.oidcLinked')} {(user as UserWithOidc).oidc_issuer!.replace('https://', '').replace(/\/+$/, '')} +
+ )} ++ {t('settings.deleteBlockedMessage')} +
++ {t('settings.deleteAccountWarning')} +
+
+ {mcpEndpoint}
+
+
+
+ {mcpJsonConfig}
+
+ {t('settings.mcp.clientConfigHint')}
++ {t('settings.mcp.noTokens')} +
+ ) : ( +{token.name}
++ {token.token_prefix}... + {t('settings.mcp.tokenCreatedAt')} {new Date(token.created_at).toLocaleDateString(locale)} + {token.last_used_at && ( + · {t('settings.mcp.tokenUsedAt')} {new Date(token.last_used_at).toLocaleDateString(locale)} + )} +
+{t('settings.mcp.modal.createdWarning')}
+
+ {mcpCreatedToken}
+
+
+ {t('settings.mcp.deleteTokenMessage')}
+{t('settings.mapDefaultHint')}
+Loading…
+ + if (visibleChannels.length === 0) { + return ( ++ {t('settings.notificationPreferences.noChannels')} +
+ ) + } + + return ( +Saving…
} + {matrix.available_channels.webhook && ( +{t('settings.webhookUrl.hint')}
+Loading…
- - // Which channels are both available AND have at least one implemented event - const visibleChannels = (['email', 'webhook', 'inapp'] as const).filter(ch => { - if (!matrix.available_channels[ch]) return false - return matrix.event_types.some(evt => matrix.implemented_combos[evt]?.includes(ch)) - }) - - if (visibleChannels.length === 0) { - return ( -- {t('settings.notificationPreferences.noChannels')} -
- ) - } - - const toggle = async (eventType: string, channel: string) => { - const current = matrix.preferences[eventType]?.[channel] ?? true - const updated = { - ...matrix.preferences, - [eventType]: { ...matrix.preferences[eventType], [channel]: !current }, - } - setMatrix(m => m ? { ...m, preferences: updated } : m) - setSaving(true) - try { - await notificationsApi.updatePreferences(updated) - } catch { - // Revert on failure - setMatrix(m => m ? { ...m, preferences: matrix.preferences } : m) - } finally { - setSaving(false) - } - } - - const saveWebhookUrl = async () => { - setWebhookSaving(true) - try { - await settingsApi.set('webhook_url', webhookUrl) - toast.success(t('settings.webhookUrl.saved')) - } catch { - toast.error(t('common.error')) - } finally { - setWebhookSaving(false) - } - } - - const testWebhookUrl = async () => { - if (!webhookUrl) return - setWebhookTesting(true) - try { - const result = await notificationsApi.testWebhook(webhookUrl) - if (result.success) toast.success(t('settings.webhookUrl.testSuccess')) - else toast.error(result.error || t('settings.webhookUrl.testFailed')) - } catch { - toast.error(t('settings.webhookUrl.testFailed')) - } finally { - setWebhookTesting(false) - } - } - - return ( -Saving…
} - {/* Webhook URL configuration */} - {matrix.available_channels.webhook && ( -{t('settings.webhookUrl.hint')}
-${new Date().toLocaleString()}
${backupCodesText}`
- const w = window.open('', '_blank', 'width=900,height=700')
- if (!w) return
- w.document.open()
- w.document.write(html)
- w.document.close()
- w.focus()
- w.print()
- }
-
- useEffect(() => {
- setMapTileUrl(settings.map_tile_url || '')
- setDefaultLat(settings.default_lat || 48.8566)
- setDefaultLng(settings.default_lng || 2.3522)
- setDefaultZoom(settings.default_zoom || 10)
- setTempUnit(settings.temperature_unit || 'celsius')
- }, [settings])
-
- useEffect(() => {
- setUsername(user?.username || '')
- setEmail(user?.email || '')
- }, [user])
-
- const saveMapSettings = async (): Promise{t('settings.subtitle')}
+{t('settings.subtitle')}
+{t('settings.mapDefaultHint')}
-
- {mcpEndpoint}
-
-
-
- {mcpJsonConfig}
-
- {t('settings.mcp.clientConfigHint')}
-- {t('settings.mcp.noTokens')} -
- ) : ( -{token.name}
-- {token.token_prefix}... - {t('settings.mcp.tokenCreatedAt')} {new Date(token.created_at).toLocaleDateString(locale)} - {token.last_used_at && ( - · {t('settings.mcp.tokenUsedAt')} {new Date(token.last_used_at).toLocaleDateString(locale)} - )} -
-{t('settings.mcp.modal.createdWarning')}
-
- {mcpCreatedToken}
-
-
- {t('settings.mcp.deleteTokenMessage')}
-{t('settings.mfa.requiredByPolicy')}
-{t('settings.mfa.description')}
- {demoMode ? ( -{t('settings.mfa.demoBlocked')}
- ) : ( - <> -- {user?.mfa_enabled ? t('settings.mfa.enabled') : t('settings.mfa.disabled')} -
- - {!user?.mfa_enabled && !mfaQr && ( - - )} - - {!user?.mfa_enabled && mfaQr && ( -{t('settings.mfa.scanQr')}
-{mfaSecret}
- {t('settings.mfa.disableTitle')}
-{t('settings.mfa.disableHint')}
- setMfaDisablePwd(e.target.value)} - placeholder={t('settings.currentPassword')} - className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm" - /> - setMfaDisableCode(e.target.value.replace(/\D/g, '').slice(0, 8))} - placeholder={t('settings.mfa.codePlaceholder')} - className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm" - /> - -{t('settings.mfa.backupTitle')}
-{t('settings.mfa.backupDescription')}
-{backupCodesText}
- {t('settings.mfa.backupWarning')}
-- {t('settings.oidcLinked')} {(user as UserWithOidc).oidc_issuer!.replace('https://', '').replace(/\/+$/, '')} -
- )} -- {t('settings.deleteBlockedMessage')} -
-- {t('settings.deleteAccountWarning')} -
-