diff --git a/AI_HANDOVER.md b/AI_HANDOVER.md
index dd180ce..bdf466b 100644
--- a/AI_HANDOVER.md
+++ b/AI_HANDOVER.md
@@ -467,7 +467,14 @@ Wichtige Settings:
### Gestaltung
- Alle Auth-Styles liegen zentral in `public/css/auth-module.css`, global eingebunden über `contao/config/config.php` (`$GLOBALS['TL_CSS']`).
-- Das Passwort-vergessen-Modul (`.mod_lostPassword.block`) verwendet bewusst dieselbe zentrierte Karte wie das Login-Modul: gleiche Breite, Radien, Farbverläufe, Schriften und Button-Optik.
+- Das Kartenlayout hängt **nicht** an einzelnen Contao-Modultypen, sondern an der Klasse `.survey-auth-module`. Die Template-Overrides setzen sie selbst auf den Modul-Wrapper:
+
+ ```twig
+ {% set wrapperAttributes = attrs().addClass('survey-auth-module') %}
+ ```
+
+ `block_searchable.html.twig` merged das in die Wrapper-Attribute. Ein weiteres Auth-Modul braucht damit nur diese eine Zeile und keine neuen CSS-Regeln. Aktuell nutzen sie `mod_lostPassword`, `mod_changePassword` und die Meldungsansichten über `mod_message`.
+- Passwort vergessen und Passwort ändern verwenden bewusst dieselbe zentrierte Karte wie das Login-Modul: gleiche Breite, Radien, Farbverläufe, Schriften und Button-Optik.
- Der Link `Passwort vergessen?` im Login ist rechtsbündig und im Corporate Design (Blau, Hover Orange, Chevron `›`). Auf Mobil wird er zentriert.
- Fehler- und Bestätigungsmeldungen (auch das Contao-Template `mod_message`, das der Kern bei ungültigem/abgelaufenem Token einsetzt) werden als farbige Hinweisboxen dargestellt.
@@ -481,6 +488,19 @@ Wichtige Settings:
- mit Token: Texte aus `survey.auth.set_password.*`
- Die Texte liegen in `translations/messages.de.yaml` bzw. `messages.en.yaml` unter `survey.auth`, sind also nicht im Template hartkodiert.
+### Passwort-Änderung erzwingen
+
+Die Erweiterung `terminal42/contao-password-validation` bringt das Feld `tl_member.pwChange` und die Startseiten-Einstellung `tl_page.pwChangePage` mit. Ist `pwChange` gesetzt, leitet sie das Mitglied nach dem Login auf die dort hinterlegte Seite.
+
+Aufbau in dieser Installation:
+
+- Startseite (tl_page ID 1) → `pwChangePage` = Seite `Passwort ändern` (ID 10, Alias `passwort-aendern`)
+- Dort liegt das Contao-Kernmodul `changePassword` (tl_module ID 11)
+- Dessen Weiterleitungsseite (`jumpTo`) ist `Passwort wurde geändert` (ID 11, Alias `passwort-wurde-geaendert`)
+
+- Template-Override: `contao/templates/frontend/mod_changePassword.html.twig`, gleicher Aufbau wie das Passwort-vergessen-Modul. Texte unter `survey.auth.change_password.*`.
+- Der Bestätigungstext auf Seite 11 liegt als normales Contao-Textelement mit der Klasse `survey-auth-note` und ist damit redaktionell pflegbar. Der Button führt bewusst **zur Umfragenliste** und nicht zur Anmeldung, weil das Mitglied an dieser Stelle bereits eingeloggt ist.
+
### E-Mail-Versand
- Der Versand läuft über einen benannten Mailer-Transport `ksb`, konfiguriert in der Projektinstallation (`config/config.yaml`), nicht im Bundle:
@@ -583,6 +603,7 @@ Mitglieder-Login / Passwort vergessen:
- `public/css/auth-module.css`
- `contao/templates/frontend/mod_lostPassword.html.twig`
+- `contao/templates/frontend/mod_changePassword.html.twig`
- `contao/templates/frontend/mod_message.html.twig`
- `src/EventListener/CorporateMailListener.php`
- `src/Resources/views/email/corporate.html.twig`
diff --git a/contao/templates/frontend/mod_changePassword.html.twig b/contao/templates/frontend/mod_changePassword.html.twig
new file mode 100644
index 0000000..a6b209f
--- /dev/null
+++ b/contao/templates/frontend/mod_changePassword.html.twig
@@ -0,0 +1,34 @@
+{% extends '@Contao/block_unsearchable.html.twig' %}
+{% trans_default_domain 'messages' %}
+
+{# Der Modul-Wrapper bekommt die gemeinsame Auth-Klasse, an der das Kartenlayout haengt. #}
+{% set wrapperAttributes = attrs().addClass('survey-auth-module') %}
+
+{% block content %}
+
+
+
+
+
{{ 'survey.auth.change_password.headline'|trans }}
+
{{ 'survey.auth.change_password.intro'|trans }}
+
{{ 'survey.auth.change_password.hint'|trans }}
+
+
+ {% if message|default %}
+
{{ message }}
+ {% endif %}
+
+
+
+
+
+{% endblock %}
diff --git a/contao/templates/frontend/mod_lostPassword.html.twig b/contao/templates/frontend/mod_lostPassword.html.twig
index 77bc851..470bd57 100644
--- a/contao/templates/frontend/mod_lostPassword.html.twig
+++ b/contao/templates/frontend/mod_lostPassword.html.twig
@@ -1,6 +1,9 @@
{% extends '@Contao/block_unsearchable.html.twig' %}
{% trans_default_domain 'messages' %}
+{# Der Modul-Wrapper bekommt die gemeinsame Auth-Klasse, an der das Kartenlayout haengt. #}
+{% set wrapperAttributes = attrs().addClass('survey-auth-module') %}
+
{# Schritt 2: Der Nutzer kommt ueber den Link aus der E-Mail und vergibt ein neues Passwort. #}
{% set surveyAuthScope = (app.request.query.get('token')|default('')) starts with 'pw-' ? 'set_password' : 'lost_password' %}
diff --git a/contao/templates/frontend/mod_message.html.twig b/contao/templates/frontend/mod_message.html.twig
index 7c5f5b2..b224771 100644
--- a/contao/templates/frontend/mod_message.html.twig
+++ b/contao/templates/frontend/mod_message.html.twig
@@ -9,6 +9,9 @@
Sackgasse landet.
#}
{% set surveyIsAuthMessage = 'mod_lostPassword' in (class|default('')) %}
+{% if surveyIsAuthMessage %}
+ {% set wrapperAttributes = attrs().addClass('survey-auth-module') %}
+{% endif %}
{% block content %}
diff --git a/public/css/auth-module.css b/public/css/auth-module.css
index a80f584..f1f2499 100644
--- a/public/css/auth-module.css
+++ b/public/css/auth-module.css
@@ -268,11 +268,15 @@ nav.mod_customnav.block:has(a[href*="/abmelden"]) .level_1 > li > a:focus {
/* ------------------------------------------------------------------
- Passwort-vergessen-Modul (Contao "lostPassword")
- Gleiches Kartenlayout wie das Login-Modul, mittig zentriert.
+ Auth-Module im gleichen Kartenlayout wie das Login-Modul.
+
+ Die Klasse .survey-auth-module setzen die Template-Overrides selbst auf den
+ Modul-Wrapper (Contao "lostPassword", "changePassword" und die Meldungs-
+ ansichten via mod_message). So haengen die Kartenregeln nicht an einzelnen
+ Contao-Modultypen und ein weiteres Auth-Modul braucht nur die Klasse.
------------------------------------------------------------------ */
-.mod_lostPassword.block {
+.survey-auth-module {
max-width: min(100%, 36rem);
margin: clamp(1.5rem, 4vw, 3rem) auto;
padding: clamp(1.5rem, 2.6vw, 2.4rem);
@@ -288,19 +292,19 @@ nav.mod_customnav.block:has(a[href*="/abmelden"]) .level_1 > li > a:focus {
color: var(--survey-auth-ink);
}
-.mod_lostPassword.block .survey-auth-card {
+.survey-auth-module .survey-auth-card {
display: grid;
gap: 1.35rem;
}
-.mod_lostPassword.block .survey-auth-intro {
+.survey-auth-module .survey-auth-intro {
display: grid;
gap: 0.55rem;
padding-bottom: 1.1rem;
border-bottom: 1px solid rgba(162, 168, 180, 0.28);
}
-.mod_lostPassword.block .survey-auth-intro-headline {
+.survey-auth-module .survey-auth-intro-headline {
margin: 0;
font-family: "Blogger Sans", sans-serif;
font-size: clamp(1.35rem, 2.4vw, 1.7rem);
@@ -309,14 +313,14 @@ nav.mod_customnav.block:has(a[href*="/abmelden"]) .level_1 > li > a:focus {
color: var(--survey-auth-blue);
}
-.mod_lostPassword.block .survey-auth-intro-text {
+.survey-auth-module .survey-auth-intro-text {
margin: 0;
font-size: 1.02rem;
line-height: 1.5;
color: var(--survey-auth-ink);
}
-.mod_lostPassword.block .survey-auth-hint {
+.survey-auth-module .survey-auth-hint {
margin: 0;
padding-left: 0.85rem;
border-left: 3px solid var(--survey-auth-green);
@@ -325,23 +329,23 @@ nav.mod_customnav.block:has(a[href*="/abmelden"]) .level_1 > li > a:focus {
color: var(--survey-auth-muted);
}
-.mod_lostPassword.block form,
-.mod_lostPassword.block .formbody {
+.survey-auth-module form,
+.survey-auth-module .formbody {
margin: 0;
}
-.mod_lostPassword.block .formbody {
+.survey-auth-module .formbody {
display: grid;
gap: 1rem;
}
-.mod_lostPassword.block .widget {
+.survey-auth-module .widget {
display: grid;
gap: 0.45rem;
margin: 0;
}
-.mod_lostPassword.block label {
+.survey-auth-module label {
font-family: "Blogger Sans", sans-serif;
font-size: 1rem;
font-weight: 500;
@@ -349,12 +353,12 @@ nav.mod_customnav.block:has(a[href*="/abmelden"]) .level_1 > li > a:focus {
letter-spacing: 0.01em;
}
-.mod_lostPassword.block label .mandatory {
+.survey-auth-module label .mandatory {
margin-left: 0.15rem;
color: var(--survey-auth-orange);
}
-.mod_lostPassword.block .invisible {
+.survey-auth-module .invisible {
position: absolute;
width: 1px;
height: 1px;
@@ -367,12 +371,12 @@ nav.mod_customnav.block:has(a[href*="/abmelden"]) .level_1 > li > a:focus {
white-space: nowrap;
}
-.mod_lostPassword.block input.text,
-.mod_lostPassword.block input.password,
-.mod_lostPassword.block input.captcha,
-.mod_lostPassword.block input[type="text"],
-.mod_lostPassword.block input[type="email"],
-.mod_lostPassword.block input[type="password"] {
+.survey-auth-module input.text,
+.survey-auth-module input.password,
+.survey-auth-module input.captcha,
+.survey-auth-module input[type="text"],
+.survey-auth-module input[type="email"],
+.survey-auth-module input[type="password"] {
width: 100%;
min-height: 3.2rem;
padding: 0.8rem 0.95rem;
@@ -386,12 +390,12 @@ nav.mod_customnav.block:has(a[href*="/abmelden"]) .level_1 > li > a:focus {
transition: border-color 0.18s ease, box-shadow 0.18s ease, background-color 0.18s ease;
}
-.mod_lostPassword.block input.text:focus,
-.mod_lostPassword.block input.password:focus,
-.mod_lostPassword.block input.captcha:focus,
-.mod_lostPassword.block input[type="text"]:focus,
-.mod_lostPassword.block input[type="email"]:focus,
-.mod_lostPassword.block input[type="password"]:focus {
+.survey-auth-module input.text:focus,
+.survey-auth-module input.password:focus,
+.survey-auth-module input.captcha:focus,
+.survey-auth-module input[type="text"]:focus,
+.survey-auth-module input[type="email"]:focus,
+.survey-auth-module input[type="password"]:focus {
outline: none;
border-color: rgba(0, 100, 173, 0.72);
box-shadow: 0 0 0 4px rgba(0, 100, 173, 0.12);
@@ -399,26 +403,26 @@ nav.mod_customnav.block:has(a[href*="/abmelden"]) .level_1 > li > a:focus {
}
/* Sicherheitsfrage: Rechenaufgabe als Hinweistext unter dem Feld */
-.mod_lostPassword.block .widget-captcha .captcha_text {
+.survey-auth-module .widget-captcha .captcha_text {
order: 3;
font-size: 0.9rem;
color: var(--survey-auth-muted);
}
-.mod_lostPassword.block .widget-captcha input.captcha {
+.survey-auth-module .widget-captcha input.captcha {
max-width: 9rem;
}
-/* Zweiter Schritt: Passwortbestaetigung liegt als eigenes Feld direkt darunter */
-.mod_lostPassword.block .widget-password + .widget-password {
+/* Passwortbestaetigung liegt als eigenes Feld direkt darunter */
+.survey-auth-module .widget-password + .widget-password {
margin-top: -0.15rem;
}
-.mod_lostPassword.block .widget-submit {
+.survey-auth-module .widget-submit {
margin-top: 0.3rem;
}
-.mod_lostPassword.block button.submit {
+.survey-auth-module button.submit {
display: inline-flex;
justify-content: center;
align-items: center;
@@ -437,8 +441,8 @@ nav.mod_customnav.block:has(a[href*="/abmelden"]) .level_1 > li > a:focus {
transition: transform 0.18s ease, box-shadow 0.18s ease, filter 0.18s ease;
}
-.mod_lostPassword.block button.submit:hover,
-.mod_lostPassword.block button.submit:focus {
+.survey-auth-module button.submit:hover,
+.survey-auth-module button.submit:focus {
transform: translateY(-1px);
box-shadow: 0 18px 34px rgba(0, 100, 173, 0.28);
filter: saturate(1.05);
@@ -446,9 +450,9 @@ nav.mod_customnav.block:has(a[href*="/abmelden"]) .level_1 > li > a:focus {
}
/* Fehler- und Bestaetigungsmeldungen (auch das Contao-Template mod_message) */
-.mod_lostPassword.block p.error,
-.mod_lostPassword.block p.confirm,
-.mod_lostPassword.block p.message {
+.survey-auth-module p.error,
+.survey-auth-module p.confirm,
+.survey-auth-module p.message {
margin: 0;
padding: 0.85rem 1rem;
border-radius: 1rem;
@@ -457,24 +461,24 @@ nav.mod_customnav.block:has(a[href*="/abmelden"]) .level_1 > li > a:focus {
line-height: 1.45;
}
-.mod_lostPassword.block p.error {
+.survey-auth-module p.error {
border-color: rgba(236, 124, 50, 0.4);
background: rgba(236, 124, 50, 0.12);
color: #a4501a;
}
-.mod_lostPassword.block p.confirm {
+.survey-auth-module p.confirm {
border-color: rgba(0, 174, 151, 0.4);
background: rgba(0, 174, 151, 0.12);
color: #00705f;
}
-.mod_lostPassword.block .survey-auth-back {
+.survey-auth-module .survey-auth-back {
margin: 0;
text-align: right;
}
-.mod_lostPassword.block .survey-auth-back a {
+.survey-auth-module .survey-auth-back a {
display: inline-flex;
align-items: center;
gap: 0.3rem;
@@ -488,14 +492,14 @@ nav.mod_customnav.block:has(a[href*="/abmelden"]) .level_1 > li > a:focus {
transition: color 0.18s ease, border-color 0.18s ease;
}
-.mod_lostPassword.block .survey-auth-back a::before {
+.survey-auth-module .survey-auth-back a::before {
content: "\2039";
font-size: 1.05em;
line-height: 1;
}
-.mod_lostPassword.block .survey-auth-back a:hover,
-.mod_lostPassword.block .survey-auth-back a:focus {
+.survey-auth-module .survey-auth-back a:hover,
+.survey-auth-module .survey-auth-back a:focus {
color: var(--survey-auth-orange);
border-color: rgba(236, 124, 50, 0.55);
outline: none;
@@ -583,14 +587,14 @@ a.survey-auth-button:focus {
outline: none;
}
-/* Meldungsseiten des Passwort-vergessen-Moduls (Contao-Template mod_message) */
-.mod_lostPassword.block .survey-auth-actions {
+/* Meldungsseiten der Auth-Module (Contao-Template mod_message) */
+.survey-auth-module .survey-auth-actions {
margin: 0;
text-align: center;
}
-/* Hilfetexte und Feldfehler im Passwort-vergessen-Modul */
-.mod_lostPassword.block p.help {
+/* Hilfetexte und Feldfehler in den Auth-Modulen */
+.survey-auth-module p.help {
margin: 0;
font-size: 0.9rem;
line-height: 1.4;
@@ -610,12 +614,12 @@ a.survey-auth-button:focus {
}
@media (max-width: 767px) {
- .mod_lostPassword.block {
+ .survey-auth-module {
padding: 1.2rem;
border-radius: 1.35rem;
}
- .mod_lostPassword.block button.submit {
+ .survey-auth-module button.submit {
width: 100%;
}
@@ -629,7 +633,7 @@ a.survey-auth-button:focus {
}
.mod_login.block.login .password-reset,
- .mod_lostPassword.block .survey-auth-back {
+ .survey-auth-module .survey-auth-back {
text-align: center;
}
}
diff --git a/translations/messages.de.yaml b/translations/messages.de.yaml
index 833f3af..4092f8b 100644
--- a/translations/messages.de.yaml
+++ b/translations/messages.de.yaml
@@ -172,6 +172,10 @@ survey:
headline: "Neues Passwort vergeben"
intro: "Bitte vergeben Sie jetzt Ihr neues Passwort für die Umfrageplattform. Anschließend können Sie sich damit sofort anmelden."
hint: "Wählen Sie ein Passwort, das Sie nicht bereits an anderer Stelle verwenden."
+ change_password:
+ headline: "Neues Passwort vergeben"
+ intro: "Bitte vergeben Sie ein neues Passwort für Ihren Zugang zur Umfrageplattform. Geben Sie dazu zuerst Ihr aktuelles Passwort ein und anschließend zweimal das neue."
+ hint: "Wählen Sie ein Passwort, das Sie nicht bereits an anderer Stelle verwenden."
back_to_login: "Zurück zur Anmeldung"
email:
cta_password: "Neues Passwort festlegen"
diff --git a/translations/messages.en.yaml b/translations/messages.en.yaml
index 4c9764a..3355b65 100644
--- a/translations/messages.en.yaml
+++ b/translations/messages.en.yaml
@@ -172,6 +172,10 @@ survey:
headline: "Set a new password"
intro: "Please choose your new password for the survey platform. You can log in with it right away."
hint: "Please pick a password that you do not already use elsewhere."
+ change_password:
+ headline: "Set a new password"
+ intro: "Please choose a new password for your survey platform account. Enter your current password first, then the new one twice."
+ hint: "Please pick a password that you do not already use elsewhere."
back_to_login: "Back to login"
email:
cta_password: "Set a new password"