From eb182096694797af8979207970fc246d55ffb366 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 13 Mar 2026 15:56:02 +0100 Subject: [PATCH] feat: rearrange smtp settings --- DESIGN_GUIDELINES.md | 5 + docs/smtp-configuration-concept.md | 2 + lib/mv_web/live/global_settings_live.ex | 1203 ++++++++++++----------- priv/gettext/de/LC_MESSAGES/default.po | 2 +- priv/gettext/default.pot | 2 +- priv/gettext/en/LC_MESSAGES/default.po | 2 +- 6 files changed, 636 insertions(+), 580 deletions(-) diff --git a/DESIGN_GUIDELINES.md b/DESIGN_GUIDELINES.md index 187864c..9a01f9d 100644 --- a/DESIGN_GUIDELINES.md +++ b/DESIGN_GUIDELINES.md @@ -221,6 +221,11 @@ If these cannot be met, use `secondary`/`outline` instead of `ghost`. - **MUST:** Required fields are marked consistently (UI indicator + accessible text). - **SHOULD:** If required-ness is configurable via settings, display it consistently in the form. +### 6.4 Form layout (settings / long forms) +- **SHOULD:** On wide viewports, use a responsive grid so related fields share a row and reduce scrolling (e.g. `grid grid-cols-1 lg:grid-cols-2` or `lg:grid-cols-[2fr_5rem_1fr]` for mixed widths). +- **SHOULD:** Limit the main content width for readability (e.g. Settings page uses `max-w-4xl mx-auto px-4` around the content area below the header). +- **Example:** SMTP settings use three rows on large screens (Host, Port, TLS/SSL | Username, Password | Sender email, Sender name) without subsection labels. + --- ## 7) Lists, Search & Filters (mandatory UX consistency) diff --git a/docs/smtp-configuration-concept.md b/docs/smtp-configuration-concept.md index c60a0e2..8832b5e 100644 --- a/docs/smtp-configuration-concept.md +++ b/docs/smtp-configuration-concept.md @@ -44,6 +44,8 @@ When an ENV variable is set, the corresponding Settings field is read-only in th **Important:** On most SMTP servers (e.g. Postfix with strict relay policies) the sender email (`smtp_from_email`) must be the same address as `smtp_username` or an alias that is owned by that account. +**Settings UI:** The form uses three rows on wide viewports: host, port, TLS/SSL | username, password | sender email, sender name. Content width is limited by the global settings wrapper (see `DESIGN_GUIDELINES.md` §6.4). + --- ## 5. Password from File diff --git a/lib/mv_web/live/global_settings_live.ex b/lib/mv_web/live/global_settings_live.ex index 84cf738..fadbc32 100644 --- a/lib/mv_web/live/global_settings_live.ex +++ b/lib/mv_web/live/global_settings_live.ex @@ -115,600 +115,649 @@ defmodule MvWeb.GlobalSettingsLive do - <%!-- Club Settings Section --%> - <.form_section title={gettext("Club Settings")}> - <.form for={@form} id="settings-form" phx-change="validate" phx-submit="save"> -
- <.input - field={@form[:club_name]} - type="text" - label={gettext("Association Name")} - required - /> -
- - <.button phx-disable-with={gettext("Saving...")} variant="primary"> - {gettext("Save Name")} - - - - <%!-- Join Form Section (Beitrittsformular) --%> - <.form_section title={gettext("Join Form")}> -

- {gettext("Configure the public join form that allows new members to submit a join request.")} -

- - <%!-- Enable/disable --%> -
- - -
- -
- <%!-- Copyable join page link (below checkbox, above field list) --%> -
-

- {gettext("Link to the public join page (share this with applicants):")} -

-
- - <.button - variant="secondary" - size="sm" - id="copy-join-url-btn" - phx-hook="CopyToClipboard" - phx-click="copy_join_url" - aria-label={gettext("Copy join page URL")} - > - <.icon name="hero-clipboard-document" class="size-4" /> - {gettext("Copy")} - -
-
- - <%!-- Field list header + Add button (left-aligned) --%> -

