25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

163 lines
7.2 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 ────────────────────────────────
  21. const colorPicker = document.getElementById('account-color-picker');
  22. const colorHex = document.getElementById('account-color');
  23. if (colorPicker && colorHex) {
  24. colorPicker.addEventListener('input', () => {
  25. colorHex.value = colorPicker.value;
  26. });
  27. colorHex.addEventListener('input', () => {
  28. if (/^#[0-9a-fA-F]{6}$/.test(colorHex.value)) {
  29. colorPicker.value = colorHex.value;
  30. }
  31. });
  32. }
  33. // ── Account-Formular ──────────────────────────────────────────────────────
  34. const btnAccountSave = document.getElementById('btn-account-save');
  35. if (btnAccountSave) {
  36. btnAccountSave.addEventListener('click', async () => {
  37. const payload = {
  38. name: document.getElementById('account-name').value.trim(),
  39. trackingInterval: parseInt(document.getElementById('account-interval').value, 10),
  40. };
  41. if (colorHex) {
  42. const hex = colorHex.value.trim();
  43. if (hex && !/^#[0-9a-fA-F]{6}$/.test(hex)) {
  44. showToast('Ungültiger Hex-Wert. Beispiel: #3a7bbf', true);
  45. return;
  46. }
  47. payload.primaryColor = hex || '';
  48. }
  49. try {
  50. await patchJson('/api/account', payload);
  51. showToast('Gespeichert. Seite wird neu geladen…');
  52. setTimeout(() => window.location.reload(), 1200);
  53. } catch (e) {
  54. showToast(e.message, true);
  55. }
  56. });
  57. }
  58. // ── Besitzer des Accounts ─────────────────────────────────────────────────
  59. const superadminSelect = document.getElementById('superadmin-select');
  60. if (superadminSelect && !superadminSelect.disabled) {
  61. superadminSelect.addEventListener('change', async () => {
  62. const selectedName = superadminSelect.options[superadminSelect.selectedIndex].text;
  63. if (!confirm(`${selectedName} zum neuen Kontoinhaber machen?`)) {
  64. // Auswahl zurücksetzen
  65. superadminSelect.value = superadminSelect.dataset.original;
  66. return;
  67. }
  68. try {
  69. await patchJson('/api/account/superadmin', {
  70. userId: parseInt(superadminSelect.value, 10),
  71. });
  72. showToast('Kontoinhaber geändert. Seite wird neu geladen…');
  73. setTimeout(() => window.location.reload(), 1500);
  74. } catch (e) {
  75. showToast(e.message, true);
  76. superadminSelect.value = superadminSelect.dataset.original;
  77. }
  78. });
  79. // Original-Wert merken für Rollback
  80. superadminSelect.dataset.original = superadminSelect.value;
  81. }
  82. // ── Passwort-Toggle ───────────────────────────────────────────────────────
  83. const btnPwToggle = document.getElementById('btn-pw-toggle');
  84. const pwSection = document.getElementById('pw-section');
  85. if (btnPwToggle && pwSection) {
  86. btnPwToggle.addEventListener('click', (e) => {
  87. e.preventDefault();
  88. const open = !pwSection.hidden;
  89. pwSection.hidden = open;
  90. btnPwToggle.textContent = open ? 'ändern' : 'abbrechen';
  91. });
  92. }
  93. // ── Theme-Picker ──────────────────────────────────────────────────────────
  94. const themePicker = document.getElementById('theme-picker');
  95. if (themePicker) {
  96. themePicker.querySelectorAll('input[name="theme"]').forEach(radio => {
  97. radio.addEventListener('change', async () => {
  98. const theme = radio.value;
  99. try {
  100. await patchJson('/api/account/user', { theme });
  101. // Optionen visuell aktualisieren
  102. themePicker.querySelectorAll('.theme-option').forEach(opt => {
  103. opt.classList.toggle('theme-option--active', opt.dataset.theme === theme);
  104. });
  105. document.body.dataset.theme = theme;
  106. showToast('Darstellung geändert.');
  107. } catch (e) {
  108. showToast(e.message, true);
  109. }
  110. });
  111. });
  112. }
  113. // ── Benutzer-Formular ─────────────────────────────────────────────────────
  114. const btnUserSave = document.getElementById('btn-user-save');
  115. if (btnUserSave) {
  116. btnUserSave.addEventListener('click', async () => {
  117. const data = {
  118. firstName: document.getElementById('user-firstname').value.trim(),
  119. lastName: document.getElementById('user-lastname').value.trim(),
  120. email: document.getElementById('user-email').value.trim(),
  121. };
  122. if (pwSection && !pwSection.hidden) {
  123. const pwNew = document.getElementById('user-pw-new').value;
  124. const pwRepeat = document.getElementById('user-pw-repeat').value;
  125. if (pwNew !== pwRepeat) {
  126. showToast('Die Passwörter stimmen nicht überein.', true);
  127. return;
  128. }
  129. data.currentPassword = document.getElementById('user-pw-current').value;
  130. data.newPassword = pwNew;
  131. }
  132. try {
  133. await patchJson('/api/account/user', data);
  134. showToast('Gespeichert.');
  135. if (pwSection) {
  136. pwSection.hidden = true;
  137. document.getElementById('btn-pw-toggle').textContent = 'ändern';
  138. ['user-pw-current', 'user-pw-new', 'user-pw-repeat'].forEach(id => {
  139. document.getElementById(id).value = '';
  140. });
  141. }
  142. } catch (e) {
  143. showToast(e.message, true);
  144. }
  145. });
  146. }
  147. });