You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

219 rivejä
11 KiB

  1. {# templates/account/index.html.twig #}
  2. {% extends 'base.html.twig' %}
  3. {% block title %}
  4. {% if tab == 'account' %}Account{% else %}Mein Benutzer{% endif %}
  5. {% endblock %}
  6. {% block body %}
  7. <div class="account-page">
  8. <div class="account-header">
  9. <h1 class="account-header__title">
  10. {% if tab == 'account' %}Account{% else %}Mein Benutzer{% endif %}
  11. </h1>
  12. {% if isAdmin %}
  13. <nav class="account-tabs">
  14. <a href="{{ path('account_index', {tab: 'account'}) }}"
  15. class="account-tab{% if tab == 'account' %} account-tab--active{% endif %}">
  16. Account
  17. </a>
  18. <a href="{{ path('account_index', {tab: 'user'}) }}"
  19. class="account-tab{% if tab == 'user' %} account-tab--active{% endif %}">
  20. Mein Benutzer
  21. </a>
  22. </nav>
  23. {% endif %}
  24. </div>
  25. <div class="account-content">
  26. <div class="account-card">
  27. {# ── Account-Tab (nur Admin) ────────────────────────────────────────── #}
  28. {% if tab == 'account' and isAdmin %}
  29. <div class="account-form__grid" id="account-form">
  30. <label class="account-form__label" for="account-name">Firmenname</label>
  31. <div class="account-form__field">
  32. <input type="text" id="account-name" class="input"
  33. value="{{ account.name|e('html_attr') }}" />
  34. <span class="account-form__hint">
  35. Subdomain: <strong>{{ account.slug }}.{{ app.request.host|split('.')|slice(1)|join('.') }}</strong> — ändert sich nicht.
  36. </span>
  37. </div>
  38. <label class="account-form__label" for="account-interval">Zeitintervall</label>
  39. <div class="account-form__field">
  40. <select id="account-interval" class="select">
  41. {% for value, label in intervalOptions %}
  42. <option value="{{ value }}"{% if account.trackingInterval == value %} selected{% endif %}>
  43. {{ label }}
  44. </option>
  45. {% endfor %}
  46. </select>
  47. <span class="account-form__hint">Auf welche Einheit werden erfasste Zeiten aufgerundet.</span>
  48. </div>
  49. <div class="account-form__actions">
  50. <button type="button" class="btn btn-primary" id="btn-account-save">Sichern</button>
  51. <a href="{{ path('account_index', {tab: 'account'}) }}" class="btn btn-secondary">Abbrechen</a>
  52. </div>
  53. </div>
  54. {# ── Benutzer-Tab ──────────────────────────────────────────────────── #}
  55. {% else %}
  56. <div class="account-form__grid" id="user-form">
  57. <label class="account-form__label" for="user-firstname">Vorname</label>
  58. <div class="account-form__field">
  59. <input type="text" id="user-firstname" class="input"
  60. value="{{ user.firstName|e('html_attr') }}" />
  61. </div>
  62. <label class="account-form__label" for="user-lastname">Nachname</label>
  63. <div class="account-form__field">
  64. <input type="text" id="user-lastname" class="input"
  65. value="{{ user.lastName|e('html_attr') }}" />
  66. </div>
  67. <label class="account-form__label" for="user-email">E-Mail</label>
  68. <div class="account-form__field">
  69. <input type="email" id="user-email" class="input"
  70. value="{{ user.email|e('html_attr') }}" />
  71. </div>
  72. <label class="account-form__label">Passwort</label>
  73. <div class="account-form__field">
  74. <a href="#" class="account-form__link" id="btn-pw-toggle">ändern</a>
  75. </div>
  76. <div class="account-form__pw-section" id="pw-section" hidden>
  77. <label class="account-form__label" for="user-pw-current">Aktuelles Passwort</label>
  78. <div class="account-form__field">
  79. <input type="password" id="user-pw-current" class="input" autocomplete="current-password" />
  80. </div>
  81. <label class="account-form__label" for="user-pw-new">Neues Passwort</label>
  82. <div class="account-form__field">
  83. <input type="password" id="user-pw-new" class="input" autocomplete="new-password" minlength="8" />
  84. </div>
  85. <label class="account-form__label" for="user-pw-repeat">Wiederholen</label>
  86. <div class="account-form__field">
  87. <input type="password" id="user-pw-repeat" class="input" autocomplete="new-password" />
  88. </div>
  89. </div>
  90. <div class="account-form__actions">
  91. <button type="button" class="btn btn-primary" id="btn-user-save">Sichern</button>
  92. <a href="{{ path('account_index', {tab: 'user'}) }}" class="btn btn-secondary">Abbrechen</a>
  93. </div>
  94. </div>
  95. {% endif %}
  96. </div>
  97. </div>
  98. </div>
  99. <div class="account-toast" id="account-toast"></div>
  100. <script>
  101. (function () {
  102. const toast = document.getElementById('account-toast');
  103. function showToast(msg, isError = false) {
  104. toast.textContent = msg;
  105. toast.classList.toggle('account-toast--error', isError);
  106. toast.classList.add('account-toast--visible');
  107. setTimeout(() => toast.classList.remove('account-toast--visible'), 3000);
  108. }
  109. async function patchJson(url, data) {
  110. const res = await fetch(url, {
  111. method: 'PATCH',
  112. headers: { 'Content-Type': 'application/json' },
  113. body: JSON.stringify(data),
  114. });
  115. const json = await res.json();
  116. if (!res.ok) throw new Error(json.error ?? 'Fehler');
  117. return json;
  118. }
  119. // ── Account-Formular ──────────────────────────────────────────────────────
  120. const btnAccountSave = document.getElementById('btn-account-save');
  121. if (btnAccountSave) {
  122. btnAccountSave.addEventListener('click', async () => {
  123. try {
  124. await patchJson('/api/account', {
  125. name: document.getElementById('account-name').value.trim(),
  126. trackingInterval: parseInt(document.getElementById('account-interval').value, 10),
  127. });
  128. showToast('Gespeichert.');
  129. } catch (e) {
  130. showToast(e.message, true);
  131. }
  132. });
  133. }
  134. // ── Passwort-Toggle ───────────────────────────────────────────────────────
  135. const btnPwToggle = document.getElementById('btn-pw-toggle');
  136. const pwSection = document.getElementById('pw-section');
  137. if (btnPwToggle && pwSection) {
  138. btnPwToggle.addEventListener('click', (e) => {
  139. e.preventDefault();
  140. const open = !pwSection.hidden;
  141. pwSection.hidden = open;
  142. btnPwToggle.textContent = open ? 'ändern' : 'abbrechen';
  143. });
  144. }
  145. // ── Benutzer-Formular ────────────────────────────────────────────────────
  146. const btnUserSave = document.getElementById('btn-user-save');
  147. if (btnUserSave) {
  148. btnUserSave.addEventListener('click', async () => {
  149. const data = {
  150. firstName: document.getElementById('user-firstname').value.trim(),
  151. lastName: document.getElementById('user-lastname').value.trim(),
  152. email: document.getElementById('user-email').value.trim(),
  153. };
  154. if (pwSection && !pwSection.hidden) {
  155. const pwNew = document.getElementById('user-pw-new').value;
  156. const pwRepeat = document.getElementById('user-pw-repeat').value;
  157. if (pwNew !== pwRepeat) {
  158. showToast('Die Passwörter stimmen nicht überein.', true);
  159. return;
  160. }
  161. data.currentPassword = document.getElementById('user-pw-current').value;
  162. data.newPassword = pwNew;
  163. }
  164. try {
  165. await patchJson('/api/account/user', data);
  166. showToast('Gespeichert.');
  167. if (pwSection) {
  168. pwSection.hidden = true;
  169. document.getElementById('btn-pw-toggle').textContent = 'ändern';
  170. ['user-pw-current', 'user-pw-new', 'user-pw-repeat'].forEach(id => {
  171. document.getElementById(id).value = '';
  172. });
  173. }
  174. } catch (e) {
  175. showToast(e.message, true);
  176. }
  177. });
  178. }
  179. })();
  180. </script>
  181. {% endblock %}