{gettext("Fields on the join form")}

-
- <.button - type="button" - variant="primary" - phx-click="toggle_add_field_dropdown" - disabled={ - Enum.empty?(@available_join_form_member_fields) and - Enum.empty?(@available_join_form_custom_fields) - } - aria-haspopup="listbox" - aria-expanded={to_string(@show_add_field_dropdown)} - > - <.icon name="hero-plus" class="size-4" /> - {gettext("Add field")} - - - <%!-- Available fields dropdown (sections: Personal data, Custom fields) --%> -
-
-
- {gettext("Personal data")} -
-
- {field.label} -
-
-
-
- {gettext("Individual fields")} -
-
- {field.label} -
-
-
-
- - <%!-- Empty state --%> -

- {gettext("No fields selected. Add at least the email field.")} -

- - <%!-- Fields table (compact width, reorderable) --%> -
- <.sortable_table - id="join-form-fields-table" - rows={@join_form_fields} - row_id={fn field -> "join-field-#{field.id}" end} - reorder_event="reorder_join_form_field" - > - <:col :let={field} label={gettext("Field")} class="min-w-[14rem]"> - {field.label} - - <:col :let={field} label={gettext("Required")} class="w-24 max-w-[9.375rem] text-center"> - - - <:action :let={field}> - <.tooltip content={gettext("Remove")} position="left"> - <.button - type="button" - variant="danger" - size="sm" - disabled={not field.can_remove} - class={if(not field.can_remove, do: "opacity-50 cursor-not-allowed", else: "")} - phx-click="remove_join_form_field" - phx-value-field_id={field.id} - aria-label={gettext("Remove field %{label}", label: field.label)} - > - <.icon name="hero-trash" class="size-4" /> - - - - -

- {gettext("The order of rows determines the field order in the join form.")} -

-
-
- - <%!-- SMTP / E-Mail Section --%> - <.form_section title={gettext("SMTP / E-Mail")}> - <%= if @smtp_env_configured do %> -

- {gettext("Some values are set via environment variables. Those fields are read-only.")} -

- <% end %> - - <%= if @environment == :prod and not @smtp_configured do %> -
- <.icon name="hero-exclamation-triangle" class="size-5 shrink-0 mt-0.5" /> - - {gettext( - "SMTP is not configured. Transactional emails (join confirmation, password reset, etc.) will not be delivered reliably." - )} - -
- <% end %> - - <.form for={@form} id="smtp-form" phx-change="validate" phx-submit="save"> -
- <.input - field={@form[:smtp_host]} - type="text" - label={gettext("Host")} - disabled={@smtp_host_env_set} - placeholder={ - if(@smtp_host_env_set, - do: gettext("From SMTP_HOST"), - else: "smtp.example.com" - ) - } - /> - <.input - field={@form[:smtp_port]} - type="number" - label={gettext("Port")} - disabled={@smtp_port_env_set} - placeholder={if(@smtp_port_env_set, do: gettext("From SMTP_PORT"), else: "587")} - /> - <.input - field={@form[:smtp_username]} - type="text" - label={gettext("Username")} - disabled={@smtp_username_env_set} - placeholder={ - if(@smtp_username_env_set, - do: gettext("From SMTP_USERNAME"), - else: "user@example.com" - ) - } - /> -
- +
+ <%!-- Club Settings Section --%> + <.form_section title={gettext("Club Settings")}> + <.form for={@form} id="settings-form" phx-change="validate" phx-submit="save"> +
<.input - field={@form[:smtp_password]} - type="password" - label="" - disabled={@smtp_password_env_set} - placeholder={ - if(@smtp_password_env_set, - do: gettext("From SMTP_PASSWORD"), - else: - if(@smtp_password_set, - do: gettext("Leave blank to keep current"), - else: nil - ) - ) - } + field={@form[:club_name]} + type="text" + label={gettext("Association Name")} + required />
- <.input - field={@form[:smtp_ssl]} - type="select" - label={gettext("TLS/SSL")} - disabled={@smtp_ssl_env_set} - options={[ - {gettext("TLS (port 587, recommended)"), "tls"}, - {gettext("SSL (port 465)"), "ssl"}, - {gettext("None (port 25, insecure)"), "none"} - ]} - placeholder={if(@smtp_ssl_env_set, do: gettext("From SMTP_SSL"), else: nil)} - /> - <.input - field={@form[:smtp_from_email]} - type="email" - label={gettext("Sender email (From)")} - disabled={@smtp_from_email_env_set} - placeholder={ - if(@smtp_from_email_env_set, - do: gettext("From MAIL_FROM_EMAIL"), - else: "noreply@example.com" - ) - } - /> - <.input - field={@form[:smtp_from_name]} - type="text" - label={gettext("Sender name (From)")} - disabled={@smtp_from_name_env_set} - placeholder={ - if(@smtp_from_name_env_set, do: gettext("From MAIL_FROM_NAME"), else: "Mila") - } - /> -
-

