feat: rearrange smtp settings
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
104faf7006
commit
eb18209669
6 changed files with 636 additions and 580 deletions
|
|
@ -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).
|
- **MUST:** Required fields are marked consistently (UI indicator + accessible text).
|
||||||
- **SHOULD:** If required-ness is configurable via settings, display it consistently in the form.
|
- **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)
|
## 7) Lists, Search & Filters (mandatory UX consistency)
|
||||||
|
|
|
||||||
|
|
@ -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.
|
**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
|
## 5. Password from File
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,7 @@ defmodule MvWeb.GlobalSettingsLive do
|
||||||
</:subtitle>
|
</:subtitle>
|
||||||
</.header>
|
</.header>
|
||||||
|
|
||||||
|
<div class="mt-6 space-y-6 max-w-4xl px-4">
|
||||||
<%!-- Club Settings Section --%>
|
<%!-- Club Settings Section --%>
|
||||||
<.form_section title={gettext("Club Settings")}>
|
<.form_section title={gettext("Club Settings")}>
|
||||||
<.form for={@form} id="settings-form" phx-change="validate" phx-submit="save">
|
<.form for={@form} id="settings-form" phx-change="validate" phx-submit="save">
|
||||||
|
|
@ -135,7 +136,9 @@ defmodule MvWeb.GlobalSettingsLive do
|
||||||
<%!-- Join Form Section (Beitrittsformular) --%>
|
<%!-- Join Form Section (Beitrittsformular) --%>
|
||||||
<.form_section title={gettext("Join Form")}>
|
<.form_section title={gettext("Join Form")}>
|
||||||
<p class="text-sm text-base-content/70 mb-4">
|
<p class="text-sm text-base-content/70 mb-4">
|
||||||
{gettext("Configure the public join form that allows new members to submit a join request.")}
|
{gettext(
|
||||||
|
"Configure the public join form that allows new members to submit a join request."
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<%!-- Enable/disable --%>
|
<%!-- Enable/disable --%>
|
||||||
|
|
@ -155,7 +158,7 @@ defmodule MvWeb.GlobalSettingsLive do
|
||||||
|
|
||||||
<div :if={@join_form_enabled}>
|
<div :if={@join_form_enabled}>
|
||||||
<%!-- Copyable join page link (below checkbox, above field list) --%>
|
<%!-- Copyable join page link (below checkbox, above field list) --%>
|
||||||
<div class="mb-4 p-3 max-w-2xl rounded-lg border border-base-300 bg-base-200/50">
|
<div class="mb-4 p-3 rounded-lg border border-base-300 bg-base-200/50">
|
||||||
<p class="text-sm text-base-content/70 mb-2">
|
<p class="text-sm text-base-content/70 mb-2">
|
||||||
{gettext("Link to the public join page (share this with applicants):")}
|
{gettext("Link to the public join page (share this with applicants):")}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -253,7 +256,7 @@ defmodule MvWeb.GlobalSettingsLive do
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<%!-- Fields table (compact width, reorderable) --%>
|
<%!-- Fields table (compact width, reorderable) --%>
|
||||||
<div :if={not Enum.empty?(@join_form_fields)} class="mb-4 max-w-2xl">
|
<div :if={not Enum.empty?(@join_form_fields)} class="mb-4">
|
||||||
<.sortable_table
|
<.sortable_table
|
||||||
id="join-form-fields-table"
|
id="join-form-fields-table"
|
||||||
rows={@join_form_fields}
|
rows={@join_form_fields}
|
||||||
|
|
@ -263,7 +266,11 @@ defmodule MvWeb.GlobalSettingsLive do
|
||||||
<:col :let={field} label={gettext("Field")} class="min-w-[14rem]">
|
<:col :let={field} label={gettext("Field")} class="min-w-[14rem]">
|
||||||
{field.label}
|
{field.label}
|
||||||
</:col>
|
</:col>
|
||||||
<:col :let={field} label={gettext("Required")} class="w-24 max-w-[9.375rem] text-center">
|
<:col
|
||||||
|
:let={field}
|
||||||
|
label={gettext("Required")}
|
||||||
|
class="w-24 max-w-[9.375rem] text-center"
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="checkbox checkbox-sm"
|
class="checkbox checkbox-sm"
|
||||||
|
|
@ -317,7 +324,8 @@ defmodule MvWeb.GlobalSettingsLive do
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<.form for={@form} id="smtp-form" phx-change="validate" phx-submit="save">
|
<.form for={@form} id="smtp-form" phx-change="validate" phx-submit="save">
|
||||||
<div class="grid gap-4">
|
<div class="">
|
||||||
|
<div class="grid grid-cols-1 gap-4 lg:grid-cols-[2fr_5rem_1fr]">
|
||||||
<.input
|
<.input
|
||||||
field={@form[:smtp_host]}
|
field={@form[:smtp_host]}
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -337,6 +345,21 @@ defmodule MvWeb.GlobalSettingsLive do
|
||||||
disabled={@smtp_port_env_set}
|
disabled={@smtp_port_env_set}
|
||||||
placeholder={if(@smtp_port_env_set, do: gettext("From SMTP_PORT"), else: "587")}
|
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)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
||||||
<.input
|
<.input
|
||||||
field={@form[:smtp_username]}
|
field={@form[:smtp_username]}
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -349,19 +372,10 @@ defmodule MvWeb.GlobalSettingsLive do
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<div class="form-control">
|
|
||||||
<label class="label" for={@form[:smtp_password].id}>
|
|
||||||
<span class="label-text">{gettext("Password")}</span>
|
|
||||||
<%= if @smtp_password_set do %>
|
|
||||||
<span class="label-text-alt">
|
|
||||||
<.badge variant="neutral" size="sm">{gettext("(set)")}</.badge>
|
|
||||||
</span>
|
|
||||||
<% end %>
|
|
||||||
</label>
|
|
||||||
<.input
|
<.input
|
||||||
field={@form[:smtp_password]}
|
field={@form[:smtp_password]}
|
||||||
type="password"
|
type="password"
|
||||||
label=""
|
label={gettext("Password")}
|
||||||
disabled={@smtp_password_env_set}
|
disabled={@smtp_password_env_set}
|
||||||
placeholder={
|
placeholder={
|
||||||
if(@smtp_password_env_set,
|
if(@smtp_password_env_set,
|
||||||
|
|
@ -375,18 +389,8 @@ defmodule MvWeb.GlobalSettingsLive do
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<.input
|
|
||||||
field={@form[:smtp_ssl]}
|
<div class="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
||||||
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
|
<.input
|
||||||
field={@form[:smtp_from_email]}
|
field={@form[:smtp_from_email]}
|
||||||
type="email"
|
type="email"
|
||||||
|
|
@ -409,7 +413,8 @@ defmodule MvWeb.GlobalSettingsLive do
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-2 text-sm text-base-content/60">
|
</div>
|
||||||
|
<p class="mb-3 text-sm text-base-content/60">
|
||||||
{gettext(
|
{gettext(
|
||||||
"The sender email must be owned by or authorized for the SMTP user on most servers."
|
"The sender email must be owned by or authorized for the SMTP user on most servers."
|
||||||
)}
|
)}
|
||||||
|
|
@ -439,24 +444,25 @@ defmodule MvWeb.GlobalSettingsLive do
|
||||||
class="space-y-3"
|
class="space-y-3"
|
||||||
>
|
>
|
||||||
<div class="flex flex-wrap items-end gap-3">
|
<div class="flex flex-wrap items-end gap-3">
|
||||||
<div class="form-control">
|
<fieldset class="fieldset">
|
||||||
<label class="label" for="smtp-test-to-email">
|
<label>
|
||||||
<span class="label-text">{gettext("Recipient")}</span>
|
<span class="mb-1 label">{gettext("Recipient")}</span>
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
id="smtp-test-to-email"
|
id="smtp-test-to-email"
|
||||||
type="email"
|
type="email"
|
||||||
name="to_email"
|
name="to_email"
|
||||||
data-testid="smtp-test-email-input"
|
data-testid="smtp-test-email-input"
|
||||||
value={@smtp_test_to_email}
|
value={@smtp_test_to_email}
|
||||||
class="input input-bordered"
|
class="w-full input input-bordered"
|
||||||
placeholder="test@example.com"
|
placeholder="test@example.com"
|
||||||
phx-change="update_smtp_test_to_email"
|
phx-change="update_smtp_test_to_email"
|
||||||
/>
|
/>
|
||||||
</div>
|
</label>
|
||||||
|
</fieldset>
|
||||||
<.button
|
<.button
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="outline"
|
variant="secondary"
|
||||||
|
class="mb-1"
|
||||||
data-testid="smtp-send-test-email"
|
data-testid="smtp-send-test-email"
|
||||||
phx-disable-with={gettext("Sending...")}
|
phx-disable-with={gettext("Sending...")}
|
||||||
>
|
>
|
||||||
|
|
@ -493,19 +499,27 @@ defmodule MvWeb.GlobalSettingsLive do
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<div class="form-control">
|
<fieldset class="mb-2 fieldset">
|
||||||
<label class="label" for={@form[:vereinfacht_api_key].id}>
|
<label>
|
||||||
<span class="label-text">{gettext("API Key")}</span>
|
<span class="mb-1 label">{gettext("API Key")}</span>
|
||||||
<%= if @vereinfacht_api_key_set do %>
|
<%= if @vereinfacht_api_key_set do %>
|
||||||
<span class="label-text-alt">
|
<span class="label-text-alt">
|
||||||
<.badge variant="neutral" size="sm">{gettext("(set)")}</.badge>
|
<.badge variant="neutral" size="sm">{gettext("(set)")}</.badge>
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</label>
|
<input
|
||||||
<.input
|
|
||||||
field={@form[:vereinfacht_api_key]}
|
|
||||||
type="password"
|
type="password"
|
||||||
label=""
|
name={@form[:vereinfacht_api_key].name}
|
||||||
|
id={@form[:vereinfacht_api_key].id}
|
||||||
|
value={
|
||||||
|
Phoenix.HTML.Form.normalize_value("password", @form[:vereinfacht_api_key].value)
|
||||||
|
}
|
||||||
|
class={
|
||||||
|
if Phoenix.Component.used_input?(@form[:vereinfacht_api_key]) &&
|
||||||
|
@form[:vereinfacht_api_key].errors != [],
|
||||||
|
do: "w-full input input-error",
|
||||||
|
else: "w-full input"
|
||||||
|
}
|
||||||
disabled={@vereinfacht_api_key_env_set}
|
disabled={@vereinfacht_api_key_env_set}
|
||||||
placeholder={
|
placeholder={
|
||||||
if(@vereinfacht_api_key_env_set,
|
if(@vereinfacht_api_key_env_set,
|
||||||
|
|
@ -518,7 +532,20 @@ defmodule MvWeb.GlobalSettingsLive do
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</label>
|
||||||
|
<%= 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 %>
|
||||||
|
<p class="mt-1.5 flex gap-2 items-center text-sm text-error">
|
||||||
|
<.icon name="hero-exclamation-circle" class="size-5" />
|
||||||
|
{msg}
|
||||||
|
</p>
|
||||||
|
<% end %>
|
||||||
|
</fieldset>
|
||||||
<.input
|
<.input
|
||||||
field={@form[:vereinfacht_club_id]}
|
field={@form[:vereinfacht_club_id]}
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -556,7 +583,7 @@ defmodule MvWeb.GlobalSettingsLive do
|
||||||
<.button
|
<.button
|
||||||
:if={Mv.Config.vereinfacht_configured?()}
|
:if={Mv.Config.vereinfacht_configured?()}
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="secondary"
|
||||||
phx-click="test_vereinfacht_connection"
|
phx-click="test_vereinfacht_connection"
|
||||||
phx-disable-with={gettext("Testing...")}
|
phx-disable-with={gettext("Testing...")}
|
||||||
>
|
>
|
||||||
|
|
@ -565,7 +592,7 @@ defmodule MvWeb.GlobalSettingsLive do
|
||||||
<.button
|
<.button
|
||||||
:if={Mv.Config.vereinfacht_configured?()}
|
:if={Mv.Config.vereinfacht_configured?()}
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="secondary"
|
||||||
phx-click="sync_vereinfacht_contacts"
|
phx-click="sync_vereinfacht_contacts"
|
||||||
phx-disable-with={gettext("Syncing...")}
|
phx-disable-with={gettext("Syncing...")}
|
||||||
>
|
>
|
||||||
|
|
@ -622,19 +649,27 @@ defmodule MvWeb.GlobalSettingsLive do
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<div class="form-control">
|
<fieldset class="mb-2 fieldset">
|
||||||
<label class="label" for={@form[:oidc_client_secret].id}>
|
<label>
|
||||||
<span class="label-text">{gettext("Client Secret")}</span>
|
<span class="mb-1 label">{gettext("Client Secret")}</span>
|
||||||
<%= if @oidc_client_secret_set do %>
|
<%= if @oidc_client_secret_set do %>
|
||||||
<span class="label-text-alt">
|
<span class="label-text-alt">
|
||||||
<.badge variant="neutral" size="sm">{gettext("(set)")}</.badge>
|
<.badge variant="neutral" size="sm">{gettext("(set)")}</.badge>
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</label>
|
<input
|
||||||
<.input
|
|
||||||
field={@form[:oidc_client_secret]}
|
|
||||||
type="password"
|
type="password"
|
||||||
label=""
|
name={@form[:oidc_client_secret].name}
|
||||||
|
id={@form[:oidc_client_secret].id}
|
||||||
|
value={
|
||||||
|
Phoenix.HTML.Form.normalize_value("password", @form[:oidc_client_secret].value)
|
||||||
|
}
|
||||||
|
class={
|
||||||
|
if Phoenix.Component.used_input?(@form[:oidc_client_secret]) &&
|
||||||
|
@form[:oidc_client_secret].errors != [],
|
||||||
|
do: "w-full input input-error",
|
||||||
|
else: "w-full input"
|
||||||
|
}
|
||||||
disabled={@oidc_client_secret_env_set}
|
disabled={@oidc_client_secret_env_set}
|
||||||
placeholder={
|
placeholder={
|
||||||
if(@oidc_client_secret_env_set,
|
if(@oidc_client_secret_env_set,
|
||||||
|
|
@ -647,7 +682,20 @@ defmodule MvWeb.GlobalSettingsLive do
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</label>
|
||||||
|
<%= 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 %>
|
||||||
|
<p class="mt-1.5 flex gap-2 items-center text-sm text-error">
|
||||||
|
<.icon name="hero-exclamation-circle" class="size-5" />
|
||||||
|
{msg}
|
||||||
|
</p>
|
||||||
|
<% end %>
|
||||||
|
</fieldset>
|
||||||
<.input
|
<.input
|
||||||
field={@form[:oidc_admin_group_name]}
|
field={@form[:oidc_admin_group_name]}
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -709,6 +757,7 @@ defmodule MvWeb.GlobalSettingsLive do
|
||||||
</.button>
|
</.button>
|
||||||
</.form>
|
</.form>
|
||||||
</.form_section>
|
</.form_section>
|
||||||
|
</div>
|
||||||
</Layouts.app>
|
</Layouts.app>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -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:"
|
msgid "To confirm deletion, please enter this text:"
|
||||||
msgstr "Um die Löschung zu bestätigen, gib bitte folgenden Text ein:"
|
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
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Toggle dark mode"
|
msgid "Toggle dark mode"
|
||||||
msgstr "Dunklen Modus umschalten"
|
msgstr "Dunklen Modus umschalten"
|
||||||
|
|
|
||||||
|
|
@ -3307,7 +3307,7 @@ msgstr ""
|
||||||
msgid "To confirm deletion, please enter this text:"
|
msgid "To confirm deletion, please enter this text:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/components/layouts/sidebar.ex
|
#: lib/mv_web/components/core_components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Toggle dark mode"
|
msgid "Toggle dark mode"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
|
||||||
|
|
@ -3307,7 +3307,7 @@ msgstr ""
|
||||||
msgid "To confirm deletion, please enter this text:"
|
msgid "To confirm deletion, please enter this text:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/components/layouts/sidebar.ex
|
#: lib/mv_web/components/core_components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Toggle dark mode"
|
msgid "Toggle dark mode"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue