Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 
 

178 wiersze
8.0 KiB

  1. // account.js
  2. document.addEventListener('DOMContentLoaded', () => {
  3. const toast = document.getElementById('account-toast');
  4. function showToast(msg, isError = false) {
  5. toast.textContent = msg;
  6. toast.classList.toggle('account-toast--error', isError);
  7. toast.classList.add('account-toast--visible');
  8. setTimeout(() => toast.classList.remove('account-toast--visible'), 3000);
  9. }
  10. async function patchJson(url, data) {
  11. const res = await fetch(url, {
  12. method: 'PATCH',
  13. headers: { 'Content-Type': 'application/json' },
  14. body: JSON.stringify(data),
  15. });
  16. const json = await res.json();
  17. if (!res.ok) throw new Error(json.error ?? 'Fehler');
  18. return json;
  19. }
  20. // ── Farbfeld: Picker ↔ Hex-Input synchron + Live-Kontrast ────────────────
  21. const colorPicker = document.getElementById('account-color-picker');
  22. const colorHex = document.getElementById('account-color');
  23. function applyHeaderContrast(hex) {
  24. const r = parseInt(hex.slice(1, 3), 16);
  25. const g = parseInt(hex.slice(3, 5), 16);
  26. const b = parseInt(hex.slice(5, 7), 16);
  27. const brightness = (r * 299 + g * 587 + b * 114) / 1000;
  28. const isLight = brightness > 128;
  29. const root = document.documentElement;
  30. root.style.setProperty('--header-text', isLight ? '#1a2a3a' : '#ffffff');
  31. root.style.setProperty('--header-text-muted', isLight ? 'rgba(26, 42, 58, 0.65)' : 'rgba(255, 255, 255, 0.75)');
  32. root.style.setProperty('--header-overlay', isLight ? 'rgba(0, 0, 0, 0.08)' : 'rgba(255, 255, 255, 0.18)');
  33. }
  34. if (colorPicker && colorHex) {
  35. colorPicker.addEventListener('input', () => {
  36. colorHex.value = colorPicker.value;
  37. applyHeaderContrast(colorPicker.value);
  38. });
  39. colorHex.addEventListener('input', () => {
  40. if (/^#[0-9a-fA-F]{6}$/.test(colorHex.value)) {
  41. colorPicker.value = colorHex.value;
  42. applyHeaderContrast(colorHex.value);
  43. }
  44. });
  45. }
  46. // ── Account-Formular ──────────────────────────────────────────────────────
  47. const btnAccountSave = document.getElementById('btn-account-save');
  48. if (btnAccountSave) {
  49. btnAccountSave.addEventListener('click', async () => {
  50. const payload = {
  51. name: document.getElementById('account-name').value.trim(),
  52. trackingInterval: parseInt(document.getElementById('account-interval').value, 10),
  53. };
  54. if (colorHex) {
  55. const hex = colorHex.value.trim();
  56. if (hex && !/^#[0-9a-fA-F]{6}$/.test(hex)) {
  57. showToast('Ungültiger Hex-Wert. Beispiel: #3a7bbf', true);
  58. return;
  59. }
  60. payload.primaryColor = hex || '';
  61. }
  62. try {
  63. await patchJson('/api/account', payload);
  64. showToast('Gespeichert. Seite wird neu geladen…');
  65. setTimeout(() => window.location.reload(), 1200);
  66. } catch (e) {
  67. showToast(e.message, true);
  68. }
  69. });
  70. }
  71. // ── Besitzer des Accounts ─────────────────────────────────────────────────
  72. const superadminSelect = document.getElementById('superadmin-select');
  73. if (superadminSelect && !superadminSelect.disabled) {
  74. superadminSelect.addEventListener('change', async () => {
  75. const selectedName = superadminSelect.options[superadminSelect.selectedIndex].text;
  76. if (!confirm(`${selectedName} zum neuen Kontoinhaber machen?`)) {
  77. // Auswahl zurücksetzen
  78. superadminSelect.value = superadminSelect.dataset.original;
  79. return;
  80. }
  81. try {
  82. await patchJson('/api/account/superadmin', {
  83. userId: parseInt(superadminSelect.value, 10),
  84. });
  85. showToast('Kontoinhaber geändert. Seite wird neu geladen…');
  86. setTimeout(() => window.location.reload(), 1500);
  87. } catch (e) {
  88. showToast(e.message, true);
  89. superadminSelect.value = superadminSelect.dataset.original;
  90. }
  91. });
  92. // Original-Wert merken für Rollback
  93. superadminSelect.dataset.original = superadminSelect.value;
  94. }
  95. // ── Passwort-Toggle ───────────────────────────────────────────────────────
  96. const btnPwToggle = document.getElementById('btn-pw-toggle');
  97. const pwSection = document.getElementById('pw-section');
  98. if (btnPwToggle && pwSection) {
  99. btnPwToggle.addEventListener('click', (e) => {
  100. e.preventDefault();
  101. const open = !pwSection.hidden;
  102. pwSection.hidden = open;
  103. btnPwToggle.textContent = open ? 'ändern' : 'abbrechen';
  104. });
  105. }
  106. // ── Theme-Picker ──────────────────────────────────────────────────────────
  107. const themePicker = document.getElementById('theme-picker');
  108. if (themePicker) {
  109. themePicker.querySelectorAll('input[name="theme"]').forEach(radio => {
  110. radio.addEventListener('change', async () => {
  111. const theme = radio.value;
  112. try {
  113. await patchJson('/api/account/user', { theme });
  114. // Optionen visuell aktualisieren
  115. themePicker.querySelectorAll('.theme-option').forEach(opt => {
  116. opt.classList.toggle('theme-option--active', opt.dataset.theme === theme);
  117. });
  118. document.body.dataset.theme = theme;
  119. showToast('Darstellung geändert.');
  120. } catch (e) {
  121. showToast(e.message, true);
  122. }
  123. });
  124. });
  125. }
  126. // ── Benutzer-Formular ─────────────────────────────────────────────────────
  127. const btnUserSave = document.getElementById('btn-user-save');
  128. if (btnUserSave) {
  129. btnUserSave.addEventListener('click', async () => {
  130. const data = {
  131. firstName: document.getElementById('user-firstname').value.trim(),
  132. lastName: document.getElementById('user-lastname').value.trim(),
  133. email: document.getElementById('user-email').value.trim(),
  134. };
  135. if (pwSection && !pwSection.hidden) {
  136. const pwNew = document.getElementById('user-pw-new').value;
  137. const pwRepeat = document.getElementById('user-pw-repeat').value;
  138. if (pwNew !== pwRepeat) {
  139. showToast('Die Passwörter stimmen nicht überein.', true);
  140. return;
  141. }
  142. data.currentPassword = document.getElementById('user-pw-current').value;
  143. data.newPassword = pwNew;
  144. }
  145. try {
  146. await patchJson('/api/account/user', data);
  147. showToast('Gespeichert.');
  148. if (pwSection) {
  149. pwSection.hidden = true;
  150. document.getElementById('btn-pw-toggle').textContent = 'ändern';
  151. ['user-pw-current', 'user-pw-new', 'user-pw-repeat'].forEach(id => {
  152. document.getElementById(id).value = '';
  153. });
  154. }
  155. } catch (e) {
  156. showToast(e.message, true);
  157. }
  158. });
  159. }
  160. });