+ + <.button phx-disable-with={gettext("Saving...")} variant="primary"> + {gettext("Save Name")} + + + + <%!-- Join Form Section (Beitrittsformular) --%> + <.form_section title={gettext("Join Form")}> +

{gettext( - "The sender email must be owned by or authorized for the SMTP user on most servers." + "Configure the public join form that allows new members to submit a join request." )}

- <.button - :if={ - not (@smtp_host_env_set and @smtp_port_env_set and @smtp_username_env_set and - @smtp_password_env_set and @smtp_ssl_env_set and @smtp_from_email_env_set and - @smtp_from_name_env_set) - } - phx-disable-with={gettext("Saving...")} - variant="primary" - class="mt-2" - > - {gettext("Save SMTP Settings")} - - - <%!-- Test email: use form phx-submit so the current input value is always sent (e.g. after paste without blur) --%> -
-

{gettext("Test email")}

- <.form - for={%{}} - id="smtp-test-email-form" - data-testid="smtp-test-email-form" - phx-submit="send_smtp_test_email" - class="space-y-3" - > -
-
- + <%!-- Enable/disable --%> +
+ + +
+ +
+ <%!-- Copyable join page link (below checkbox, above field list) --%> +
+

+ {gettext("Link to the public join page (share this with applicants):")} +

+
+ <.button + variant="secondary" + size="sm" + id="copy-join-url-btn" + phx-hook="CopyToClipboard" + phx-click="copy_join_url" + aria-label={gettext("Copy join page URL")} + > + <.icon name="hero-clipboard-document" class="size-4" /> + {gettext("Copy")} +
- <.button - type="submit" - variant="outline" - data-testid="smtp-send-test-email" - phx-disable-with={gettext("Sending...")} - > - {gettext("Send test email")} -
- - <%= if @smtp_test_result do %> -
- <.smtp_test_result result={@smtp_test_result} /> -
- <% end %> -
- - <%!-- Vereinfacht Integration Section --%> - <.form_section title={gettext("Accounting-Software (Vereinfacht) Integration")}> - <%= if @vereinfacht_env_configured do %> -

- {gettext("Some values are set via environment variables. Those fields are read-only.")} -

- <% end %> - <.form for={@form} id="vereinfacht-form" phx-change="validate" phx-submit="save"> -
- <.input - field={@form[:vereinfacht_api_url]} - type="text" - label={gettext("API URL")} - disabled={@vereinfacht_api_url_env_set} - placeholder={ - if(@vereinfacht_api_url_env_set, - do: gettext("From VEREINFACHT_API_URL"), - else: "https://api.verein.visuel.dev/api/v1" - ) - } - /> -
- - <.input - field={@form[:vereinfacht_api_key]} - type="password" - label="" - disabled={@vereinfacht_api_key_env_set} - placeholder={ - if(@vereinfacht_api_key_env_set, - do: gettext("From VEREINFACHT_API_KEY"), - else: - if(@vereinfacht_api_key_set, - do: gettext("Leave blank to keep current"), - else: nil - ) - ) + <%!-- Field list header + Add button (left-aligned) --%> +

{gettext("Fields on the join form")}

+
+ <.button + type="button" + variant="primary" + phx-click="toggle_add_field_dropdown" + disabled={ + Enum.empty?(@available_join_form_member_fields) and + Enum.empty?(@available_join_form_custom_fields) } - /> + aria-haspopup="listbox" + aria-expanded={to_string(@show_add_field_dropdown)} + > + <.icon name="hero-plus" class="size-4" /> + {gettext("Add field")} + + + <%!-- Available fields dropdown (sections: Personal data, Custom fields) --%> +
+
+
+ {gettext("Personal data")} +
+
+ {field.label} +
+
+
+
+ {gettext("Individual fields")} +
+
+ {field.label} +
+
+
- <.input - field={@form[:vereinfacht_club_id]} - type="text" - label={gettext("Club ID")} - disabled={@vereinfacht_club_id_env_set} - placeholder={ - if(@vereinfacht_club_id_env_set, do: gettext("From VEREINFACHT_CLUB_ID"), else: "2") - } - /> - <.input - field={@form[:vereinfacht_app_url]} - type="text" - label={gettext("App URL (contact view link)")} - disabled={@vereinfacht_app_url_env_set} - placeholder={ - if(@vereinfacht_app_url_env_set, - do: gettext("From VEREINFACHT_APP_URL"), - else: "https://app.verein.visuel.dev" - ) - } - /> -
- <.button - :if={ - not (@vereinfacht_api_url_env_set and @vereinfacht_api_key_env_set and - @vereinfacht_club_id_env_set) - } - phx-disable-with={gettext("Saving...")} - variant="primary" - class="mt-2" - > - {gettext("Save Vereinfacht Settings")} - -
- <.button - :if={Mv.Config.vereinfacht_configured?()} - type="button" - variant="outline" - phx-click="test_vereinfacht_connection" - phx-disable-with={gettext("Testing...")} - > - {gettext("Test Integration")} - - <.button - :if={Mv.Config.vereinfacht_configured?()} - type="button" - variant="outline" - phx-click="sync_vereinfacht_contacts" - phx-disable-with={gettext("Syncing...")} - > - {gettext("Sync all members without Vereinfacht contact")} - -
- <%= if @vereinfacht_test_result do %> - <.vereinfacht_test_result result={@vereinfacht_test_result} /> - <% end %> - <%= if @last_vereinfacht_sync_result do %> - <.vereinfacht_sync_result result={@last_vereinfacht_sync_result} /> - <% end %> - - - <%!-- OIDC Section --%> - <.form_section title={gettext("OIDC (Single Sign-On)")}> - <%= if @oidc_env_configured do %> -

- {gettext("Some values are set via environment variables. Those fields are read-only.")} -

- <% end %> - <.form for={@form} id="oidc-form" phx-change="validate" phx-submit="save"> -
- <.input - field={@form[:oidc_client_id]} - type="text" - label={gettext("Client ID")} - disabled={@oidc_client_id_env_set} - placeholder={ - if(@oidc_client_id_env_set, do: gettext("From OIDC_CLIENT_ID"), else: "mv") - } - /> - <.input - field={@form[:oidc_base_url]} - type="text" - label={gettext("Base URL")} - disabled={@oidc_base_url_env_set} - placeholder={ - if(@oidc_base_url_env_set, - do: gettext("From OIDC_BASE_URL"), - else: "http://localhost:8080/auth/v1" - ) - } - /> - <.input - field={@form[:oidc_redirect_uri]} - type="text" - label={gettext("Redirect URI")} - disabled={@oidc_redirect_uri_env_set} - placeholder={ - if(@oidc_redirect_uri_env_set, - do: gettext("From OIDC_REDIRECT_URI"), - else: "http://localhost:4000/auth/user/oidc/callback" - ) - } - /> -
- - <.input - field={@form[:oidc_client_secret]} - type="password" - label="" - disabled={@oidc_client_secret_env_set} - placeholder={ - if(@oidc_client_secret_env_set, - do: gettext("From OIDC_CLIENT_SECRET"), - else: - if(@oidc_client_secret_set, - do: gettext("Leave blank to keep current"), - else: nil - ) - ) - } - /> -
- <.input - field={@form[:oidc_admin_group_name]} - type="text" - label={gettext("Admin group name")} - disabled={@oidc_admin_group_name_env_set} - placeholder={ - if(@oidc_admin_group_name_env_set, - do: gettext("From OIDC_ADMIN_GROUP_NAME"), - else: gettext("e.g. admin") - ) - } - /> - <.input - field={@form[:oidc_groups_claim]} - type="text" - label={gettext("Groups claim")} - disabled={@oidc_groups_claim_env_set} - placeholder={ - if(@oidc_groups_claim_env_set, - do: gettext("From OIDC_GROUPS_CLAIM"), - else: "groups" - ) - } - /> -
- <.input - field={@form[:oidc_only]} - type="checkbox" - class="checkbox checkbox-sm" - disabled={@oidc_only_env_set or not @oidc_configured} - label={ - if @oidc_only_env_set do - gettext("Only OIDC sign-in (hide password login)") <> - " (" <> gettext("From OIDC_ONLY") <> ")" - else - gettext("Only OIDC sign-in (hide password login)") - end - } - /> -

- {gettext( - "When enabled and OIDC is configured, the sign-in page shows only the Single Sign-On button." - )} + + <%!-- Empty state --%> +

+ {gettext("No fields selected. Add at least the email field.")} +

+ + <%!-- Fields table (compact width, reorderable) --%> +
+ <.sortable_table + id="join-form-fields-table" + rows={@join_form_fields} + row_id={fn field -> "join-field-#{field.id}" end} + reorder_event="reorder_join_form_field" + > + <:col :let={field} label={gettext("Field")} class="min-w-[14rem]"> + {field.label} + + <:col + :let={field} + label={gettext("Required")} + class="w-24 max-w-[9.375rem] text-center" + > + + + <:action :let={field}> + <.tooltip content={gettext("Remove")} position="left"> + <.button + type="button" + variant="danger" + size="sm" + disabled={not field.can_remove} + class={if(not field.can_remove, do: "opacity-50 cursor-not-allowed", else: "")} + phx-click="remove_join_form_field" + phx-value-field_id={field.id} + aria-label={gettext("Remove field %{label}", label: field.label)} + > + <.icon name="hero-trash" class="size-4" /> + + + + +

+ {gettext("The order of rows determines the field order in the join form.")}

- <.button - :if={ - not (@oidc_client_id_env_set and @oidc_base_url_env_set and - @oidc_redirect_uri_env_set and @oidc_client_secret_env_set and - @oidc_admin_group_name_env_set and @oidc_groups_claim_env_set and - @oidc_only_env_set) - } - phx-disable-with={gettext("Saving...")} - variant="primary" - class="mt-2" - > - {gettext("Save OIDC Settings")} - - - + + <%!-- SMTP / E-Mail Section --%> + <.form_section title={gettext("SMTP / E-Mail")}> + <%= if @smtp_env_configured do %> +

+ {gettext("Some values are set via environment variables. Those fields are read-only.")} +

+ <% end %> + + <%= if @environment == :prod and not @smtp_configured do %> +
+ <.icon name="hero-exclamation-triangle" class="size-5 shrink-0 mt-0.5" /> + + {gettext( + "SMTP is not configured. Transactional emails (join confirmation, password reset, etc.) will not be delivered reliably." + )} + +
+ <% end %> + + <.form for={@form} id="smtp-form" phx-change="validate" phx-submit="save"> +
+
+ <.input + field={@form[:smtp_host]} + type="text" + label={gettext("Host")} + disabled={@smtp_host_env_set} + placeholder={ + if(@smtp_host_env_set, + do: gettext("From SMTP_HOST"), + else: "smtp.example.com" + ) + } + /> + <.input + field={@form[:smtp_port]} + type="number" + label={gettext("Port")} + disabled={@smtp_port_env_set} + placeholder={if(@smtp_port_env_set, do: gettext("From SMTP_PORT"), else: "587")} + /> + <.input + field={@form[:smtp_ssl]} + type="select" + label={gettext("TLS/SSL")} + disabled={@smtp_ssl_env_set} + options={[ + {gettext("TLS (port 587, recommended)"), "tls"}, + {gettext("SSL (port 465)"), "ssl"}, + {gettext("None (port 25, insecure)"), "none"} + ]} + placeholder={if(@smtp_ssl_env_set, do: gettext("From SMTP_SSL"), else: nil)} + /> +
+ +
+ <.input + field={@form[:smtp_username]} + type="text" + label={gettext("Username")} + disabled={@smtp_username_env_set} + placeholder={ + if(@smtp_username_env_set, + do: gettext("From SMTP_USERNAME"), + else: "user@example.com" + ) + } + /> + <.input + field={@form[:smtp_password]} + type="password" + label={gettext("Password")} + disabled={@smtp_password_env_set} + placeholder={ + if(@smtp_password_env_set, + do: gettext("From SMTP_PASSWORD"), + else: + if(@smtp_password_set, + do: gettext("Leave blank to keep current"), + else: nil + ) + ) + } + /> +
+ +
+ <.input + field={@form[:smtp_from_email]} + type="email" + label={gettext("Sender email (From)")} + disabled={@smtp_from_email_env_set} + placeholder={ + if(@smtp_from_email_env_set, + do: gettext("From MAIL_FROM_EMAIL"), + else: "noreply@example.com" + ) + } + /> + <.input + field={@form[:smtp_from_name]} + type="text" + label={gettext("Sender name (From)")} + disabled={@smtp_from_name_env_set} + placeholder={ + if(@smtp_from_name_env_set, do: gettext("From MAIL_FROM_NAME"), else: "Mila") + } + /> +
+
+

+ {gettext( + "The sender email must be owned by or authorized for the SMTP user on most servers." + )} +

+ <.button + :if={ + not (@smtp_host_env_set and @smtp_port_env_set and @smtp_username_env_set and + @smtp_password_env_set and @smtp_ssl_env_set and @smtp_from_email_env_set and + @smtp_from_name_env_set) + } + phx-disable-with={gettext("Saving...")} + variant="primary" + class="mt-2" + > + {gettext("Save SMTP Settings")} + + + + <%!-- Test email: use form phx-submit so the current input value is always sent (e.g. after paste without blur) --%> +
+

{gettext("Test email")}

+ <.form + for={%{}} + id="smtp-test-email-form" + data-testid="smtp-test-email-form" + phx-submit="send_smtp_test_email" + class="space-y-3" + > +
+
+ +
+ <.button + type="submit" + variant="secondary" + class="mb-1" + data-testid="smtp-send-test-email" + phx-disable-with={gettext("Sending...")} + > + {gettext("Send test email")} + +
+ + <%= if @smtp_test_result do %> +
+ <.smtp_test_result result={@smtp_test_result} /> +
+ <% end %> +
+ + + <%!-- Vereinfacht Integration Section --%> + <.form_section title={gettext("Accounting-Software (Vereinfacht) Integration")}> + <%= if @vereinfacht_env_configured do %> +

+ {gettext("Some values are set via environment variables. Those fields are read-only.")} +

+ <% end %> + <.form for={@form} id="vereinfacht-form" phx-change="validate" phx-submit="save"> +
+ <.input + field={@form[:vereinfacht_api_url]} + type="text" + label={gettext("API URL")} + disabled={@vereinfacht_api_url_env_set} + placeholder={ + if(@vereinfacht_api_url_env_set, + do: gettext("From VEREINFACHT_API_URL"), + else: "https://api.verein.visuel.dev/api/v1" + ) + } + /> +
+ + <%= for msg <- ( + if Phoenix.Component.used_input?(@form[:vereinfacht_api_key]) do + Enum.map(@form[:vereinfacht_api_key].errors, &MvWeb.CoreComponents.translate_error/1) + else + [] + end + ) do %> +

+ <.icon name="hero-exclamation-circle" class="size-5" /> + {msg} +

+ <% end %> +
+ <.input + field={@form[:vereinfacht_club_id]} + type="text" + label={gettext("Club ID")} + disabled={@vereinfacht_club_id_env_set} + placeholder={ + if(@vereinfacht_club_id_env_set, do: gettext("From VEREINFACHT_CLUB_ID"), else: "2") + } + /> + <.input + field={@form[:vereinfacht_app_url]} + type="text" + label={gettext("App URL (contact view link)")} + disabled={@vereinfacht_app_url_env_set} + placeholder={ + if(@vereinfacht_app_url_env_set, + do: gettext("From VEREINFACHT_APP_URL"), + else: "https://app.verein.visuel.dev" + ) + } + /> +
+ <.button + :if={ + not (@vereinfacht_api_url_env_set and @vereinfacht_api_key_env_set and + @vereinfacht_club_id_env_set) + } + phx-disable-with={gettext("Saving...")} + variant="primary" + class="mt-2" + > + {gettext("Save Vereinfacht Settings")} + +
+ <.button + :if={Mv.Config.vereinfacht_configured?()} + type="button" + variant="secondary" + phx-click="test_vereinfacht_connection" + phx-disable-with={gettext("Testing...")} + > + {gettext("Test Integration")} + + <.button + :if={Mv.Config.vereinfacht_configured?()} + type="button" + variant="secondary" + phx-click="sync_vereinfacht_contacts" + phx-disable-with={gettext("Syncing...")} + > + {gettext("Sync all members without Vereinfacht contact")} + +
+ <%= if @vereinfacht_test_result do %> + <.vereinfacht_test_result result={@vereinfacht_test_result} /> + <% end %> + <%= if @last_vereinfacht_sync_result do %> + <.vereinfacht_sync_result result={@last_vereinfacht_sync_result} /> + <% end %> + + + <%!-- OIDC Section --%> + <.form_section title={gettext("OIDC (Single Sign-On)")}> + <%= if @oidc_env_configured do %> +

+ {gettext("Some values are set via environment variables. Those fields are read-only.")} +

+ <% end %> + <.form for={@form} id="oidc-form" phx-change="validate" phx-submit="save"> +
+ <.input + field={@form[:oidc_client_id]} + type="text" + label={gettext("Client ID")} + disabled={@oidc_client_id_env_set} + placeholder={ + if(@oidc_client_id_env_set, do: gettext("From OIDC_CLIENT_ID"), else: "mv") + } + /> + <.input + field={@form[:oidc_base_url]} + type="text" + label={gettext("Base URL")} + disabled={@oidc_base_url_env_set} + placeholder={ + if(@oidc_base_url_env_set, + do: gettext("From OIDC_BASE_URL"), + else: "http://localhost:8080/auth/v1" + ) + } + /> + <.input + field={@form[:oidc_redirect_uri]} + type="text" + label={gettext("Redirect URI")} + disabled={@oidc_redirect_uri_env_set} + placeholder={ + if(@oidc_redirect_uri_env_set, + do: gettext("From OIDC_REDIRECT_URI"), + else: "http://localhost:4000/auth/user/oidc/callback" + ) + } + /> +
+ + <%= for msg <- ( + if Phoenix.Component.used_input?(@form[:oidc_client_secret]) do + Enum.map(@form[:oidc_client_secret].errors, &MvWeb.CoreComponents.translate_error/1) + else + [] + end + ) do %> +

+ <.icon name="hero-exclamation-circle" class="size-5" /> + {msg} +

+ <% end %> +
+ <.input + field={@form[:oidc_admin_group_name]} + type="text" + label={gettext("Admin group name")} + disabled={@oidc_admin_group_name_env_set} + placeholder={ + if(@oidc_admin_group_name_env_set, + do: gettext("From OIDC_ADMIN_GROUP_NAME"), + else: gettext("e.g. admin") + ) + } + /> + <.input + field={@form[:oidc_groups_claim]} + type="text" + label={gettext("Groups claim")} + disabled={@oidc_groups_claim_env_set} + placeholder={ + if(@oidc_groups_claim_env_set, + do: gettext("From OIDC_GROUPS_CLAIM"), + else: "groups" + ) + } + /> +
+ <.input + field={@form[:oidc_only]} + type="checkbox" + class="checkbox checkbox-sm" + disabled={@oidc_only_env_set or not @oidc_configured} + label={ + if @oidc_only_env_set do + gettext("Only OIDC sign-in (hide password login)") <> + " (" <> gettext("From OIDC_ONLY") <> ")" + else + gettext("Only OIDC sign-in (hide password login)") + end + } + /> +

+ {gettext( + "When enabled and OIDC is configured, the sign-in page shows only the Single Sign-On button." + )} +

+
+
+ <.button + :if={ + not (@oidc_client_id_env_set and @oidc_base_url_env_set and + @oidc_redirect_uri_env_set and @oidc_client_secret_env_set and + @oidc_admin_group_name_env_set and @oidc_groups_claim_env_set and + @oidc_only_env_set) + } + phx-disable-with={gettext("Saving...")} + variant="primary" + class="mt-2" + > + {gettext("Save OIDC Settings")} + + + +
""" end diff --git a/priv/gettext/de/LC_MESSAGES/default.po b/priv/gettext/de/LC_MESSAGES/default.po index a96e6c9..c23799a 100644 --- a/priv/gettext/de/LC_MESSAGES/default.po +++ b/priv/gettext/de/LC_MESSAGES/default.po @@ -3306,7 +3306,7 @@ msgstr "Um die Löschung zu bestätigen, gib bitte den Gruppennamen ein:" msgid "To confirm deletion, please enter this text:" msgstr "Um die Löschung zu bestätigen, gib bitte folgenden Text ein:" -#: lib/mv_web/components/layouts/sidebar.ex +#: lib/mv_web/components/core_components.ex #, elixir-autogen, elixir-format msgid "Toggle dark mode" msgstr "Dunklen Modus umschalten" diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index 6945957..ff61365 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -3307,7 +3307,7 @@ msgstr "" msgid "To confirm deletion, please enter this text:" msgstr "" -#: lib/mv_web/components/layouts/sidebar.ex +#: lib/mv_web/components/core_components.ex #, elixir-autogen, elixir-format msgid "Toggle dark mode" msgstr "" diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po index 827290b..82aed54 100644 --- a/priv/gettext/en/LC_MESSAGES/default.po +++ b/priv/gettext/en/LC_MESSAGES/default.po @@ -3307,7 +3307,7 @@ msgstr "" msgid "To confirm deletion, please enter this text:" msgstr "" -#: lib/mv_web/components/layouts/sidebar.ex +#: lib/mv_web/components/core_components.ex #, elixir-autogen, elixir-format msgid "Toggle dark mode" msgstr ""