diff --git a/backend/locale/en/LC_MESSAGES/django.mo b/backend/locale/en/LC_MESSAGES/django.mo index 5f4707a..46ff1ed 100644 Binary files a/backend/locale/en/LC_MESSAGES/django.mo and b/backend/locale/en/LC_MESSAGES/django.mo differ diff --git a/backend/locale/en/LC_MESSAGES/django.po b/backend/locale/en/LC_MESSAGES/django.po index 46fae62..705a0dc 100644 --- a/backend/locale/en/LC_MESSAGES/django.po +++ b/backend/locale/en/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: tubco-portal\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-03-25 23:12+0000\n" +"POT-Creation-Date: 2026-03-25 23:29+0000\n" "PO-Revision-Date: 2026-03-24 00:00+0000\n" "Language: en\n" "MIME-Version: 1.0\n" @@ -350,11 +350,18 @@ msgstr "Sign in" msgid "Bitte melden Sie sich mit Ihrem Benutzerkonto an." msgstr "Please sign in with your user account." -#: workflows/templates/registration/login.html:25 -msgid "Anmeldung fehlgeschlagen. Bitte Zugangsdaten prüfen." -msgstr "Login failed. Please check your credentials." - #: workflows/templates/registration/login.html:29 +#, fuzzy +#| msgid "Fehlgeschlagen" +msgid "Anmeldung fehlgeschlagen" +msgstr "Failed" + +#: workflows/templates/registration/login.html:30 +msgid "" +"Benutzername oder Passwort sind nicht korrekt. Bitte versuchen Sie es erneut." +msgstr "" + +#: workflows/templates/registration/login.html:35 msgid "Anmelden" msgstr "Sign in" @@ -453,6 +460,19 @@ msgstr "" msgid "Noch keine Audit-Einträge vorhanden." msgstr "No requests available yet." +#: workflows/templates/workflows/base_shell.html:24 +msgid "Bitte bestätigen" +msgstr "" + +#: workflows/templates/workflows/base_shell.html:28 +#: workflows/templates/workflows/welcome_emails.html:138 +msgid "Abbrechen" +msgstr "Cancel" + +#: workflows/templates/workflows/base_shell.html:29 +msgid "Bestätigen" +msgstr "" + #: workflows/templates/workflows/form_builder.html:4 #: workflows/templates/workflows/form_builder.html:14 #: workflows/templates/workflows/home.html:134 @@ -1795,6 +1815,12 @@ msgstr "" msgid "Bis" msgstr "" +#: workflows/templates/workflows/requests_dashboard.html:176 +#, fuzzy +#| msgid "Ausgewählte Welcome-Einträge wirklich löschen?" +msgid "Ausgewählte Einträge wirklich löschen?" +msgstr "Delete the selected welcome entries?" + #: workflows/templates/workflows/requests_dashboard.html:179 #: workflows/templates/workflows/welcome_emails.html:78 msgid "ausgewählt" @@ -1849,10 +1875,20 @@ msgstr "Not relevant" msgid "Timeline" msgstr "" +#: workflows/templates/workflows/requests_dashboard.html:276 +msgid "Eintrag erneut verarbeiten?" +msgstr "" + #: workflows/templates/workflows/requests_dashboard.html:278 msgid "Erneut versuchen" msgstr "" +#: workflows/templates/workflows/requests_dashboard.html:281 +#, fuzzy +#| msgid "Option wirklich löschen?" +msgid "Eintrag wirklich löschen?" +msgstr "Delete this option?" + #: workflows/templates/workflows/requests_dashboard.html:290 msgid "Noch keine Vorgänge vorhanden." msgstr "No requests available yet." @@ -1939,10 +1975,6 @@ msgstr "Sent at" msgid "Fortsetzen" msgstr "Resume" -#: workflows/templates/workflows/welcome_emails.html:138 -msgid "Abbrechen" -msgstr "Cancel" - #: workflows/templates/workflows/welcome_emails.html:145 msgid "Keine geplanten Welcome E-Mails vorhanden." msgstr "No scheduled welcome emails available." @@ -2215,6 +2247,9 @@ msgstr "Introduction was saved as completed." msgid "Einweisung wurde als Entwurf gespeichert." msgstr "Introduction was saved as draft." +#~ msgid "Anmeldung fehlgeschlagen. Bitte Zugangsdaten prüfen." +#~ msgstr "Login failed. Please check your credentials." + #, fuzzy #~| msgid "Nextcloud speichern" #~ msgid "Nextcloud deaktivieren" diff --git a/backend/workflows/static/workflows/css/admin_tools.css b/backend/workflows/static/workflows/css/admin_tools.css index 8d6bf8c..715150c 100644 --- a/backend/workflows/static/workflows/css/admin_tools.css +++ b/backend/workflows/static/workflows/css/admin_tools.css @@ -5,10 +5,10 @@ h1 { margin: 12px 0 6px; color: #000078; } .flash, .msg { border-radius: 10px; padding: 10px 12px; margin: 0 0 12px; border: 1px solid #d6e1ef; background: #f8fbff; color: #1f3a5f; } .flash.error, .msg.error { border-color: #fecaca; background: #fff1f2; color: #991b1b; } .flash.success { border-color: #bfe6c9; background: #edf9f1; color: #116634; } -.card { border: 1px solid #d8e3f0; border-radius: 12px; background: #fbfdff; padding: 12px; margin-bottom: 14px; } +.card { border: 1px solid #d8e3f0; border-radius: 12px; background: #fbfdff; padding: 12px; margin-bottom: 14px; transition: border-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 220ms cubic-bezier(0.2, 0.8, 0.2, 1), transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1); } .grid { display: grid; grid-template-columns: repeat(2, minmax(240px, 1fr)); gap: 10px; } label { display: block; margin-bottom: 4px; font-size: 12px; color: #334155; font-weight: 700; } -input, select, textarea { width: 100%; box-sizing: border-box; border: 1px solid #cbd5e1; border-radius: 8px; padding: 8px 9px; background: #fff; } +input, select, textarea { width: 100%; box-sizing: border-box; border: 1px solid #cbd5e1; border-radius: 8px; padding: 8px 9px; background: #fff; transition: border-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 180ms cubic-bezier(0.2, 0.8, 0.2, 1), background-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1); } textarea { min-height: 120px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; } .actions { margin-top: 10px; display: flex; gap: 8px; flex-wrap: wrap; } .toggle-row { margin-top: 10px; display: inline-flex; align-items: center; gap: 10px; padding: 8px 10px 8px 12px; border: 1px solid #d8e3f0; border-radius: 999px; background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(246,249,255,0.94)); box-shadow: inset 0 1px 0 rgba(255,255,255,0.9), 0 6px 14px rgba(16, 32, 57, 0.05); } @@ -24,8 +24,9 @@ textarea { min-height: 120px; font-family: ui-monospace, SFMono-Regular, Menlo, .hint { margin-top: 6px; color: #64748b; font-size: 12px; } .toolbar { display: flex; justify-content: space-between; align-items: center; gap: 10px; margin-bottom: 10px; flex-wrap: wrap; } .switch { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; } -.switch .tab { border: 1px solid #c9d6e7; border-radius: 999px; padding: 8px 14px; text-decoration: none; color: #1f2f49; font-weight: 700; background: #f6f9ff; } +.switch .tab { border: 1px solid #c9d6e7; border-radius: 999px; padding: 8px 14px; text-decoration: none; color: #1f2f49; font-weight: 700; background: #f6f9ff; transition: background-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), border-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), transform 180ms cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 180ms cubic-bezier(0.2, 0.8, 0.2, 1); } .switch .tab.active { background: #000078; color: #fff; border-color: #000078; } +.switch .tab:hover { transform: translateY(-1px); box-shadow: 0 8px 16px rgba(16, 32, 57, 0.06); } .check-row { margin-top: 8px; display: flex; gap: 12px; flex-wrap: wrap; } .check-row label { display: inline-flex; align-items: center; gap: 6px; margin: 0; font-size: 13px; } .check-row input[type="checkbox"] { width: auto; } diff --git a/backend/workflows/static/workflows/css/app_chrome.css b/backend/workflows/static/workflows/css/app_chrome.css index fe96482..2ccb206 100644 --- a/backend/workflows/static/workflows/css/app_chrome.css +++ b/backend/workflows/static/workflows/css/app_chrome.css @@ -4,6 +4,10 @@ --app-brand-blue: #000078; --app-panel: rgba(255, 255, 255, 0.9); --app-shadow: 0 22px 48px rgba(18, 34, 56, 0.14); + --motion-fast: 160ms; + --motion-base: 200ms; + --motion-slow: 260ms; + --motion-ease: cubic-bezier(0.2, 0.8, 0.2, 1); } .app-header { @@ -19,6 +23,10 @@ border-radius: 28px; background: linear-gradient(180deg, rgba(255,255,255,0.95), rgba(248,251,255,0.84)); box-shadow: var(--app-shadow); + transition: + box-shadow var(--motion-base) var(--motion-ease), + border-color var(--motion-base) var(--motion-ease), + background-color var(--motion-base) var(--motion-ease); } .app-header-in-shell { @@ -38,6 +46,11 @@ min-width: 0; align-items: center; text-decoration: none; + transition: transform var(--motion-base) var(--motion-ease), opacity var(--motion-base) var(--motion-ease); +} + +.app-brand:hover { + transform: translateY(-1px); } .app-brand-logo { @@ -45,6 +58,12 @@ max-width: 100%; height: auto; display: block; + transition: transform var(--motion-base) var(--motion-ease), filter var(--motion-base) var(--motion-ease); +} + +.app-brand:hover .app-brand-logo { + transform: translateY(-1px); + filter: saturate(1.02); } .app-header-actions { @@ -71,6 +90,12 @@ font-size: 12px; font-weight: 700; cursor: pointer; + transition: + background-color var(--motion-fast) var(--motion-ease), + border-color var(--motion-fast) var(--motion-ease), + color var(--motion-fast) var(--motion-ease), + transform var(--motion-fast) var(--motion-ease), + box-shadow var(--motion-fast) var(--motion-ease); } .app-lang-btn.active { @@ -79,6 +104,15 @@ color: #fff; } +.app-lang-btn:hover { + transform: translateY(-1px); +} + +.app-lang-btn:focus-visible { + outline: none; + box-shadow: 0 0 0 4px rgba(0, 0, 120, 0.10); +} + .shell, .wrap, .top-wrap { @@ -99,3 +133,83 @@ justify-content: flex-start; } } + +.confirm-modal[hidden] { + display: none; +} + +.confirm-modal { + position: fixed; + inset: 0; + z-index: 1200; +} + +.confirm-backdrop { + position: absolute; + inset: 0; + background: rgba(16, 32, 57, 0.42); + backdrop-filter: blur(4px); + animation: confirmFadeIn var(--motion-base) var(--motion-ease); +} + +.confirm-dialog { + position: relative; + width: min(480px, calc(100% - 32px)); + margin: min(18vh, 120px) auto 0; + padding: 18px; + border: 1px solid rgba(217, 227, 238, 0.94); + border-radius: 22px; + background: linear-gradient(180deg, rgba(255,255,255,0.99), rgba(247,250,255,0.96)); + box-shadow: 0 24px 56px rgba(18, 34, 56, 0.20); + animation: confirmPopIn var(--motion-slow) var(--motion-ease); +} + +.confirm-dialog-head h2 { + margin: 0; + font-size: 20px; + color: #18345f; + letter-spacing: -0.02em; +} + +.confirm-message { + margin: 10px 0 0; + color: #52647a; + font-size: 14px; + line-height: 1.55; +} + +.confirm-actions { + margin-top: 18px; + display: flex; + justify-content: flex-end; + gap: 8px; + flex-wrap: wrap; +} + +body.confirm-open { + overflow: hidden; +} + +@keyframes confirmFadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes confirmPopIn { + from { + opacity: 0; + transform: translateY(10px) scale(0.98); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +@media (prefers-reduced-motion: reduce) { + :root { + --motion-fast: 1ms; + --motion-base: 1ms; + --motion-slow: 1ms; + } +} diff --git a/backend/workflows/static/workflows/css/buttons.css b/backend/workflows/static/workflows/css/buttons.css index 28ea76c..be716fb 100644 --- a/backend/workflows/static/workflows/css/buttons.css +++ b/backend/workflows/static/workflows/css/buttons.css @@ -20,7 +20,13 @@ font-size: 14px; line-height: 1.2; cursor: pointer; - transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease, transform 0.08s ease; + transition: + background-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), + border-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), + color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), + box-shadow 180ms cubic-bezier(0.2, 0.8, 0.2, 1), + transform 180ms cubic-bezier(0.2, 0.8, 0.2, 1), + filter 180ms cubic-bezier(0.2, 0.8, 0.2, 1); } .btn:focus-visible { @@ -34,7 +40,7 @@ } .btn:active:not(:disabled) { - transform: translateY(1px); + transform: translateY(0); } .btn-primary { @@ -44,7 +50,9 @@ } .btn-primary:hover:not(:disabled) { - filter: brightness(0.95); + filter: brightness(0.97); + transform: translateY(-1px); + box-shadow: 0 10px 20px rgba(0, 0, 120, 0.12); } .btn-secondary { @@ -55,4 +63,6 @@ .btn-secondary:hover:not(:disabled) { background: #f8fafc; + transform: translateY(-1px); + box-shadow: 0 8px 18px rgba(18, 34, 56, 0.08); } diff --git a/backend/workflows/static/workflows/css/home.css b/backend/workflows/static/workflows/css/home.css index fe7efa9..7b66da9 100644 --- a/backend/workflows/static/workflows/css/home.css +++ b/backend/workflows/static/workflows/css/home.css @@ -95,6 +95,10 @@ background: rgba(255,255,255,0.92); border-radius: 22px; box-shadow: inset 0 1px 0 rgba(255,255,255,0.92); + transition: + transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1), + box-shadow 220ms cubic-bezier(0.2, 0.8, 0.2, 1), + border-color 220ms cubic-bezier(0.2, 0.8, 0.2, 1); } .hero-card { @@ -169,6 +173,12 @@ min-height: 38px; display: inline-flex; align-items: center; + transition: + background-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), + border-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), + color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), + box-shadow 180ms cubic-bezier(0.2, 0.8, 0.2, 1), + transform 180ms cubic-bezier(0.2, 0.8, 0.2, 1); } .status-pill-neutral { @@ -385,6 +395,10 @@ flex-direction: column; justify-content: space-between; box-shadow: inset 0 1px 0 rgba(255,255,255,0.92); + transition: + transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1), + box-shadow 220ms cubic-bezier(0.2, 0.8, 0.2, 1), + border-color 220ms cubic-bezier(0.2, 0.8, 0.2, 1); } .app-card::before { @@ -395,6 +409,17 @@ height: 118px; border-radius: 50%; background: rgba(31, 79, 214, 0.08); + transition: transform 260ms cubic-bezier(0.2, 0.8, 0.2, 1); + } + + .app-card:hover { + transform: translateY(-3px); + border-color: rgba(196, 208, 225, 1); + box-shadow: 0 14px 28px rgba(16, 32, 57, 0.10), inset 0 1px 0 rgba(255,255,255,0.92); + } + + .app-card:hover::before { + transform: scale(1.06); } .app-card.primary { @@ -468,6 +493,11 @@ font-weight: 800; letter-spacing: 0.03em; text-transform: uppercase; + transition: + background-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), + border-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), + color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), + transform 180ms cubic-bezier(0.2, 0.8, 0.2, 1); } .card-actions { @@ -490,6 +520,10 @@ padding: 14px; background: linear-gradient(180deg, rgba(255,255,255,0.99), rgba(247,250,255,0.96)); box-shadow: inset 0 1px 0 rgba(255,255,255,0.92); + transition: + transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1), + box-shadow 220ms cubic-bezier(0.2, 0.8, 0.2, 1), + border-color 220ms cubic-bezier(0.2, 0.8, 0.2, 1); } .admin-card::before { @@ -500,6 +534,17 @@ height: 84px; border-radius: 50%; background: rgba(31, 79, 214, 0.06); + transition: transform 260ms cubic-bezier(0.2, 0.8, 0.2, 1); + } + + .admin-card:hover { + transform: translateY(-2px); + border-color: rgba(196, 208, 225, 1); + box-shadow: 0 12px 24px rgba(16, 32, 57, 0.08), inset 0 1px 0 rgba(255,255,255,0.92); + } + + .admin-card:hover::before { + transform: scale(1.05); } .admin-card h3 { diff --git a/backend/workflows/static/workflows/css/login.css b/backend/workflows/static/workflows/css/login.css index 54cb0fd..5de49cb 100644 --- a/backend/workflows/static/workflows/css/login.css +++ b/backend/workflows/static/workflows/css/login.css @@ -51,6 +51,12 @@ body { margin-bottom: 12px; } +.field.has-error input { + border-color: #e3a3a3; + background: #fffafa; + box-shadow: 0 0 0 4px rgba(185, 28, 28, 0.06); +} + .field label { display: block; font-weight: 700; @@ -67,19 +73,44 @@ body { min-height: 44px; font: inherit; background: #fff; + transition: border-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 180ms cubic-bezier(0.2, 0.8, 0.2, 1), background-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1); +} + +.field input:focus { + outline: none; + border-color: rgba(0, 0, 120, 0.3); + box-shadow: 0 0 0 4px rgba(0, 0, 120, 0.08); } .btn { width: 100%; } -.errorlist { - color: #b91c1c; - margin: 0 0 10px; - padding: 10px 12px; - border-radius: 10px; - border: 1px solid #f0c8c8; - background: #fff3f3; +.login-alert { + margin: 0 0 12px; + padding: 12px 14px; + border-radius: 12px; + border: 1px solid #d6e1ef; + background: #f8fbff; + display: grid; + gap: 4px; + font-size: 14px; +} + +.login-alert strong { + font-size: 14px; + line-height: 1.2; +} + +.login-alert span { + color: #5f6f85; + line-height: 1.45; +} + +.login-alert-error { + color: #991b1b; + border-color: #f0c8c8; + background: linear-gradient(180deg, #fff7f7, #fff1f1); } @media (max-width: 760px) { diff --git a/backend/workflows/static/workflows/css/requests_dashboard.css b/backend/workflows/static/workflows/css/requests_dashboard.css index 76adda7..9af2e0e 100644 --- a/backend/workflows/static/workflows/css/requests_dashboard.css +++ b/backend/workflows/static/workflows/css/requests_dashboard.css @@ -101,6 +101,10 @@ background: var(--panel); border-radius: 22px; box-shadow: inset 0 1px 0 rgba(255,255,255,0.92); + transition: + transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1), + box-shadow 220ms cubic-bezier(0.2, 0.8, 0.2, 1), + border-color 220ms cubic-bezier(0.2, 0.8, 0.2, 1); } .hero-card { @@ -256,6 +260,17 @@ height: 110px; border-radius: 50%; background: rgba(31, 79, 214, 0.08); + transition: transform 260ms cubic-bezier(0.2, 0.8, 0.2, 1); + } + + .stat-card:hover { + transform: translateY(-2px); + border-color: rgba(196, 208, 225, 1); + box-shadow: 0 14px 30px rgba(18, 33, 56, 0.08), inset 0 1px 0 rgba(255,255,255,0.92); + } + + .stat-card:hover::before { + transform: scale(1.05); } .stat-card.red::before { background: rgba(163, 32, 32, 0.08); } @@ -461,6 +476,10 @@ color: var(--ink); background: rgba(255,255,255,0.98); box-shadow: inset 0 1px 0 rgba(255,255,255,0.98); + transition: + border-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), + box-shadow 180ms cubic-bezier(0.2, 0.8, 0.2, 1), + background-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1); } .search-box input:focus, @@ -626,6 +645,11 @@ padding: 15px 14px; vertical-align: middle; box-shadow: 0 8px 22px rgba(26, 51, 89, 0.035); + transition: + background-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), + border-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), + box-shadow 220ms cubic-bezier(0.2, 0.8, 0.2, 1), + transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1); } tbody td:first-child { @@ -686,6 +710,12 @@ border: 1px solid rgba(217, 227, 238, 0.96); background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(248,251,255,0.96)); box-shadow: inset 0 1px 0 rgba(255,255,255,0.94); + transition: + background-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), + border-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), + color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), + box-shadow 180ms cubic-bezier(0.2, 0.8, 0.2, 1), + transform 180ms cubic-bezier(0.2, 0.8, 0.2, 1); } .kind-pill { @@ -725,6 +755,7 @@ text-decoration: none; font-weight: 600; word-break: break-word; + transition: color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), opacity 180ms cubic-bezier(0.2, 0.8, 0.2, 1); } .mail-link:hover { text-decoration: underline; } @@ -750,6 +781,7 @@ .doc-link:hover { background: linear-gradient(180deg, rgba(248,251,255,0.99), rgba(238,244,255,0.98)); border-color: rgba(0, 0, 120, 0.18); + transform: translateY(-1px); } .actions-cell { white-space: normal; } @@ -781,6 +813,16 @@ font-weight: 800; color: #18345f; background: rgba(255,255,255,0.65); + transition: + background-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), + border-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), + box-shadow 180ms cubic-bezier(0.2, 0.8, 0.2, 1), + transform 180ms cubic-bezier(0.2, 0.8, 0.2, 1); + } + + .intro-toggle:hover { + transform: translateY(-1px); + box-shadow: 0 8px 18px rgba(18, 33, 56, 0.06); } .intro-toggle::after { diff --git a/backend/workflows/static/workflows/js/confirm_dialog.js b/backend/workflows/static/workflows/js/confirm_dialog.js new file mode 100644 index 0000000..3041f80 --- /dev/null +++ b/backend/workflows/static/workflows/js/confirm_dialog.js @@ -0,0 +1,92 @@ +(function () { + const modal = document.getElementById('app-confirm-modal'); + if (!modal) return; + + const messageNode = document.getElementById('app-confirm-message'); + const acceptBtn = document.getElementById('app-confirm-accept'); + const cancelBtn = document.getElementById('app-confirm-cancel'); + const closeNodes = modal.querySelectorAll('[data-confirm-close]'); + let resolver = null; + let lastActive = null; + + function close(result) { + modal.hidden = true; + modal.setAttribute('aria-hidden', 'true'); + document.body.classList.remove('confirm-open'); + if (resolver) { + resolver(result); + resolver = null; + } + if (lastActive && typeof lastActive.focus === 'function') { + lastActive.focus(); + } + } + + function open(message) { + lastActive = document.activeElement; + messageNode.textContent = message || ''; + modal.hidden = false; + modal.setAttribute('aria-hidden', 'false'); + document.body.classList.add('confirm-open'); + acceptBtn.focus(); + return new Promise(function (resolve) { + resolver = resolve; + }); + } + + window.AppConfirm = { open: open }; + + acceptBtn.addEventListener('click', function () { close(true); }); + cancelBtn.addEventListener('click', function () { close(false); }); + closeNodes.forEach(function (node) { + node.addEventListener('click', function () { close(false); }); + }); + + document.addEventListener('keydown', function (event) { + if (modal.hidden) return; + if (event.key === 'Escape') { + event.preventDefault(); + close(false); + } + }); + + document.addEventListener('submit', function (event) { + const form = event.target; + const message = form.dataset.confirm; + if (!message || form.dataset.confirmBypass === '1') return; + event.preventDefault(); + open(message).then(function (confirmed) { + if (!confirmed) return; + form.dataset.confirmBypass = '1'; + if (typeof form.requestSubmit === 'function') { + form.requestSubmit(); + } else { + form.submit(); + } + window.setTimeout(function () { + delete form.dataset.confirmBypass; + }, 0); + }); + }, true); + + document.addEventListener('click', function (event) { + const button = event.target.closest('button[data-confirm], input[type="submit"][data-confirm]'); + if (!button || button.dataset.confirmBypass === '1') return; + const form = button.form; + if (!form) return; + event.preventDefault(); + event.stopPropagation(); + open(button.dataset.confirm).then(function (confirmed) { + if (!confirmed) return; + button.dataset.confirmBypass = '1'; + if (typeof form.requestSubmit === 'function') { + form.requestSubmit(button); + } else { + button.click(); + } + window.setTimeout(function () { + delete button.dataset.confirmBypass; + }, 0); + }); + }, true); +})(); diff --git a/backend/workflows/static/workflows/js/welcome_emails.js b/backend/workflows/static/workflows/js/welcome_emails.js index 456d734..3bf90ae 100644 --- a/backend/workflows/static/workflows/js/welcome_emails.js +++ b/backend/workflows/static/workflows/js/welcome_emails.js @@ -29,24 +29,35 @@ }); rowChecks.forEach((c) => c.addEventListener('change', syncState)); - bulkForm.addEventListener('submit', syncState); - - window.confirmBulkAction = function () { + bulkForm.addEventListener('submit', function (event) { syncState(); const count = currentSelected().length; if (!count) { - alert(bulkForm.dataset.alertEmpty || 'Bitte mindestens einen Welcome-Eintrag auswählen.'); - return false; + event.preventDefault(); + const emptyMessage = bulkForm.dataset.alertEmpty || 'Bitte mindestens einen Welcome-Eintrag auswählen.'; + if (window.AppConfirm) { + window.AppConfirm.open(emptyMessage); + } else { + window.alert(emptyMessage); + } + return; } + if (bulkForm.dataset.confirmBypass === '1') return; + event.preventDefault(); const action = bulkAction.value; + let message = bulkForm.dataset.confirmSend || 'Ausgewählte Welcome-Einträge sofort senden?'; if (action === 'delete') { - return confirm(bulkForm.dataset.confirmDelete || 'Ausgewählte Welcome-Einträge wirklich löschen?'); + message = bulkForm.dataset.confirmDelete || 'Ausgewählte Welcome-Einträge wirklich löschen?'; + } else if (action === 'pause') { + message = bulkForm.dataset.confirmPause || 'Ausgewählte Welcome-Einträge pausieren?'; } - if (action === 'pause') { - return confirm(bulkForm.dataset.confirmPause || 'Ausgewählte Welcome-Einträge pausieren?'); - } - return confirm(bulkForm.dataset.confirmSend || 'Ausgewählte Welcome-Einträge sofort senden?'); - }; + window.AppConfirm.open(message).then(function (confirmed) { + if (!confirmed) return; + bulkForm.dataset.confirmBypass = '1'; + bulkForm.requestSubmit(); + window.setTimeout(function () { delete bulkForm.dataset.confirmBypass; }, 0); + }); + }); syncState(); })(); diff --git a/backend/workflows/templates/registration/login.html b/backend/workflows/templates/registration/login.html index 4ba811d..121a34e 100644 --- a/backend/workflows/templates/registration/login.html +++ b/backend/workflows/templates/registration/login.html @@ -21,11 +21,17 @@
{% csrf_token %} - {% if form.errors %} -
{% trans "Anmeldung fehlgeschlagen. Bitte Zugangsdaten prüfen." %}
+ {% if next %} + {% endif %} -
{{ form.username.label_tag }}{{ form.username }}
-
{{ form.password.label_tag }}{{ form.password }}
+ {% if form.errors %} + + {% endif %} +
{{ form.username.label_tag }}{{ form.username }}
+
{{ form.password.label_tag }}{{ form.password }}
diff --git a/backend/workflows/templates/workflows/base_shell.html b/backend/workflows/templates/workflows/base_shell.html index 96cf3b7..67c7159 100644 --- a/backend/workflows/templates/workflows/base_shell.html +++ b/backend/workflows/templates/workflows/base_shell.html @@ -17,6 +17,20 @@ {% block shell_header %}{% endblock %} {% block shell_body %}{% endblock %} + + {% block extra_scripts %}{% endblock %} diff --git a/backend/workflows/templates/workflows/form_builder.html b/backend/workflows/templates/workflows/form_builder.html index 94c54d3..7ef692f 100644 --- a/backend/workflows/templates/workflows/form_builder.html +++ b/backend/workflows/templates/workflows/form_builder.html @@ -108,7 +108,7 @@ - + {% empty %} @@ -164,4 +164,3 @@ {% endblock %} - diff --git a/backend/workflows/templates/workflows/intro_builder.html b/backend/workflows/templates/workflows/intro_builder.html index a7116ce..2f7acad 100644 --- a/backend/workflows/templates/workflows/intro_builder.html +++ b/backend/workflows/templates/workflows/intro_builder.html @@ -103,7 +103,7 @@ - + {% empty %} diff --git a/backend/workflows/templates/workflows/onboarding_intro_session.html b/backend/workflows/templates/workflows/onboarding_intro_session.html index b2388af..a36bcd3 100644 --- a/backend/workflows/templates/workflows/onboarding_intro_session.html +++ b/backend/workflows/templates/workflows/onboarding_intro_session.html @@ -81,7 +81,7 @@
- +
@@ -102,4 +102,3 @@ {% endblock %} - diff --git a/backend/workflows/templates/workflows/requests_dashboard.html b/backend/workflows/templates/workflows/requests_dashboard.html index cd2e62e..8f306e3 100644 --- a/backend/workflows/templates/workflows/requests_dashboard.html +++ b/backend/workflows/templates/workflows/requests_dashboard.html @@ -173,7 +173,7 @@ {% if request.user.is_staff %}
-
+ {% csrf_token %}
0 {% trans "ausgewählt" %} @@ -273,12 +273,12 @@ {% trans "Timeline" %} {% if row.status_key == 'failed' %} - + {% csrf_token %} {% endif %} -
+ {% csrf_token %}
diff --git a/backend/workflows/templates/workflows/welcome_emails.html b/backend/workflows/templates/workflows/welcome_emails.html index d3ff3c7..94eb5e9 100644 --- a/backend/workflows/templates/workflows/welcome_emails.html +++ b/backend/workflows/templates/workflows/welcome_emails.html @@ -62,7 +62,7 @@
-
+ {% csrf_token %}