Compare commits
34 commits
edb3978b64
...
ff9b3bc5f3
| Author | SHA1 | Date | |
|---|---|---|---|
| ff9b3bc5f3 | |||
| 55943d0640 | |||
| 778700d5c3 | |||
| 80903a48fe | |||
| 0cc740b5b6 | |||
| 4846961d7a | |||
| d78d416d89 | |||
| 4f74d54128 | |||
| 25919470d9 | |||
|
|
96434c020b | ||
|
|
bba7683200 | ||
|
|
fd8f046298 | ||
| 18f0b44144 | |||
|
|
8b86b7139c | ||
| 5c4f8ec07e | |||
| ed9616035b | |||
| ab81b29467 | |||
| 50c80eed38 | |||
| f17f8fe74d | |||
| bbf760c2b5 | |||
| f485f7bd8f | |||
|
|
d620e9077a | ||
| 7aa53dc9ef | |||
| aa843933f9 | |||
| 4dd114c22a | |||
| 2255dfbf6e | |||
| c2cb75a32b | |||
| acaa12fea6 | |||
| 15d6fd38c9 | |||
| afda276d22 | |||
| 0334260de5 | |||
| 50832da885 | |||
| d89b1d1cc0 | |||
|
|
8cb023dc61 |
19 changed files with 1692 additions and 362 deletions
|
|
@ -118,7 +118,7 @@ environment:
|
|||
|
||||
steps:
|
||||
- name: renovate
|
||||
image: renovate/renovate:41.37
|
||||
image: renovate/renovate:41.42
|
||||
environment:
|
||||
RENOVATE_CONFIG_FILE: "renovate_backend_config.js"
|
||||
RENOVATE_TOKEN:
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
elixir 1.18.3-otp-27
|
||||
erlang 27.3.4
|
||||
just 1.42.2
|
||||
just 1.42.3
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ services:
|
|||
|
||||
rauthy:
|
||||
container_name: rauthy-dev
|
||||
image: ghcr.io/sebadob/rauthy:0.31.2
|
||||
image: ghcr.io/sebadob/rauthy:0.31.3
|
||||
environment:
|
||||
- LOCAL_TEST=true
|
||||
- SMTP_URL=mailcrab
|
||||
|
|
|
|||
|
|
@ -71,6 +71,19 @@ defmodule Mv.Accounts.User do
|
|||
accept [:email]
|
||||
end
|
||||
|
||||
# Admin action for direct password changes in admin panel
|
||||
# Uses the official Ash Authentication HashPasswordChange with correct context
|
||||
update :admin_set_password do
|
||||
accept [:email]
|
||||
argument :password, :string, allow_nil?: false, sensitive?: true
|
||||
|
||||
# Set the strategy context that HashPasswordChange expects
|
||||
change set_context(%{strategy_name: :password})
|
||||
|
||||
# Use the official Ash Authentication password change
|
||||
change AshAuthentication.Strategy.Password.HashPasswordChange
|
||||
end
|
||||
|
||||
read :get_by_subject do
|
||||
description "Get a user by the subject claim in a JWT"
|
||||
argument :subject, :string, allow_nil?: false
|
||||
|
|
@ -83,14 +96,14 @@ defmodule Mv.Accounts.User do
|
|||
argument :oauth_tokens, :map, allow_nil?: false
|
||||
prepare AshAuthentication.Strategy.OAuth2.SignInPreparation
|
||||
|
||||
filter expr(email == get_path(^arg(:user_info), [:email]))
|
||||
filter expr(email == get_path(^arg(:user_info), [:preferred_username]))
|
||||
end
|
||||
|
||||
create :register_with_rauthy do
|
||||
argument :user_info, :map, allow_nil?: false
|
||||
argument :oauth_tokens, :map, allow_nil?: false
|
||||
upsert? true
|
||||
upsert_identity :unique_email
|
||||
upsert_identity :unique_oidc_id
|
||||
|
||||
change AshAuthentication.GenerateTokenChange
|
||||
|
||||
|
|
@ -121,6 +134,14 @@ defmodule Mv.Accounts.User do
|
|||
identity :unique_oidc_id, [:oidc_id]
|
||||
end
|
||||
|
||||
# Global validations - applied to all relevant actions
|
||||
validations do
|
||||
# Password strength policy: minimum 8 characters for all password-related actions
|
||||
validate string_length(:password, min: 8) do
|
||||
where action_is([:register_with_password, :admin_set_password])
|
||||
end
|
||||
end
|
||||
|
||||
# You can customize this if you wish, but this is a safe default that
|
||||
# only allows user data to be interacted with via AshAuthentication.
|
||||
# policies do
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ defmodule MvWeb.Layouts do
|
|||
in regular views and live views.
|
||||
"""
|
||||
use MvWeb, :html
|
||||
use Gettext, backend: MvWeb.Gettext
|
||||
import MvWeb.Layouts.Navbar
|
||||
|
||||
embed_templates "layouts/*"
|
||||
|
||||
|
|
@ -19,7 +21,7 @@ defmodule MvWeb.Layouts do
|
|||
<Layouts.app flash={@flash}>
|
||||
<h1>Content</h1>
|
||||
</Layout.app>
|
||||
|
||||
|
||||
"""
|
||||
attr :flash, :map, required: true, doc: "the map of flash messages"
|
||||
|
||||
|
|
@ -31,44 +33,9 @@ defmodule MvWeb.Layouts do
|
|||
|
||||
def app(assigns) do
|
||||
~H"""
|
||||
<header class="navbar px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex-1">
|
||||
<a href="/" class="flex-1 flex w-fit items-center gap-2">
|
||||
<img src={~p"/images/logo.svg"} width="36" />
|
||||
<span class="text-sm font-semibold">v{Application.spec(:phoenix, :vsn)}</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex-none">
|
||||
<ul class="flex flex-column px-1 space-x-4 items-center">
|
||||
<li>
|
||||
<form method="post" action="/set_locale" class="mr-4">
|
||||
<input type="hidden" name="_csrf_token" value={Plug.CSRFProtection.get_csrf_token()} />
|
||||
<select name="locale" onchange="this.form.submit()" class="select select-sm">
|
||||
<option value="de" selected={Gettext.get_locale() == "de"}>Deutsch</option>
|
||||
<option value="en" selected={Gettext.get_locale() == "en"}>English</option>
|
||||
</select>
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://phoenixframework.org/" class="btn btn-ghost">Website</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/phoenixframework/phoenix" class="btn btn-ghost">GitHub</a>
|
||||
</li>
|
||||
<li>
|
||||
<.theme_toggle />
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://hexdocs.pm/phoenix/overview.html" class="btn btn-primary">
|
||||
Get Started <span aria-hidden="true">→</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="px-4 py-20 sm:px-6 lg:px-8">
|
||||
<div class="mx-auto max-w-2xl space-y-4">
|
||||
<.navbar />
|
||||
<main class="px-4 py-20 sm:px-6 lg:px-16">
|
||||
<div class="mx-auto max-full space-y-4">
|
||||
{render_slot(@inner_block)}
|
||||
</div>
|
||||
</main>
|
||||
|
|
@ -119,38 +86,4 @@ defmodule MvWeb.Layouts do
|
|||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Provides dark vs light theme toggle based on themes defined in app.css.
|
||||
|
||||
See <head> in root.html.heex which applies the theme before page load.
|
||||
"""
|
||||
def theme_toggle(assigns) do
|
||||
~H"""
|
||||
<div class="card relative flex flex-row items-center border-2 border-base-300 bg-base-300 rounded-full">
|
||||
<div class="absolute w-1/3 h-full rounded-full border-1 border-base-200 bg-base-100 brightness-200 left-0 [[data-theme=light]_&]:left-1/3 [[data-theme=dark]_&]:left-2/3 transition-[left]" />
|
||||
|
||||
<button
|
||||
phx-click={JS.dispatch("phx:set-theme", detail: %{theme: "system"})}
|
||||
class="flex p-2 cursor-pointer w-1/3"
|
||||
>
|
||||
<.icon name="hero-computer-desktop-micro" class="size-4 opacity-75 hover:opacity-100" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
phx-click={JS.dispatch("phx:set-theme", detail: %{theme: "light"})}
|
||||
class="flex p-2 cursor-pointer w-1/3"
|
||||
>
|
||||
<.icon name="hero-sun-micro" class="size-4 opacity-75 hover:opacity-100" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
phx-click={JS.dispatch("phx:set-theme", detail: %{theme: "dark"})}
|
||||
class="flex p-2 cursor-pointer w-1/3"
|
||||
>
|
||||
<.icon name="hero-moon-micro" class="size-4 opacity-75 hover:opacity-100" />
|
||||
</button>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
|
|
|||
80
lib/mv_web/components/layouts/navbar.ex
Normal file
80
lib/mv_web/components/layouts/navbar.ex
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
defmodule MvWeb.Layouts.Navbar do
|
||||
@moduledoc """
|
||||
Navbar that is used in the rootlayout shown on every page
|
||||
"""
|
||||
use Phoenix.Component
|
||||
use Gettext, backend: MvWeb.Gettext
|
||||
|
||||
def navbar(assigns) do
|
||||
~H"""
|
||||
<header class="navbar bg-base-100 shadow-sm">
|
||||
<div class="flex-1">
|
||||
<a class="btn btn-ghost text-xl">Mitgliederverwaltung</a>
|
||||
<ul class="menu menu-horizontal bg-base-200">
|
||||
<li><a href="/members">{gettext("Members")}</a></li>
|
||||
<li><a>Transaktionen</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<form method="post" action="/set_locale" class="mr-4">
|
||||
<input type="hidden" name="_csrf_token" value={Plug.CSRFProtection.get_csrf_token()} />
|
||||
<select name="locale" onchange="this.form.submit()" class="select select-sm">
|
||||
<option value="de" selected={Gettext.get_locale() == "de"}>Deutsch</option>
|
||||
<option value="en" selected={Gettext.get_locale() == "en"}>English</option>
|
||||
</select>
|
||||
</form>
|
||||
<!-- Daisy UI Theme Toggle for dark and light mode-->
|
||||
<label class="flex cursor-pointer gap-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<circle cx="12" cy="12" r="5" />
|
||||
<path d="M12 1v2M12 21v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M1 12h2M21 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4" />
|
||||
</svg>
|
||||
<input type="checkbox" value="dark" class="toggle theme-controller" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
|
||||
</svg>
|
||||
</label>
|
||||
<div class="dropdown dropdown-end">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost btn-circle avatar avatar-placeholder">
|
||||
<div class="bg-neutral text-neutral-content w-12 rounded-full">
|
||||
<span>AA</span>
|
||||
</div>
|
||||
</div>
|
||||
<ul
|
||||
tabindex="0"
|
||||
class="menu menu-sm dropdown-content bg-base-100 rounded-box z-1 mt-3 w-52 p-2 shadow"
|
||||
>
|
||||
<li>
|
||||
<a>
|
||||
{gettext("Profil")}
|
||||
</a>
|
||||
</li>
|
||||
<li><a>{gettext("Settings")}</a></li>
|
||||
<li><a href="sign-out">{gettext("Logout")}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
"""
|
||||
end
|
||||
end
|
||||
44
lib/mv_web/components/table_components.ex
Normal file
44
lib/mv_web/components/table_components.ex
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
defmodule MvWeb.TableComponents do
|
||||
@moduledoc """
|
||||
TableComponents that can be used in tables as components (like a button for sorting, a filter...)
|
||||
"""
|
||||
use Phoenix.Component
|
||||
import MvWeb.CoreComponents
|
||||
use Gettext, backend: MvWeb.Gettext
|
||||
|
||||
attr :field, :atom, required: true
|
||||
attr :label, :string, required: true
|
||||
attr :sort_field, :atom, default: nil
|
||||
attr :sort_order, :atom, default: nil
|
||||
|
||||
@doc """
|
||||
A sort button (with chevron icon) that can be used to sort a list of items
|
||||
"""
|
||||
def sort_button(assigns) do
|
||||
~H"""
|
||||
<button
|
||||
type="button"
|
||||
phx-click="sort"
|
||||
phx-value-field={@field}
|
||||
aria-sort={aria_sort(@sort_field, @sort_order, @field)}
|
||||
class="flex items-center gap-1 hover:underline focus:outline-none"
|
||||
>
|
||||
<span>{@label}</span>
|
||||
<%= if @sort_field == @field do %>
|
||||
<.icon name={if @sort_order == :asc, do: "hero-chevron-up", else: "hero-chevron-down"} />
|
||||
<span class="sr-only">
|
||||
({(@sort_order == :asc && gettext("ascending")) || gettext("descending")})
|
||||
</span>
|
||||
<% end %>
|
||||
</button>
|
||||
"""
|
||||
end
|
||||
|
||||
defp aria_sort(current_field, current_order, this_field) do
|
||||
cond do
|
||||
current_field != this_field -> "none"
|
||||
current_order == :asc -> "ascending"
|
||||
true -> "descending"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,58 +1,19 @@
|
|||
defmodule MvWeb.MemberLive.Index do
|
||||
use MvWeb, :live_view
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<Layouts.app flash={@flash}>
|
||||
<.header>
|
||||
{gettext("Listing Members")}
|
||||
<:actions>
|
||||
<.button variant="primary" navigate={~p"/members/new"}>
|
||||
<.icon name="hero-plus" /> {gettext("New Member")}
|
||||
</.button>
|
||||
</:actions>
|
||||
</.header>
|
||||
|
||||
<.table
|
||||
id="members"
|
||||
rows={@streams.members}
|
||||
row_click={fn {_id, member} -> JS.navigate(~p"/members/#{member}") end}
|
||||
>
|
||||
<!-- <:col :let={{_id, member}} label="Id">{member.id}</:col> -->
|
||||
<:col :let={{_id, member}} label={gettext("First Name")}>{member.first_name}</:col>
|
||||
<:col :let={{_id, member}} label={gettext("Last Name")}>{member.last_name}</:col>
|
||||
<:col :let={{_id, member}} label={gettext("Email")}>{member.email}</:col>
|
||||
<:col :let={{_id, member}} label={gettext("City")}>{member.city}</:col>
|
||||
<:col :let={{_id, member}} label={gettext("Join Date")}>{member.join_date}</:col>
|
||||
|
||||
<:action :let={{_id, member}}>
|
||||
<div class="sr-only">
|
||||
<.link navigate={~p"/members/#{member}"}>{gettext("Show")}</.link>
|
||||
</div>
|
||||
|
||||
<.link navigate={~p"/members/#{member}/edit"}>{gettext("Edit")}</.link>
|
||||
</:action>
|
||||
|
||||
<:action :let={{id, member}}>
|
||||
<.link
|
||||
phx-click={JS.push("delete", value: %{id: member.id}) |> hide("##{id}")}
|
||||
data-confirm={gettext("Are you sure?")}
|
||||
>
|
||||
{gettext("Delete")}
|
||||
</.link>
|
||||
</:action>
|
||||
</.table>
|
||||
</Layouts.app>
|
||||
"""
|
||||
end
|
||||
import MvWeb.TableComponents
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
members = Ash.read!(Mv.Membership.Member)
|
||||
sorted = Enum.sort_by(members, & &1.first_name)
|
||||
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(:page_title, gettext("Listing Members"))
|
||||
|> stream(:members, Ash.read!(Mv.Membership.Member))}
|
||||
|> assign(:page_title, gettext("Members"))
|
||||
|> assign(:sort_field, :first_name)
|
||||
|> assign(:sort_order, :asc)
|
||||
|> assign(:members, sorted)
|
||||
|> assign(:selected_members, [])}
|
||||
end
|
||||
|
||||
@impl true
|
||||
|
|
@ -62,4 +23,64 @@ defmodule MvWeb.MemberLive.Index do
|
|||
|
||||
{:noreply, stream_delete(socket, :members, member)}
|
||||
end
|
||||
|
||||
# Selects one member in the list of members
|
||||
@impl true
|
||||
def handle_event("select_member", %{"id" => id}, socket) do
|
||||
selected =
|
||||
if id in socket.assigns.selected_members do
|
||||
List.delete(socket.assigns.selected_members, id)
|
||||
else
|
||||
[id | socket.assigns.selected_members]
|
||||
end
|
||||
|
||||
{:noreply, assign(socket, :selected_members, selected)}
|
||||
end
|
||||
|
||||
# Sorts the list of members according to a field, when you click on the column header
|
||||
@impl true
|
||||
def handle_event("sort", %{"field" => field_str}, socket) do
|
||||
members = socket.assigns.members
|
||||
field = String.to_existing_atom(field_str)
|
||||
|
||||
new_order =
|
||||
if socket.assigns.sort_field == field do
|
||||
toggle_order(socket.assigns.sort_order)
|
||||
else
|
||||
:asc
|
||||
end
|
||||
|
||||
sorted_members =
|
||||
members
|
||||
|> Enum.sort_by(&Map.get(&1, field), sort_fun(new_order))
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:sort_field, field)
|
||||
|> assign(:sort_order, new_order)
|
||||
|> assign(:members, sorted_members)}
|
||||
end
|
||||
|
||||
# Selects all members in the list of members
|
||||
|
||||
@impl true
|
||||
def handle_event("select_all", _params, socket) do
|
||||
members = socket.assigns.members
|
||||
|
||||
all_ids = Enum.map(members, & &1.id)
|
||||
|
||||
selected =
|
||||
if Enum.sort(socket.assigns.selected_members) == Enum.sort(all_ids) do
|
||||
[]
|
||||
else
|
||||
all_ids
|
||||
end
|
||||
|
||||
{:noreply, assign(socket, :selected_members, selected)}
|
||||
end
|
||||
|
||||
defp toggle_order(:asc), do: :desc
|
||||
defp toggle_order(:desc), do: :asc
|
||||
defp sort_fun(:asc), do: &<=/2
|
||||
defp sort_fun(:desc), do: &>=/2
|
||||
end
|
||||
|
|
|
|||
83
lib/mv_web/live/member_live/index.html.heex
Normal file
83
lib/mv_web/live/member_live/index.html.heex
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
<Layouts.app flash={@flash}>
|
||||
<.header>
|
||||
{gettext("Members")}
|
||||
<:actions>
|
||||
<.button variant="primary" navigate={~p"/members/new"}>
|
||||
<.icon name="hero-plus" /> {gettext("New Member")}
|
||||
</.button>
|
||||
</:actions>
|
||||
</.header>
|
||||
|
||||
<.table
|
||||
id="members"
|
||||
rows={@members}
|
||||
row_click={fn member -> JS.navigate(~p"/members/#{member}") end}
|
||||
>
|
||||
|
||||
<!-- <:col :let={member} label="Id">{member.id}</:col> -->
|
||||
<:col
|
||||
:let={member}
|
||||
label={
|
||||
~H"""
|
||||
<.input
|
||||
type="checkbox"
|
||||
name="select_all"
|
||||
phx-click="select_all"
|
||||
checked={Enum.sort(@selected_members) == Enum.map(@members, & &1.id) |> Enum.sort()}
|
||||
aria-label={gettext("Select all members")}
|
||||
role="checkbox"
|
||||
/>
|
||||
"""
|
||||
}
|
||||
>
|
||||
<.input
|
||||
type="checkbox"
|
||||
name={member.id}
|
||||
phx-click="select_member"
|
||||
phx-value-id={member.id}
|
||||
checked={member.id in @selected_members}
|
||||
phx-capture-click
|
||||
phx-stop-propagation
|
||||
aria-label={gettext("Select member")}
|
||||
role="checkbox"
|
||||
/>
|
||||
</:col>
|
||||
<:col
|
||||
:let={member}
|
||||
label={
|
||||
sort_button(%{
|
||||
field: :first_name,
|
||||
label: gettext("Name"),
|
||||
sort_field: @sort_field,
|
||||
sort_order: @sort_order
|
||||
})
|
||||
}
|
||||
>
|
||||
{member.first_name} {member.last_name}
|
||||
</:col>
|
||||
<:col :let={member} label={gettext("Email")}>{member.email}</:col>
|
||||
<:col :let={member} label={gettext("Street")}>{member.street}</:col>
|
||||
<:col :let={member} label={gettext("House Number")}>{member.house_number}</:col>
|
||||
<:col :let={member} label={gettext("Postal Code")}>{member.postal_code}</:col>
|
||||
<:col :let={member} label={gettext("City")}>{member.city}</:col>
|
||||
<:col :let={member} label={gettext("Phone Number")}>{member.phone_number}</:col>
|
||||
<:col :let={member} label={gettext("Join Date")}>{member.join_date}</:col>
|
||||
|
||||
<:action :let={member}>
|
||||
<div class="sr-only">
|
||||
<.link navigate={~p"/members/#{member}"}>{gettext("Show")}</.link>
|
||||
</div>
|
||||
|
||||
<.link navigate={~p"/members/#{member}/edit"}>{gettext("Edit")}</.link>
|
||||
</:action>
|
||||
|
||||
<:action :let={member}>
|
||||
<.link
|
||||
phx-click={JS.push("delete", value: %{id: member.id}) |> hide("#row-#{member.id}")}
|
||||
data-confirm={gettext("Are you sure?")}
|
||||
>
|
||||
{gettext("Delete")}
|
||||
</.link>
|
||||
</:action>
|
||||
</.table>
|
||||
</Layouts.app>
|
||||
|
|
@ -13,23 +13,81 @@ defmodule MvWeb.UserLive.Form do
|
|||
<.form for={@form} id="user-form" phx-change="validate" phx-submit="save">
|
||||
<.input field={@form[:email]} label={gettext("Email")} required type="email" />
|
||||
|
||||
<%= if @user do %>
|
||||
<div class="mt-4 p-4 bg-blue-50 rounded-lg">
|
||||
<p class="text-sm text-blue-800">
|
||||
<strong>{gettext("Note")}:</strong> {gettext(
|
||||
"Password can only be changed through authentication functions."
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="mt-4 p-4 bg-yellow-50 rounded-lg">
|
||||
<p class="text-sm text-yellow-800">
|
||||
<strong>{gettext("Note")}:</strong> {gettext(
|
||||
"Users created here will need to set their password through the authentication system."
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<% end %>
|
||||
<!-- Password Section -->
|
||||
<div class="mt-6">
|
||||
<label class="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="set_password"
|
||||
phx-click="toggle_password_section"
|
||||
checked={@show_password_fields}
|
||||
class="checkbox checkbox-sm"
|
||||
/>
|
||||
<span class="text-sm font-medium">
|
||||
{if @user, do: gettext("Change Password"), else: gettext("Set Password")}
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<%= if @show_password_fields do %>
|
||||
<div class="mt-4 space-y-4 p-4 bg-gray-50 rounded-lg">
|
||||
<.input
|
||||
field={@form[:password]}
|
||||
label={gettext("Password")}
|
||||
type="password"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
|
||||
<!-- Only show password confirmation for new users (register_with_password) -->
|
||||
<%= if !@user do %>
|
||||
<.input
|
||||
field={@form[:password_confirmation]}
|
||||
label={gettext("Confirm Password")}
|
||||
type="password"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
<% end %>
|
||||
|
||||
<div class="text-sm text-gray-600">
|
||||
<p><strong>{gettext("Password requirements")}:</strong></p>
|
||||
<ul class="list-disc list-inside text-xs mt-1 space-y-1">
|
||||
<li>{gettext("At least 8 characters")}</li>
|
||||
<li>{gettext("Include both letters and numbers")}</li>
|
||||
<li>{gettext("Consider using special characters")}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<%= if @user do %>
|
||||
<div class="mt-3 p-3 bg-orange-50 border border-orange-200 rounded">
|
||||
<p class="text-sm text-orange-800">
|
||||
<strong>{gettext("Admin Note")}:</strong> {gettext(
|
||||
"As an administrator, you can directly set a new password for this user using the same secure Ash Authentication system."
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<%= if @user do %>
|
||||
<div class="mt-4 p-4 bg-blue-50 rounded-lg">
|
||||
<p class="text-sm text-blue-800">
|
||||
<strong>{gettext("Note")}:</strong> {gettext(
|
||||
"Check 'Change Password' above to set a new password for this user."
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="mt-4 p-4 bg-yellow-50 rounded-lg">
|
||||
<p class="text-sm text-yellow-800">
|
||||
<strong>{gettext("Note")}:</strong> {gettext(
|
||||
"User will be created without a password. Check 'Set Password' to add one."
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<.button phx-disable-with={gettext("Saving...")} variant="primary">
|
||||
{gettext("Save User")}
|
||||
|
|
@ -56,6 +114,7 @@ defmodule MvWeb.UserLive.Form do
|
|||
|> assign(:return_to, return_to(params["return_to"]))
|
||||
|> assign(user: user)
|
||||
|> assign(:page_title, page_title)
|
||||
|> assign(:show_password_fields, false)
|
||||
|> assign_form()}
|
||||
end
|
||||
|
||||
|
|
@ -63,6 +122,19 @@ defmodule MvWeb.UserLive.Form do
|
|||
defp return_to(_), do: "index"
|
||||
|
||||
@impl true
|
||||
def handle_event("toggle_password_section", _params, socket) do
|
||||
show_password_fields = !socket.assigns.show_password_fields
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:show_password_fields, show_password_fields)
|
||||
|> assign_form()
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
|
||||
|
||||
def handle_event("validate", %{"user" => user_params}, socket) do
|
||||
{:noreply, assign(socket, form: AshPhoenix.Form.validate(socket.assigns.form, user_params))}
|
||||
end
|
||||
|
|
@ -86,12 +158,16 @@ defmodule MvWeb.UserLive.Form do
|
|||
|
||||
defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
|
||||
|
||||
defp assign_form(%{assigns: %{user: user}} = socket) do
|
||||
defp assign_form(%{assigns: %{user: user, show_password_fields: show_password_fields}} = socket) do
|
||||
form =
|
||||
if user do
|
||||
AshPhoenix.Form.for_update(user, :update_user, domain: Mv.Accounts, as: "user")
|
||||
# For existing users, use admin password action if password fields are shown
|
||||
action = if show_password_fields, do: :admin_set_password, else: :update_user
|
||||
AshPhoenix.Form.for_update(user, action, domain: Mv.Accounts, as: "user")
|
||||
else
|
||||
AshPhoenix.Form.for_create(Mv.Accounts.User, :create_user,
|
||||
# For new users, use password registration if password fields are shown
|
||||
action = if show_password_fields, do: :register_with_password, else: :create_user
|
||||
AshPhoenix.Form.for_create(Mv.Accounts.User, action,
|
||||
domain: Mv.Accounts,
|
||||
as: "user"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,54 +1,19 @@
|
|||
defmodule MvWeb.UserLive.Index do
|
||||
use MvWeb, :live_view
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<Layouts.app flash={@flash}>
|
||||
<.header>
|
||||
{gettext("Listing Users")}
|
||||
<:actions>
|
||||
<.button variant="primary" navigate={~p"/users/new"}>
|
||||
<.icon name="hero-plus" /> {gettext("New User")}
|
||||
</.button>
|
||||
</:actions>
|
||||
</.header>
|
||||
|
||||
<.table
|
||||
id="users"
|
||||
rows={@streams.users}
|
||||
row_click={fn {_id, user} -> JS.navigate(~p"/users/#{user}") end}
|
||||
>
|
||||
<:col :let={{_id, user}} label={gettext("Email")}>{user.email}</:col>
|
||||
<:col :let={{_id, user}} label={gettext("OIDC ID")}>{user.oidc_id}</:col>
|
||||
|
||||
<:action :let={{_id, user}}>
|
||||
<div class="sr-only">
|
||||
<.link navigate={~p"/users/#{user}"}>{gettext("Show")}</.link>
|
||||
</div>
|
||||
|
||||
<.link navigate={~p"/users/#{user}/edit"}>{gettext("Edit")}</.link>
|
||||
</:action>
|
||||
|
||||
<:action :let={{id, user}}>
|
||||
<.link
|
||||
phx-click={JS.push("delete", value: %{id: user.id}) |> hide("##{id}")}
|
||||
data-confirm={gettext("Are you sure?")}
|
||||
>
|
||||
{gettext("Delete")}
|
||||
</.link>
|
||||
</:action>
|
||||
</.table>
|
||||
</Layouts.app>
|
||||
"""
|
||||
end
|
||||
import MvWeb.TableComponents
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
users = Ash.read!(Mv.Accounts.User, domain: Mv.Accounts)
|
||||
sorted = Enum.sort_by(users, & &1.email)
|
||||
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(:page_title, gettext("Listing Users"))
|
||||
|> stream(:users, Ash.read!(Mv.Accounts.User, domain: Mv.Accounts))}
|
||||
|> assign(:sort_field, :email)
|
||||
|> assign(:sort_order, :asc)
|
||||
|> assign(:users, sorted)
|
||||
|> assign(:selected_users, [])}
|
||||
end
|
||||
|
||||
@impl true
|
||||
|
|
@ -56,6 +21,66 @@ defmodule MvWeb.UserLive.Index do
|
|||
user = Ash.get!(Mv.Accounts.User, id, domain: Mv.Accounts)
|
||||
Ash.destroy!(user, domain: Mv.Accounts)
|
||||
|
||||
{:noreply, stream_delete(socket, :users, user)}
|
||||
updated_users = Enum.reject(socket.assigns.users, &(&1.id == id))
|
||||
{:noreply, assign(socket, :users, updated_users)}
|
||||
end
|
||||
|
||||
# Selects one user in the list of users
|
||||
@impl true
|
||||
def handle_event("select_user", %{"id" => id}, socket) do
|
||||
selected =
|
||||
if id in socket.assigns.selected_users do
|
||||
List.delete(socket.assigns.selected_users, id)
|
||||
else
|
||||
[id | socket.assigns.selected_users]
|
||||
end
|
||||
|
||||
{:noreply, assign(socket, :selected_users, selected)}
|
||||
end
|
||||
|
||||
# Sorts the list of users according to a field, when you click on the column header
|
||||
@impl true
|
||||
def handle_event("sort", %{"field" => field_str}, socket) do
|
||||
users = socket.assigns.users
|
||||
field = String.to_existing_atom(field_str)
|
||||
|
||||
new_order =
|
||||
if socket.assigns.sort_field == field do
|
||||
toggle_order(socket.assigns.sort_order)
|
||||
else
|
||||
:asc
|
||||
end
|
||||
|
||||
sorted_users =
|
||||
users
|
||||
|> Enum.sort_by(&Map.get(&1, field), sort_fun(new_order))
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:sort_field, field)
|
||||
|> assign(:sort_order, new_order)
|
||||
|> assign(:users, sorted_users)}
|
||||
end
|
||||
|
||||
# Selects all users in the list of users
|
||||
@impl true
|
||||
def handle_event("select_all", _params, socket) do
|
||||
users = socket.assigns.users
|
||||
|
||||
all_ids = Enum.map(users, & &1.id)
|
||||
|
||||
selected =
|
||||
if Enum.sort(socket.assigns.selected_users) == Enum.sort(all_ids) do
|
||||
[]
|
||||
else
|
||||
all_ids
|
||||
end
|
||||
|
||||
{:noreply, assign(socket, :selected_users, selected)}
|
||||
end
|
||||
|
||||
defp toggle_order(:asc), do: :desc
|
||||
defp toggle_order(:desc), do: :asc
|
||||
defp sort_fun(:asc), do: &<=/2
|
||||
defp sort_fun(:desc), do: &>=/2
|
||||
end
|
||||
|
|
|
|||
75
lib/mv_web/live/user_live/index.html.heex
Normal file
75
lib/mv_web/live/user_live/index.html.heex
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
<Layouts.app flash={@flash}>
|
||||
<.header>
|
||||
{gettext("Listing Users")}
|
||||
<:actions>
|
||||
<.button variant="primary" navigate={~p"/users/new"}>
|
||||
<.icon name="hero-plus" /> {gettext("New User")}
|
||||
</.button>
|
||||
</:actions>
|
||||
</.header>
|
||||
|
||||
<.table
|
||||
id="users"
|
||||
rows={@users}
|
||||
row_click={fn user -> JS.navigate(~p"/users/#{user}") end}
|
||||
>
|
||||
<:col
|
||||
:let={user}
|
||||
label={
|
||||
~H"""
|
||||
<.input
|
||||
type="checkbox"
|
||||
name="select_all"
|
||||
phx-click="select_all"
|
||||
checked={Enum.sort(@selected_users) == Enum.map(@users, & &1.id) |> Enum.sort()}
|
||||
aria-label={gettext("Select all users")}
|
||||
role="checkbox"
|
||||
/>
|
||||
"""
|
||||
}
|
||||
>
|
||||
<.input
|
||||
type="checkbox"
|
||||
name={user.id}
|
||||
phx-click="select_user"
|
||||
phx-value-id={user.id}
|
||||
checked={user.id in @selected_users}
|
||||
phx-capture-click
|
||||
phx-stop-propagation
|
||||
aria-label={gettext("Select user")}
|
||||
role="checkbox"
|
||||
/>
|
||||
</:col>
|
||||
<:col
|
||||
:let={user}
|
||||
label={
|
||||
sort_button(%{
|
||||
field: :email,
|
||||
label: gettext("Email"),
|
||||
sort_field: @sort_field,
|
||||
sort_order: @sort_order
|
||||
})
|
||||
}
|
||||
>
|
||||
{user.email}
|
||||
</:col>
|
||||
<:col :let={user} label={gettext("OIDC ID")}>{user.oidc_id}</:col>
|
||||
|
||||
<:action :let={user}>
|
||||
<div class="sr-only">
|
||||
<.link navigate={~p"/users/#{user}"}>{gettext("Show")}</.link>
|
||||
</div>
|
||||
|
||||
<.link navigate={~p"/users/#{user}/edit"}>{gettext("Edit")}</.link>
|
||||
</:action>
|
||||
|
||||
<:action :let={user}>
|
||||
<.link
|
||||
phx-click={JS.push("delete", value: %{id: user.id}) |> hide("#row-#{user.id}")}
|
||||
data-confirm={gettext("Are you sure?")}
|
||||
>
|
||||
{gettext("Delete")}
|
||||
</.link>
|
||||
</:action>
|
||||
</.table>
|
||||
</Layouts.app>
|
||||
14
mix.lock
14
mix.lock
|
|
@ -1,11 +1,11 @@
|
|||
%{
|
||||
"ash": {:hex, :ash, "3.5.27", "bfa227b75da2b447d1b98e16a19b2fa957fe32bef33dfe33aa93b861a532c641", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.65 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f75694b0012e4e56293e1beef1d6c20a52f2f2c9baebfa5c8f2426d25d43608e"},
|
||||
"ash": {:hex, :ash, "3.5.31", "fea1abcbb58d00d1edf65ac5bccba5d679ca80754aaac6af7877cbf9056d4462", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.65 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a7e07c8ae297dd764d92dd3c478e8bbb825fc0dd14a5cce83b85d19235510a74"},
|
||||
"ash_admin": {:hex, :ash_admin, "0.13.12", "5d4bb5e64b1aca7ae0086411a3cafc3afba6d42699944408ef1075b32fc244af", [:mix], [{:ash, ">= 3.4.63 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_phoenix, ">= 2.1.8 and < 3.0.0-0", [hex: :ash_phoenix, repo: "hexpm", optional: false]}, {:gettext, "~> 0.26", [hex: :gettext, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.1-rc", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}], "hexpm", "821c94ba7066948327dc3d9f2a1a32a9ffd42db95b13ad3b650f194157591503"},
|
||||
"ash_authentication": {:hex, :ash_authentication, "4.9.7", "1e7cbd21597c34cc37bd1f6a81b8a4cde15c9c555fbff2457887e3484433d399", [:mix], [{:argon2_elixir, "~> 4.0", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:ash, ">= 3.4.29 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_postgres, ">= 2.6.8 and < 3.0.0-0", [hex: :ash_postgres, repo: "hexpm", optional: true]}, {:assent, "~> 0.2.13", [hex: :assent, repo: "hexpm", optional: false]}, {:bcrypt_elixir, "~> 3.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: false]}, {:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:finch, "~> 0.19", [hex: :finch, repo: "hexpm", optional: false]}, {:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:joken, "~> 2.5", [hex: :joken, repo: "hexpm", optional: false]}, {:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}], "hexpm", "26483e61a846d17a68f4aa0bc686fb3bcb6f98955978121b4baa610f2701268d"},
|
||||
"ash_authentication_phoenix": {:hex, :ash_authentication_phoenix, "2.10.3", "b3c32e51a77eefc02c155eccdd17f1b697da3314fb40102854dcdd79288325b7", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_authentication, ">= 4.9.1 and < 5.0.0-0", [hex: :ash_authentication, repo: "hexpm", optional: false]}, {:ash_phoenix, "~> 2.0", [hex: :ash_phoenix, repo: "hexpm", optional: false]}, {:bcrypt_elixir, "~> 3.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: false]}, {:gettext, "~> 0.26", [hex: :gettext, repo: "hexpm", optional: true]}, {:igniter, ">= 0.5.25 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_html_helpers, "~> 1.0", [hex: :phoenix_html_helpers, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:slugify, "~> 1.3", [hex: :slugify, repo: "hexpm", optional: false]}], "hexpm", "89be0de638123193933a54ae15b9d1c670bb4010775c38b2b22a99180ecc1ac3"},
|
||||
"ash_authentication": {:hex, :ash_authentication, "4.9.8", "2ad462c9cf078eed122d6df433c2bb142a55702aa2c9cc702dd8fdc94edcdbb2", [:mix], [{:argon2_elixir, "~> 4.0", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:ash, ">= 3.4.29 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_postgres, ">= 2.6.8 and < 3.0.0-0", [hex: :ash_postgres, repo: "hexpm", optional: true]}, {:assent, "~> 0.2.13", [hex: :assent, repo: "hexpm", optional: false]}, {:bcrypt_elixir, "~> 3.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: false]}, {:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:finch, "~> 0.19", [hex: :finch, repo: "hexpm", optional: false]}, {:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:joken, "~> 2.5", [hex: :joken, repo: "hexpm", optional: false]}, {:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}], "hexpm", "c306d679c46b4a6207fd0e0f3f87e1d39e67e75524bdbb71398c083390be9814"},
|
||||
"ash_authentication_phoenix": {:hex, :ash_authentication_phoenix, "2.10.4", "b5a8e852dd48d875fe3089c28765379d112efed8bc1a5379f47e184d50259b73", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_authentication, ">= 4.9.1 and < 5.0.0-0", [hex: :ash_authentication, repo: "hexpm", optional: false]}, {:ash_phoenix, "~> 2.0", [hex: :ash_phoenix, repo: "hexpm", optional: false]}, {:bcrypt_elixir, "~> 3.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: false]}, {:gettext, "~> 0.26", [hex: :gettext, repo: "hexpm", optional: true]}, {:igniter, ">= 0.5.25 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_html_helpers, "~> 1.0", [hex: :phoenix_html_helpers, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:slugify, "~> 1.3", [hex: :slugify, repo: "hexpm", optional: false]}], "hexpm", "99e8ebc0606dc3ff81aac649c2838bf0d5d1d4bd880eb977cf31d2494b7a3f6a"},
|
||||
"ash_phoenix": {:hex, :ash_phoenix, "2.3.11", "3003cb7fbda4eba829a67a064af15bc23f4032b86c768c3336e3a04218d19f37", [:mix], [{:ash, ">= 3.5.13 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:igniter, "~> 0.6", [hex: :igniter, repo: "hexpm", optional: true]}, {:inertia, "~> 2.3", [hex: :inertia, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.5.6 or ~> 1.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.20.3 or ~> 1.0-rc.1", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}], "hexpm", "33294de690af8c408759c629fd4212e1b4639b1da8417f5c7b31cb3898e6cb4f"},
|
||||
"ash_postgres": {:hex, :ash_postgres, "2.6.11", "7c6b4fa9b8725c6644dd863323f8a2dae93f5df8ddae4b53df5dabde451a8b0c", [:mix], [{:ash, ">= 3.5.13 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_sql, ">= 0.2.72 and < 1.0.0-0", [hex: :ash_sql, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.13", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.14 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "d915f06406b46130559481b8b4290fd3bf60b0bd57bf5a4903cde0613b642c36"},
|
||||
"ash_sql": {:hex, :ash_sql, "0.2.86", "9b4013010981352e295eed22dc6b09998e23a724baca07f1ff617b6bec4ba8d4", [:mix], [{:ash, ">= 3.5.25 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "97fa13e87d55194f7746c5a9fa97a9693bc26cdf7825d1c6d7cdfe5f166285a4"},
|
||||
"ash_sql": {:hex, :ash_sql, "0.2.87", "17197c643918cdaee657946a1998860402dcf53a980f7665bb81d1fa53c224e7", [:mix], [{:ash, ">= 3.5.25 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "f82d6bf78f08bd9040af3adc28676965421598c88866074d8b1ccca65978d774"},
|
||||
"assent": {:hex, :assent, "0.2.13", "11226365d2d8661d23e9a2cf94d3255e81054ff9d88ac877f28bfdf38fa4ef31", [:mix], [{:certifi, ">= 0.0.0", [hex: :certifi, repo: "hexpm", optional: true]}, {:finch, "~> 0.15", [hex: :finch, repo: "hexpm", optional: true]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: true]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: true]}, {:ssl_verify_fun, ">= 0.0.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: true]}], "hexpm", "bf9f351b01dd6bceea1d1f157f05438f6765ce606e6eb8d29296003d29bf6eab"},
|
||||
"bandit": {:hex, :bandit, "1.7.0", "d1564f30553c97d3e25f9623144bb8df11f3787a26733f00b21699a128105c0c", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "3e2f7a98c7a11f48d9d8c037f7177cd39778e74d55c7af06fe6227c742a8168a"},
|
||||
"bcrypt_elixir": {:hex, :bcrypt_elixir, "3.3.2", "d50091e3c9492d73e17fc1e1619a9b09d6a5ef99160eb4d736926fd475a16ca3", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "471be5151874ae7931911057d1467d908955f93554f7a6cd1b7d804cac8cef53"},
|
||||
|
|
@ -24,18 +24,16 @@
|
|||
"elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"},
|
||||
"esbuild": {:hex, :esbuild, "0.10.0", "b0aa3388a1c23e727c5a3e7427c932d89ee791746b0081bbe56103e9ef3d291f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "468489cda427b974a7cc9f03ace55368a83e1a7be12fba7e30969af78e5f8c70"},
|
||||
"ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"},
|
||||
"ex_phone_number": {:hex, :ex_phone_number, "0.4.5", "2065cc48c3e9d1ed9821f50877c32f2f6898362cb990f44147ca217c5d1374ed", [:mix], [{:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: false]}], "hexpm", "67163f8706f8cbfef1b1f4b9230c461f19786d0d79fd0b22cbeeefc6f0b99d4a"},
|
||||
"ex_phone_number": {:hex, :ex_phone_number, "0.4.8", "c1c5e6f0673822a2a7b439b43af7d3eb1a5c19ae582b772b8b8d12625dd51ec1", [:mix], [{:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: false]}], "hexpm", "43e2357c6b8cfe556bcd417f4ce9aaef267a786e31a2938902daaa0d36f69757"},
|
||||
"expo": {:hex, :expo, "1.1.0", "f7b9ed7fb5745ebe1eeedf3d6f29226c5dd52897ac67c0f8af62a07e661e5c75", [:mix], [], "hexpm", "fbadf93f4700fb44c331362177bdca9eeb8097e8b0ef525c9cc501cb9917c960"},
|
||||
"file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"},
|
||||
"finch": {:hex, :finch, "0.20.0", "5330aefb6b010f424dcbbc4615d914e9e3deae40095e73ab0c1bb0968933cadf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2658131a74d051aabfcba936093c903b8e89da9a1b63e430bee62045fa9b2ee2"},
|
||||
"fine": {:hex, :fine, "0.1.1", "df2ce44e438bed0061627e10c470873c69374ee7390a51bc612c2358ad37d556", [:mix], [], "hexpm", "41335526b82cf2c196d2588cd54d4504480e2e6ead24f2c07ae0c1cf40af61e5"},
|
||||
"floki": {:hex, :floki, "0.38.0", "62b642386fa3f2f90713f6e231da0fa3256e41ef1089f83b6ceac7a3fd3abf33", [:mix], [], "hexpm", "a5943ee91e93fb2d635b612caf5508e36d37548e84928463ef9dd986f0d1abd9"},
|
||||
"gettext": {:hex, :gettext, "0.26.2", "5978aa7b21fada6deabf1f6341ddba50bc69c999e812211903b169799208f2a8", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "aa978504bcf76511efdc22d580ba08e2279caab1066b76bb9aa81c4a1e0a32a5"},
|
||||
"glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"},
|
||||
"heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "0435d4ca364a608cc75e2f8683d374e55abbae26", [tag: "v2.2.0", sparse: "optimized", depth: 1]},
|
||||
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
|
||||
"igniter": {:hex, :igniter, "0.6.19", "d87703b36890bc4278341d966a7ed8e10604a18610a4331ac10c75d1af48fff4", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "c2070b3fdbd238fc0a0bfbc1f125b5c0f79a1fe2f5b3c7b43cd33de696783663"},
|
||||
"inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"},
|
||||
"igniter": {:hex, :igniter, "0.6.22", "b170fc64ae0cae54a7713cf3f96e7c96183f81d75f31746de080b27518b0f96e", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "d753129f693a214da32f39ba5b335f7ea6e1e87833e647b280f395b3c3742acf"},
|
||||
"iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"},
|
||||
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
|
||||
"joken": {:hex, :joken, "2.6.2", "5daaf82259ca603af4f0b065475099ada1b2b849ff140ccd37f4b6828ca6892a", [:mix], [{:jose, "~> 1.11.10", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5134b5b0a6e37494e46dbf9e4dad53808e5e787904b7c73972651b51cce3d72b"},
|
||||
|
|
|
|||
|
|
@ -13,36 +13,36 @@ msgstr ""
|
|||
#: lib/mv_web/components/core_components.ex:339
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Actions"
|
||||
msgstr ""
|
||||
msgstr "Aktionen"
|
||||
|
||||
#: lib/mv_web/live/member_live/index.ex:40
|
||||
#: lib/mv_web/live/user_live/index.ex:36
|
||||
#: lib/mv_web/live/member_live/index.html.heex:77
|
||||
#: lib/mv_web/live/user_live/index.html.heex:69
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Are you sure?"
|
||||
msgstr "Bist du sicher?"
|
||||
|
||||
#: lib/mv_web/components/layouts.ex:104
|
||||
#: lib/mv_web/components/layouts.ex:116
|
||||
#: lib/mv_web/components/layouts.ex:71
|
||||
#: lib/mv_web/components/layouts.ex:83
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Attempting to reconnect"
|
||||
msgstr "Verbindung wird wiederhergestellt"
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:25
|
||||
#: lib/mv_web/live/member_live/index.ex:26
|
||||
#: lib/mv_web/live/member_live/index.html.heex:62
|
||||
#: lib/mv_web/live/member_live/show.ex:36
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "City"
|
||||
msgstr "Stadt"
|
||||
|
||||
#: lib/mv_web/live/member_live/index.ex:42
|
||||
#: lib/mv_web/live/user_live/index.ex:38
|
||||
#: lib/mv_web/live/member_live/index.html.heex:79
|
||||
#: lib/mv_web/live/user_live/index.html.heex:71
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Delete"
|
||||
msgstr "Löschen"
|
||||
|
||||
#: lib/mv_web/live/member_live/index.ex:34
|
||||
#: lib/mv_web/live/user_live/form.ex:45
|
||||
#: lib/mv_web/live/user_live/index.ex:30
|
||||
#: lib/mv_web/live/member_live/index.html.heex:71
|
||||
#: lib/mv_web/live/user_live/form.ex:109
|
||||
#: lib/mv_web/live/user_live/index.html.heex:63
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Edit"
|
||||
msgstr "Bearbeite"
|
||||
|
|
@ -54,59 +54,51 @@ msgid "Edit Member"
|
|||
msgstr "Mitglied bearbeiten"
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:18
|
||||
#: lib/mv_web/live/member_live/index.ex:25
|
||||
#: lib/mv_web/live/member_live/index.html.heex:58
|
||||
#: lib/mv_web/live/member_live/show.ex:27
|
||||
#: lib/mv_web/live/user_live/form.ex:14
|
||||
#: lib/mv_web/live/user_live/index.ex:22
|
||||
#: lib/mv_web/live/user_live/index.html.heex:48
|
||||
#: lib/mv_web/live/user_live/show.ex:24
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Email"
|
||||
msgstr "E-Mail"
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:16
|
||||
#: lib/mv_web/live/member_live/index.ex:23
|
||||
#: lib/mv_web/live/member_live/show.ex:25
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "First Name"
|
||||
msgstr "Vorname"
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:22
|
||||
#: lib/mv_web/live/member_live/index.ex:27
|
||||
#: lib/mv_web/live/member_live/index.html.heex:64
|
||||
#: lib/mv_web/live/member_live/show.ex:33
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Join Date"
|
||||
msgstr "Beitrittsdatum"
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:17
|
||||
#: lib/mv_web/live/member_live/index.ex:24
|
||||
#: lib/mv_web/live/member_live/show.ex:26
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Last Name"
|
||||
msgstr "Nachname"
|
||||
|
||||
#: lib/mv_web/live/member_live/index.ex:9
|
||||
#: lib/mv_web/live/member_live/index.ex:54
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Listing Members"
|
||||
msgstr "Mitglieder"
|
||||
|
||||
#: lib/mv_web/live/member_live/index.ex:12
|
||||
#: lib/mv_web/live/member_live/index.html.heex:6
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "New Member"
|
||||
msgstr "Neues Mitglied"
|
||||
|
||||
#: lib/mv_web/live/member_live/index.ex:31
|
||||
#: lib/mv_web/live/user_live/index.ex:27
|
||||
#: lib/mv_web/live/member_live/index.html.heex:68
|
||||
#: lib/mv_web/live/user_live/index.html.heex:60
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Show"
|
||||
msgstr "Anzeigen"
|
||||
|
||||
#: lib/mv_web/components/layouts.ex:111
|
||||
#: lib/mv_web/components/layouts.ex:78
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Something went wrong!"
|
||||
msgstr "Etwas ist schiefgelaufen!"
|
||||
|
||||
#: lib/mv_web/components/layouts.ex:99
|
||||
#: lib/mv_web/components/layouts.ex:66
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "We can't find the internet"
|
||||
msgstr "Keine Internetverbindung gefunden"
|
||||
|
|
@ -135,6 +127,7 @@ msgid "Exit Date"
|
|||
msgstr "Austrittsdatum"
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:27
|
||||
#: lib/mv_web/live/member_live/index.html.heex:60
|
||||
#: lib/mv_web/live/member_live/show.ex:38
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "House Number"
|
||||
|
|
@ -153,12 +146,14 @@ msgid "Paid"
|
|||
msgstr "Bezahlt"
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:21
|
||||
#: lib/mv_web/live/member_live/index.html.heex:63
|
||||
#: lib/mv_web/live/member_live/show.ex:32
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Phone Number"
|
||||
msgstr "Telefonnummer"
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:28
|
||||
#: lib/mv_web/live/member_live/index.html.heex:61
|
||||
#: lib/mv_web/live/member_live/show.ex:39
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Postal Code"
|
||||
|
|
@ -172,12 +167,13 @@ msgstr "Mitglied speichern"
|
|||
#: lib/mv_web/live/member_live/form.ex:49
|
||||
#: lib/mv_web/live/property_live/form.ex:41
|
||||
#: lib/mv_web/live/property_type_live/form.ex:29
|
||||
#: lib/mv_web/live/user_live/form.ex:30
|
||||
#: lib/mv_web/live/user_live/form.ex:92
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Saving..."
|
||||
msgstr "Speichern..."
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:26
|
||||
#: lib/mv_web/live/member_live/index.html.heex:59
|
||||
#: lib/mv_web/live/member_live/show.ex:37
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Street"
|
||||
|
|
@ -230,7 +226,7 @@ msgstr "aktualisiert"
|
|||
#: lib/mv_web/controllers/auth_controller.ex:43
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Incorrect email or password"
|
||||
msgstr ""
|
||||
msgstr "Falsche E-Mail oder Passwort"
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:115
|
||||
#, elixir-autogen, elixir-format
|
||||
|
|
@ -240,50 +236,50 @@ msgstr "Mitglied %{action} erfolgreich"
|
|||
#: lib/mv_web/controllers/auth_controller.ex:14
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "You are now signed in"
|
||||
msgstr ""
|
||||
msgstr "Sie sind jetzt angemeldet"
|
||||
|
||||
#: lib/mv_web/controllers/auth_controller.ex:56
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "You are now signed out"
|
||||
msgstr ""
|
||||
msgstr "Sie sind jetzt abgemeldet"
|
||||
|
||||
#: lib/mv_web/controllers/auth_controller.ex:37
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "You have already signed in another way, but have not confirmed your account.\nYou can confirm your account using the link we sent to you, or by resetting your password.\n"
|
||||
msgstr ""
|
||||
msgstr "Sie haben sich bereits auf andere Weise angemeldet, aber Ihr Konto noch nicht bestätigt.\nSie können Ihr Konto über den Link bestätigen, den wir Ihnen gesendet haben, oder durch Zurücksetzen Ihres Passworts.\n"
|
||||
|
||||
#: lib/mv_web/controllers/auth_controller.ex:12
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Your email address has now been confirmed"
|
||||
msgstr ""
|
||||
msgstr "Ihre E-Mail-Adresse wurde bestätigt"
|
||||
|
||||
#: lib/mv_web/controllers/auth_controller.ex:13
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Your password has successfully been reset"
|
||||
msgstr ""
|
||||
msgstr "Ihr Passwort wurde erfolgreich zurückgesetzt"
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:52
|
||||
#: lib/mv_web/live/property_live/form.ex:44
|
||||
#: lib/mv_web/live/property_type_live/form.ex:32
|
||||
#: lib/mv_web/live/user_live/form.ex:31
|
||||
#: lib/mv_web/live/user_live/form.ex:95
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
msgstr "Abbrechen"
|
||||
|
||||
#: lib/mv_web/live/property_live/form.ex:29
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Choose a member"
|
||||
msgstr ""
|
||||
msgstr "Mitglied auswählen"
|
||||
|
||||
#: lib/mv_web/live/property_live/form.ex:20
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Choose a property type"
|
||||
msgstr ""
|
||||
msgstr "Eigenschaftstyp auswählen"
|
||||
|
||||
#: lib/mv_web/live/property_type_live/form.ex:25
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Description"
|
||||
msgstr ""
|
||||
msgstr "Beschreibung"
|
||||
|
||||
#: lib/mv_web/live/user_live/show.ex:17
|
||||
#, elixir-autogen, elixir-format
|
||||
|
|
@ -303,10 +299,15 @@ msgstr "ID"
|
|||
#: lib/mv_web/live/property_type_live/form.ex:26
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Immutable"
|
||||
msgstr ""
|
||||
msgstr "Unveränderlich"
|
||||
|
||||
#: lib/mv_web/live/user_live/index.ex:9
|
||||
#: lib/mv_web/live/user_live/index.ex:50
|
||||
#: lib/mv_web/components/layouts/navbar.ex:73
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Logout"
|
||||
msgstr "Abmelden"
|
||||
|
||||
#: lib/mv_web/live/user_live/index.ex:12
|
||||
#: lib/mv_web/live/user_live/index.html.heex:3
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Listing Users"
|
||||
msgstr "Benutzer auflisten"
|
||||
|
|
@ -314,14 +315,22 @@ msgstr "Benutzer auflisten"
|
|||
#: lib/mv_web/live/property_live/form.ex:27
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Member"
|
||||
msgstr ""
|
||||
msgstr "Mitglied"
|
||||
|
||||
#: lib/mv_web/components/layouts/navbar.ex:14
|
||||
#: lib/mv_web/live/member_live/index.ex:12
|
||||
#: lib/mv_web/live/member_live/index.html.heex:3
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Members"
|
||||
msgstr "Mitglieder"
|
||||
|
||||
#: lib/mv_web/live/member_live/index.html.heex:50
|
||||
#: lib/mv_web/live/property_type_live/form.ex:16
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
msgstr "Name"
|
||||
|
||||
#: lib/mv_web/live/user_live/index.ex:12
|
||||
#: lib/mv_web/live/user_live/index.html.heex:6
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "New User"
|
||||
msgstr "Neuer Benutzer"
|
||||
|
|
@ -336,13 +345,13 @@ msgstr "Nicht aktiviert"
|
|||
msgid "Not set"
|
||||
msgstr "Nicht gesetzt"
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:19
|
||||
#: lib/mv_web/live/user_live/form.ex:25
|
||||
#: lib/mv_web/live/user_live/form.ex:75
|
||||
#: lib/mv_web/live/user_live/form.ex:83
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Note"
|
||||
msgstr "Hinweis"
|
||||
|
||||
#: lib/mv_web/live/user_live/index.ex:23
|
||||
#: lib/mv_web/live/user_live/index.html.heex:56
|
||||
#: lib/mv_web/live/user_live/show.ex:25
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "OIDC ID"
|
||||
|
|
@ -353,15 +362,15 @@ msgstr "OIDC ID"
|
|||
msgid "Password Authentication"
|
||||
msgstr "Passwort-Authentifizierung"
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:19
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Password can only be changed through authentication functions."
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/property_live/form.ex:37
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Please select a property type first"
|
||||
msgstr ""
|
||||
msgstr "Bitte wählen Sie zuerst einen Eigenschaftstyp"
|
||||
|
||||
#: lib/mv_web/components/layouts/navbar.ex:69
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Profil"
|
||||
msgstr "Profil"
|
||||
|
||||
#: lib/mv_web/live/property_live/form.ex:207
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
|
|
@ -371,29 +380,44 @@ msgstr "Mitglied %{action} erfolgreich"
|
|||
#: lib/mv_web/live/property_live/form.ex:18
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Property type"
|
||||
msgstr ""
|
||||
msgstr "Eigenschaftstyp"
|
||||
|
||||
#: lib/mv_web/live/property_type_live/form.ex:80
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Property type %{action} successfully"
|
||||
msgstr ""
|
||||
msgstr "Eigenschaftstyp %{action} erfolgreich"
|
||||
|
||||
#: lib/mv_web/live/property_type_live/form.ex:27
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Required"
|
||||
msgstr ""
|
||||
msgstr "Erforderlich"
|
||||
|
||||
#: lib/mv_web/live/property_live/form.ex:42
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Save Property"
|
||||
msgstr ""
|
||||
msgstr "Eigenschaft speichern"
|
||||
|
||||
#: lib/mv_web/live/property_type_live/form.ex:30
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Save Property type"
|
||||
msgstr ""
|
||||
msgstr "Eigenschaftstyp speichern"
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:30
|
||||
#: lib/mv_web/live/member_live/index.html.heex:27
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Select all members"
|
||||
msgstr "Alle Mitglieder auswählen"
|
||||
|
||||
#: lib/mv_web/live/member_live/index.html.heex:41
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Select member"
|
||||
msgstr "Mitglied auswählen"
|
||||
|
||||
#: lib/mv_web/components/layouts/navbar.ex:72
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Settings"
|
||||
msgstr "Einstellungen"
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:93
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Save User"
|
||||
msgstr "Benutzer speichern"
|
||||
|
|
@ -411,7 +435,7 @@ msgstr "Dies ist ein Benutzer-Datensatz aus Ihrer Datenbank."
|
|||
#: lib/mv_web/live/property_live/form.ex:95
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Unsupported value type: %{type}"
|
||||
msgstr ""
|
||||
msgstr "Nicht unterstützter Wertetyp: %{type}"
|
||||
|
||||
#: lib/mv_web/live/property_live/form.ex:10
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
|
|
@ -428,7 +452,7 @@ msgstr "Dieses Formular dient zur Verwaltung von Mitgliedern und deren Eigenscha
|
|||
msgid "Use this form to manage user records in your database."
|
||||
msgstr "Verwenden Sie dieses Formular, um Benutzer-Datensätze zu verwalten."
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:46
|
||||
#: lib/mv_web/live/user_live/form.ex:110
|
||||
#: lib/mv_web/live/user_live/show.ex:9
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "User"
|
||||
|
|
@ -437,19 +461,94 @@ msgstr "Benutzer"
|
|||
#: lib/mv_web/live/property_live/form.ex:59
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Value"
|
||||
msgstr ""
|
||||
msgstr "Wert"
|
||||
|
||||
#: lib/mv_web/live/property_type_live/form.ex:20
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Value type"
|
||||
msgstr ""
|
||||
msgstr "Wertetyp"
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:45
|
||||
#: lib/mv_web/components/table_components.ex:30
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "ascending"
|
||||
msgstr "aufsteigend"
|
||||
|
||||
#: lib/mv_web/components/table_components.ex:30
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "descending"
|
||||
msgstr "absteigend"
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:109
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "New"
|
||||
msgstr "Neuer"
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:25
|
||||
#: lib/mv_web/live/user_live/form.ex:64
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Users created here will need to set their password through the authentication system."
|
||||
msgstr "Hier erstellte Benutzer müssen ihr Passwort über das Authentifizierungssystem setzen."
|
||||
msgid "Admin Note"
|
||||
msgstr "Administrator-Hinweis"
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:64
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "As an administrator, you can directly set a new password for this user using the same secure Ash Authentication system."
|
||||
msgstr "Als Administrator können Sie direkt ein neues Passwort für diesen Benutzer setzen, wobei das gleiche sichere Ash Authentication System verwendet wird."
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:55
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "At least 8 characters"
|
||||
msgstr "Mindestens 8 Zeichen"
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:27
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Change Password"
|
||||
msgstr "Passwort ändern"
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:75
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Check 'Change Password' above to set a new password for this user."
|
||||
msgstr "Aktivieren Sie 'Passwort ändern' oben, um ein neues Passwort für diesen Benutzer zu setzen."
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:45
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Confirm Password"
|
||||
msgstr "Passwort bestätigen"
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:57
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Consider using special characters"
|
||||
msgstr "Sonderzeichen empfohlen"
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:56
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Include both letters and numbers"
|
||||
msgstr "Buchstaben und Zahlen verwenden"
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:35
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Password"
|
||||
msgstr "Passwort"
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:53
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Password requirements"
|
||||
msgstr "Passwort-Anforderungen"
|
||||
|
||||
#: lib/mv_web/live/user_live/index.html.heex:25
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Select all users"
|
||||
msgstr "Alle Benutzer auswählen"
|
||||
|
||||
#: lib/mv_web/live/user_live/index.html.heex:39
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Select user"
|
||||
msgstr "Benutzer auswählen"
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:27
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Set Password"
|
||||
msgstr "Passwort setzen"
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:83
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "User will be created without a password. Check 'Set Password' to add one."
|
||||
msgstr "Benutzer wird ohne Passwort erstellt. Aktivieren Sie 'Passwort setzen', um eines hinzuzufügen."
|
||||
|
|
|
|||
|
|
@ -16,34 +16,34 @@ msgstr ""
|
|||
msgid "Actions"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/index.ex:40
|
||||
#: lib/mv_web/live/user_live/index.ex:36
|
||||
#: lib/mv_web/live/member_live/index.html.heex:77
|
||||
#: lib/mv_web/live/user_live/index.html.heex:69
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Are you sure?"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/components/layouts.ex:104
|
||||
#: lib/mv_web/components/layouts.ex:116
|
||||
#: lib/mv_web/components/layouts.ex:71
|
||||
#: lib/mv_web/components/layouts.ex:83
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Attempting to reconnect"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:25
|
||||
#: lib/mv_web/live/member_live/index.ex:26
|
||||
#: lib/mv_web/live/member_live/index.html.heex:62
|
||||
#: lib/mv_web/live/member_live/show.ex:36
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "City"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/index.ex:42
|
||||
#: lib/mv_web/live/user_live/index.ex:38
|
||||
#: lib/mv_web/live/member_live/index.html.heex:79
|
||||
#: lib/mv_web/live/user_live/index.html.heex:71
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/index.ex:34
|
||||
#: lib/mv_web/live/user_live/form.ex:45
|
||||
#: lib/mv_web/live/user_live/index.ex:30
|
||||
#: lib/mv_web/live/member_live/index.html.heex:71
|
||||
#: lib/mv_web/live/user_live/form.ex:109
|
||||
#: lib/mv_web/live/user_live/index.html.heex:63
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
|
|
@ -55,59 +55,51 @@ msgid "Edit Member"
|
|||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:18
|
||||
#: lib/mv_web/live/member_live/index.ex:25
|
||||
#: lib/mv_web/live/member_live/index.html.heex:58
|
||||
#: lib/mv_web/live/member_live/show.ex:27
|
||||
#: lib/mv_web/live/user_live/form.ex:14
|
||||
#: lib/mv_web/live/user_live/index.ex:22
|
||||
#: lib/mv_web/live/user_live/index.html.heex:48
|
||||
#: lib/mv_web/live/user_live/show.ex:24
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Email"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:16
|
||||
#: lib/mv_web/live/member_live/index.ex:23
|
||||
#: lib/mv_web/live/member_live/show.ex:25
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "First Name"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:22
|
||||
#: lib/mv_web/live/member_live/index.ex:27
|
||||
#: lib/mv_web/live/member_live/index.html.heex:64
|
||||
#: lib/mv_web/live/member_live/show.ex:33
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Join Date"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:17
|
||||
#: lib/mv_web/live/member_live/index.ex:24
|
||||
#: lib/mv_web/live/member_live/show.ex:26
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Last Name"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/index.ex:9
|
||||
#: lib/mv_web/live/member_live/index.ex:54
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Listing Members"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/index.ex:12
|
||||
#: lib/mv_web/live/member_live/index.html.heex:6
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "New Member"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/index.ex:31
|
||||
#: lib/mv_web/live/user_live/index.ex:27
|
||||
#: lib/mv_web/live/member_live/index.html.heex:68
|
||||
#: lib/mv_web/live/user_live/index.html.heex:60
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Show"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/components/layouts.ex:111
|
||||
#: lib/mv_web/components/layouts.ex:78
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Something went wrong!"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/components/layouts.ex:99
|
||||
#: lib/mv_web/components/layouts.ex:66
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "We can't find the internet"
|
||||
msgstr ""
|
||||
|
|
@ -136,6 +128,7 @@ msgid "Exit Date"
|
|||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:27
|
||||
#: lib/mv_web/live/member_live/index.html.heex:60
|
||||
#: lib/mv_web/live/member_live/show.ex:38
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "House Number"
|
||||
|
|
@ -154,12 +147,14 @@ msgid "Paid"
|
|||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:21
|
||||
#: lib/mv_web/live/member_live/index.html.heex:63
|
||||
#: lib/mv_web/live/member_live/show.ex:32
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Phone Number"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:28
|
||||
#: lib/mv_web/live/member_live/index.html.heex:61
|
||||
#: lib/mv_web/live/member_live/show.ex:39
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Postal Code"
|
||||
|
|
@ -173,12 +168,13 @@ msgstr ""
|
|||
#: lib/mv_web/live/member_live/form.ex:49
|
||||
#: lib/mv_web/live/property_live/form.ex:41
|
||||
#: lib/mv_web/live/property_type_live/form.ex:29
|
||||
#: lib/mv_web/live/user_live/form.ex:30
|
||||
#: lib/mv_web/live/user_live/form.ex:92
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Saving..."
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:26
|
||||
#: lib/mv_web/live/member_live/index.html.heex:59
|
||||
#: lib/mv_web/live/member_live/show.ex:37
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Street"
|
||||
|
|
@ -266,7 +262,7 @@ msgstr ""
|
|||
#: lib/mv_web/live/member_live/form.ex:52
|
||||
#: lib/mv_web/live/property_live/form.ex:44
|
||||
#: lib/mv_web/live/property_type_live/form.ex:32
|
||||
#: lib/mv_web/live/user_live/form.ex:31
|
||||
#: lib/mv_web/live/user_live/form.ex:95
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
|
@ -306,8 +302,13 @@ msgstr ""
|
|||
msgid "Immutable"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/index.ex:9
|
||||
#: lib/mv_web/live/user_live/index.ex:50
|
||||
#: lib/mv_web/components/layouts/navbar.ex:73
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Logout"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/index.ex:12
|
||||
#: lib/mv_web/live/user_live/index.html.heex:3
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Listing Users"
|
||||
msgstr ""
|
||||
|
|
@ -317,12 +318,20 @@ msgstr ""
|
|||
msgid "Member"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/components/layouts/navbar.ex:14
|
||||
#: lib/mv_web/live/member_live/index.ex:12
|
||||
#: lib/mv_web/live/member_live/index.html.heex:3
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Members"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/index.html.heex:50
|
||||
#: lib/mv_web/live/property_type_live/form.ex:16
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/index.ex:12
|
||||
#: lib/mv_web/live/user_live/index.html.heex:6
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "New User"
|
||||
msgstr ""
|
||||
|
|
@ -337,13 +346,13 @@ msgstr ""
|
|||
msgid "Not set"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:19
|
||||
#: lib/mv_web/live/user_live/form.ex:25
|
||||
#: lib/mv_web/live/user_live/form.ex:75
|
||||
#: lib/mv_web/live/user_live/form.ex:83
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Note"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/index.ex:23
|
||||
#: lib/mv_web/live/user_live/index.html.heex:56
|
||||
#: lib/mv_web/live/user_live/show.ex:25
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "OIDC ID"
|
||||
|
|
@ -354,16 +363,16 @@ msgstr ""
|
|||
msgid "Password Authentication"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:19
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Password can only be changed through authentication functions."
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/property_live/form.ex:37
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Please select a property type first"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/components/layouts/navbar.ex:69
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Profil"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/property_live/form.ex:207
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Property %{action} successfully"
|
||||
|
|
@ -394,7 +403,22 @@ msgstr ""
|
|||
msgid "Save Property type"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:30
|
||||
#: lib/mv_web/live/member_live/index.html.heex:27
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Select all members"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/index.html.heex:41
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Select member"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/components/layouts/navbar.ex:72
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:93
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Save User"
|
||||
msgstr ""
|
||||
|
|
@ -429,7 +453,7 @@ msgstr ""
|
|||
msgid "Use this form to manage user records in your database."
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:46
|
||||
#: lib/mv_web/live/user_live/form.ex:110
|
||||
#: lib/mv_web/live/user_live/show.ex:9
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "User"
|
||||
|
|
@ -445,12 +469,87 @@ msgstr ""
|
|||
msgid "Value type"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:45
|
||||
#: lib/mv_web/components/table_components.ex:30
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "ascending"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/components/table_components.ex:30
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "descending"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:109
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "New"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:25
|
||||
#: lib/mv_web/live/user_live/form.ex:64
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Users created here will need to set their password through the authentication system."
|
||||
msgid "Admin Note"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:64
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "As an administrator, you can directly set a new password for this user using the same secure Ash Authentication system."
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:55
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "At least 8 characters"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:27
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Change Password"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:75
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Check 'Change Password' above to set a new password for this user."
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:45
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Confirm Password"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:57
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Consider using special characters"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:56
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Include both letters and numbers"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:35
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:53
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Password requirements"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/index.html.heex:25
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Select all users"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/index.html.heex:39
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Select user"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:27
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Set Password"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:83
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "User will be created without a password. Check 'Set Password' to add one."
|
||||
msgstr ""
|
||||
|
|
|
|||
|
|
@ -16,34 +16,34 @@ msgstr ""
|
|||
msgid "Actions"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/index.ex:40
|
||||
#: lib/mv_web/live/user_live/index.ex:36
|
||||
#: lib/mv_web/live/member_live/index.html.heex:77
|
||||
#: lib/mv_web/live/user_live/index.html.heex:69
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Are you sure?"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/components/layouts.ex:104
|
||||
#: lib/mv_web/components/layouts.ex:116
|
||||
#: lib/mv_web/components/layouts.ex:71
|
||||
#: lib/mv_web/components/layouts.ex:83
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Attempting to reconnect"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:25
|
||||
#: lib/mv_web/live/member_live/index.ex:26
|
||||
#: lib/mv_web/live/member_live/index.html.heex:62
|
||||
#: lib/mv_web/live/member_live/show.ex:36
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "City"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/index.ex:42
|
||||
#: lib/mv_web/live/user_live/index.ex:38
|
||||
#: lib/mv_web/live/member_live/index.html.heex:79
|
||||
#: lib/mv_web/live/user_live/index.html.heex:71
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/index.ex:34
|
||||
#: lib/mv_web/live/user_live/form.ex:45
|
||||
#: lib/mv_web/live/user_live/index.ex:30
|
||||
#: lib/mv_web/live/member_live/index.html.heex:71
|
||||
#: lib/mv_web/live/user_live/form.ex:109
|
||||
#: lib/mv_web/live/user_live/index.html.heex:63
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
|
|
@ -55,59 +55,51 @@ msgid "Edit Member"
|
|||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:18
|
||||
#: lib/mv_web/live/member_live/index.ex:25
|
||||
#: lib/mv_web/live/member_live/index.html.heex:58
|
||||
#: lib/mv_web/live/member_live/show.ex:27
|
||||
#: lib/mv_web/live/user_live/form.ex:14
|
||||
#: lib/mv_web/live/user_live/index.ex:22
|
||||
#: lib/mv_web/live/user_live/index.html.heex:48
|
||||
#: lib/mv_web/live/user_live/show.ex:24
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Email"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:16
|
||||
#: lib/mv_web/live/member_live/index.ex:23
|
||||
#: lib/mv_web/live/member_live/show.ex:25
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "First Name"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:22
|
||||
#: lib/mv_web/live/member_live/index.ex:27
|
||||
#: lib/mv_web/live/member_live/index.html.heex:64
|
||||
#: lib/mv_web/live/member_live/show.ex:33
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Join Date"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:17
|
||||
#: lib/mv_web/live/member_live/index.ex:24
|
||||
#: lib/mv_web/live/member_live/show.ex:26
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Last Name"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/index.ex:9
|
||||
#: lib/mv_web/live/member_live/index.ex:54
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Listing Members"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/index.ex:12
|
||||
#: lib/mv_web/live/member_live/index.html.heex:6
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "New Member"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/index.ex:31
|
||||
#: lib/mv_web/live/user_live/index.ex:27
|
||||
#: lib/mv_web/live/member_live/index.html.heex:68
|
||||
#: lib/mv_web/live/user_live/index.html.heex:60
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Show"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/components/layouts.ex:111
|
||||
#: lib/mv_web/components/layouts.ex:78
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Something went wrong!"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/components/layouts.ex:99
|
||||
#: lib/mv_web/components/layouts.ex:66
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "We can't find the internet"
|
||||
msgstr ""
|
||||
|
|
@ -136,6 +128,7 @@ msgid "Exit Date"
|
|||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:27
|
||||
#: lib/mv_web/live/member_live/index.html.heex:60
|
||||
#: lib/mv_web/live/member_live/show.ex:38
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "House Number"
|
||||
|
|
@ -154,12 +147,14 @@ msgid "Paid"
|
|||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:21
|
||||
#: lib/mv_web/live/member_live/index.html.heex:63
|
||||
#: lib/mv_web/live/member_live/show.ex:32
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Phone Number"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:28
|
||||
#: lib/mv_web/live/member_live/index.html.heex:61
|
||||
#: lib/mv_web/live/member_live/show.ex:39
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Postal Code"
|
||||
|
|
@ -173,12 +168,13 @@ msgstr ""
|
|||
#: lib/mv_web/live/member_live/form.ex:49
|
||||
#: lib/mv_web/live/property_live/form.ex:41
|
||||
#: lib/mv_web/live/property_type_live/form.ex:29
|
||||
#: lib/mv_web/live/user_live/form.ex:30
|
||||
#: lib/mv_web/live/user_live/form.ex:92
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Saving..."
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex:26
|
||||
#: lib/mv_web/live/member_live/index.html.heex:59
|
||||
#: lib/mv_web/live/member_live/show.ex:37
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Street"
|
||||
|
|
@ -266,7 +262,7 @@ msgstr ""
|
|||
#: lib/mv_web/live/member_live/form.ex:52
|
||||
#: lib/mv_web/live/property_live/form.ex:44
|
||||
#: lib/mv_web/live/property_type_live/form.ex:32
|
||||
#: lib/mv_web/live/user_live/form.ex:31
|
||||
#: lib/mv_web/live/user_live/form.ex:95
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
|
@ -306,8 +302,13 @@ msgstr ""
|
|||
msgid "Immutable"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/index.ex:9
|
||||
#: lib/mv_web/live/user_live/index.ex:50
|
||||
#: lib/mv_web/components/layouts/navbar.ex:73
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Logout"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/index.ex:12
|
||||
#: lib/mv_web/live/user_live/index.html.heex:3
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
msgid "Listing Users"
|
||||
msgstr ""
|
||||
|
|
@ -317,12 +318,20 @@ msgstr ""
|
|||
msgid "Member"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/components/layouts/navbar.ex:14
|
||||
#: lib/mv_web/live/member_live/index.ex:12
|
||||
#: lib/mv_web/live/member_live/index.html.heex:3
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Members"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/index.html.heex:50
|
||||
#: lib/mv_web/live/property_type_live/form.ex:16
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/index.ex:12
|
||||
#: lib/mv_web/live/user_live/index.html.heex:6
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "New User"
|
||||
msgstr ""
|
||||
|
|
@ -337,13 +346,13 @@ msgstr ""
|
|||
msgid "Not set"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:19
|
||||
#: lib/mv_web/live/user_live/form.ex:25
|
||||
#: lib/mv_web/live/user_live/form.ex:75
|
||||
#: lib/mv_web/live/user_live/form.ex:83
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
msgid "Note"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/index.ex:23
|
||||
#: lib/mv_web/live/user_live/index.html.heex:56
|
||||
#: lib/mv_web/live/user_live/show.ex:25
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "OIDC ID"
|
||||
|
|
@ -354,16 +363,16 @@ msgstr ""
|
|||
msgid "Password Authentication"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:19
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Password can only be changed through authentication functions."
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/property_live/form.ex:37
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Please select a property type first"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/components/layouts/navbar.ex:69
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Profil"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/property_live/form.ex:207
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
msgid "Property %{action} successfully"
|
||||
|
|
@ -394,7 +403,22 @@ msgstr ""
|
|||
msgid "Save Property type"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:30
|
||||
#: lib/mv_web/live/member_live/index.html.heex:27
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Select all members"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/index.html.heex:41
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Select member"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/components/layouts/navbar.ex:72
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:93
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
msgid "Save User"
|
||||
msgstr ""
|
||||
|
|
@ -429,7 +453,7 @@ msgstr ""
|
|||
msgid "Use this form to manage user records in your database."
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:46
|
||||
#: lib/mv_web/live/user_live/form.ex:110
|
||||
#: lib/mv_web/live/user_live/show.ex:9
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "User"
|
||||
|
|
@ -445,12 +469,87 @@ msgstr ""
|
|||
msgid "Value type"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:45
|
||||
#: lib/mv_web/components/table_components.ex:30
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "ascending"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/components/table_components.ex:30
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "descending"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:109
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "New"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:25
|
||||
#: lib/mv_web/live/user_live/form.ex:64
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Users created here will need to set their password through the authentication system."
|
||||
msgid "Admin Note"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:64
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "As an administrator, you can directly set a new password for this user using the same secure Ash Authentication system."
|
||||
msgstr "As an administrator, you can directly set a new password for this user using the same secure Ash Authentication system."
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:55
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "At least 8 characters"
|
||||
msgstr "At least 8 characters"
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:27
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Change Password"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:75
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Check 'Change Password' above to set a new password for this user."
|
||||
msgstr "Check 'Change Password' above to set a new password for this user."
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:45
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Confirm Password"
|
||||
msgstr "Confirm Password"
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:57
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Consider using special characters"
|
||||
msgstr "Consider using special characters"
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:56
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Include both letters and numbers"
|
||||
msgstr "Include both letters and numbers"
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:35
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Password"
|
||||
msgstr "Password"
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:53
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Password requirements"
|
||||
msgstr "Password requirements"
|
||||
|
||||
#: lib/mv_web/live/user_live/index.html.heex:25
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
msgid "Select all users"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/index.html.heex:39
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
msgid "Select user"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:27
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Set Password"
|
||||
msgstr "Set Password"
|
||||
|
||||
#: lib/mv_web/live/user_live/form.ex:83
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "User will be created without a password. Check 'Set Password' to add one."
|
||||
msgstr "User will be created without a password. Check 'Set Password' to add one."
|
||||
|
|
|
|||
263
test/mv_web/user_live/form_test.exs
Normal file
263
test/mv_web/user_live/form_test.exs
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
defmodule MvWeb.UserLive.FormTest do
|
||||
use MvWeb.ConnCase, async: true
|
||||
import Phoenix.LiveViewTest
|
||||
|
||||
# Helper to setup authenticated connection and live view
|
||||
defp setup_live_view(conn, path) do
|
||||
conn = conn_with_oidc_user(conn, %{email: "admin@example.com"})
|
||||
live(conn, path)
|
||||
end
|
||||
|
||||
describe "new user form - display" do
|
||||
test "shows correct form elements", %{conn: conn} do
|
||||
{:ok, view, html} = setup_live_view(conn, "/users/new")
|
||||
|
||||
assert html =~ "New User"
|
||||
assert html =~ "Email"
|
||||
assert html =~ "Set Password"
|
||||
assert has_element?(view, "form#user-form[phx-submit='save']")
|
||||
assert has_element?(view, "input[name='user[email]']")
|
||||
assert has_element?(view, "input[type='checkbox'][name='set_password']")
|
||||
end
|
||||
|
||||
test "hides password fields initially", %{conn: conn} do
|
||||
{:ok, view, _html} = setup_live_view(conn, "/users/new")
|
||||
|
||||
refute has_element?(view, "input[name='user[password]']")
|
||||
refute has_element?(view, "input[name='user[password_confirmation]']")
|
||||
end
|
||||
|
||||
test "shows password fields when checkbox toggled", %{conn: conn} do
|
||||
{:ok, view, _html} = setup_live_view(conn, "/users/new")
|
||||
|
||||
view |> element("input[name='set_password']") |> render_click()
|
||||
|
||||
assert has_element?(view, "input[name='user[password]']")
|
||||
assert has_element?(view, "input[name='user[password_confirmation]']")
|
||||
assert render(view) =~ "Password requirements"
|
||||
end
|
||||
end
|
||||
|
||||
describe "new user form - creation" do
|
||||
test "creates user without password", %{conn: conn} do
|
||||
{:ok, view, _html} = setup_live_view(conn, "/users/new")
|
||||
|
||||
view
|
||||
|> form("#user-form", user: %{email: "newuser@example.com"})
|
||||
|> render_submit()
|
||||
|
||||
assert_redirected(view, "/users")
|
||||
end
|
||||
|
||||
test "creates user with password when enabled", %{conn: conn} do
|
||||
{:ok, view, _html} = setup_live_view(conn, "/users/new")
|
||||
|
||||
view |> element("input[name='set_password']") |> render_click()
|
||||
|
||||
view
|
||||
|> form("#user-form", user: %{
|
||||
email: "passworduser@example.com",
|
||||
password: "securepassword123",
|
||||
password_confirmation: "securepassword123"
|
||||
})
|
||||
|> render_submit()
|
||||
|
||||
assert_redirected(view, "/users")
|
||||
end
|
||||
|
||||
test "stores user data correctly", %{conn: conn} do
|
||||
{:ok, view, _html} = setup_live_view(conn, "/users/new")
|
||||
|
||||
view
|
||||
|> form("#user-form", user: %{email: "storetest@example.com"})
|
||||
|> render_submit()
|
||||
|
||||
user = Ash.get!(Mv.Accounts.User,
|
||||
[email: Ash.CiString.new("storetest@example.com")],
|
||||
domain: Mv.Accounts
|
||||
)
|
||||
assert to_string(user.email) == "storetest@example.com"
|
||||
assert is_nil(user.hashed_password)
|
||||
end
|
||||
|
||||
test "stores password when provided", %{conn: conn} do
|
||||
{:ok, view, _html} = setup_live_view(conn, "/users/new")
|
||||
|
||||
view |> element("input[name='set_password']") |> render_click()
|
||||
|
||||
view
|
||||
|> form("#user-form", user: %{
|
||||
email: "passwordstoretest@example.com",
|
||||
password: "securepassword123",
|
||||
password_confirmation: "securepassword123"
|
||||
})
|
||||
|> render_submit()
|
||||
|
||||
user = Ash.get!(Mv.Accounts.User,
|
||||
[email: Ash.CiString.new("passwordstoretest@example.com")],
|
||||
domain: Mv.Accounts
|
||||
)
|
||||
assert user.hashed_password != nil
|
||||
assert String.starts_with?(user.hashed_password, "$2b$")
|
||||
end
|
||||
end
|
||||
|
||||
describe "new user form - validation" do
|
||||
test "shows error for duplicate email", %{conn: conn} do
|
||||
_existing_user = create_test_user(%{email: "existing@example.com"})
|
||||
{:ok, view, _html} = setup_live_view(conn, "/users/new")
|
||||
|
||||
html = view
|
||||
|> form("#user-form", user: %{email: "existing@example.com"})
|
||||
|> render_submit()
|
||||
|
||||
assert html =~ "has already been taken"
|
||||
end
|
||||
|
||||
test "shows error for short password", %{conn: conn} do
|
||||
{:ok, view, _html} = setup_live_view(conn, "/users/new")
|
||||
|
||||
view |> element("input[name='set_password']") |> render_click()
|
||||
|
||||
html = view
|
||||
|> form("#user-form", user: %{
|
||||
email: "test@example.com",
|
||||
password: "123",
|
||||
password_confirmation: "123"
|
||||
})
|
||||
|> render_submit()
|
||||
|
||||
assert html =~ "length must be greater than or equal to 8"
|
||||
end
|
||||
end
|
||||
|
||||
describe "edit user form - display" do
|
||||
test "shows correct form elements for existing user", %{conn: conn} do
|
||||
user = create_test_user(%{email: "editme@example.com"})
|
||||
{:ok, view, html} = setup_live_view(conn, "/users/#{user.id}/edit")
|
||||
|
||||
assert html =~ "Edit User"
|
||||
assert html =~ "Change Password"
|
||||
assert has_element?(view, "input[name='user[email]'][value='editme@example.com']")
|
||||
assert html =~ "Check 'Change Password' above to set a new password for this user"
|
||||
end
|
||||
|
||||
test "shows admin password fields when enabled", %{conn: conn} do
|
||||
user = create_test_user(%{email: "editme@example.com"})
|
||||
{:ok, view, _html} = setup_live_view(conn, "/users/#{user.id}/edit")
|
||||
|
||||
view |> element("input[name='set_password']") |> render_click()
|
||||
|
||||
assert has_element?(view, "input[name='user[password]']")
|
||||
refute has_element?(view, "input[name='user[password_confirmation]']")
|
||||
assert render(view) =~ "Admin Note"
|
||||
end
|
||||
end
|
||||
|
||||
describe "edit user form - updates" do
|
||||
test "updates email without changing password", %{conn: conn} do
|
||||
user = create_test_user(%{email: "old@example.com"})
|
||||
original_password = user.hashed_password
|
||||
{:ok, view, _html} = setup_live_view(conn, "/users/#{user.id}/edit")
|
||||
|
||||
view
|
||||
|> form("#user-form", user: %{email: "new@example.com"})
|
||||
|> render_submit()
|
||||
|
||||
assert_redirected(view, "/users")
|
||||
|
||||
updated_user = Ash.reload!(user, domain: Mv.Accounts)
|
||||
assert to_string(updated_user.email) == "new@example.com"
|
||||
assert updated_user.hashed_password == original_password
|
||||
end
|
||||
|
||||
test "admin sets new password for user", %{conn: conn} do
|
||||
user = create_test_user(%{email: "user@example.com"})
|
||||
original_password = user.hashed_password
|
||||
{:ok, view, _html} = setup_live_view(conn, "/users/#{user.id}/edit")
|
||||
|
||||
view |> element("input[name='set_password']") |> render_click()
|
||||
|
||||
view
|
||||
|> form("#user-form", user: %{
|
||||
email: "user@example.com",
|
||||
password: "newadminpassword123"
|
||||
})
|
||||
|> render_submit()
|
||||
|
||||
assert_redirected(view, "/users")
|
||||
|
||||
updated_user = Ash.reload!(user, domain: Mv.Accounts)
|
||||
assert updated_user.hashed_password != original_password
|
||||
assert String.starts_with?(updated_user.hashed_password, "$2b$")
|
||||
end
|
||||
end
|
||||
|
||||
describe "edit user form - validation" do
|
||||
test "shows error for duplicate email", %{conn: conn} do
|
||||
_existing_user = create_test_user(%{email: "taken@example.com"})
|
||||
user_to_edit = create_test_user(%{email: "original@example.com"})
|
||||
{:ok, view, _html} = setup_live_view(conn, "/users/#{user_to_edit.id}/edit")
|
||||
|
||||
html = view
|
||||
|> form("#user-form", user: %{email: "taken@example.com"})
|
||||
|> render_submit()
|
||||
|
||||
assert html =~ "has already been taken"
|
||||
end
|
||||
|
||||
test "shows error for invalid password", %{conn: conn} do
|
||||
user = create_test_user(%{email: "user@example.com"})
|
||||
{:ok, view, _html} = setup_live_view(conn, "/users/#{user.id}/edit")
|
||||
|
||||
view |> element("input[name='set_password']") |> render_click()
|
||||
|
||||
result = view
|
||||
|> form("#user-form", user: %{
|
||||
email: "user@example.com",
|
||||
password: "123"
|
||||
})
|
||||
|> render_submit()
|
||||
|
||||
case result do
|
||||
{:error, {:live_redirect, %{to: "/users"}}} ->
|
||||
flunk("Expected validation error but form was submitted successfully")
|
||||
html when is_binary(html) ->
|
||||
assert html =~ "must have length of at least 8"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "internationalization" do
|
||||
test "shows German labels", %{conn: conn} do
|
||||
conn = conn_with_oidc_user(conn, %{email: "admin_de@example.com"})
|
||||
conn = Plug.Test.init_test_session(conn, locale: "de")
|
||||
{:ok, _view, html} = live(conn, "/users/new")
|
||||
|
||||
assert html =~ "Neuer Benutzer"
|
||||
assert html =~ "E-Mail"
|
||||
assert html =~ "Passwort setzen"
|
||||
end
|
||||
|
||||
test "shows English labels", %{conn: conn} do
|
||||
conn = conn_with_oidc_user(conn, %{email: "admin_en@example.com"})
|
||||
Gettext.put_locale(MvWeb.Gettext, "en")
|
||||
{:ok, _view, html} = live(conn, "/users/new")
|
||||
|
||||
assert html =~ "New User"
|
||||
assert html =~ "Email"
|
||||
assert html =~ "Set Password"
|
||||
end
|
||||
|
||||
test "shows different labels for edit vs new", %{conn: conn} do
|
||||
user = create_test_user(%{email: "test@example.com"})
|
||||
conn = conn_with_oidc_user(conn, %{email: "admin@example.com"})
|
||||
|
||||
{:ok, _view, new_html} = live(conn, "/users/new")
|
||||
{:ok, _view, edit_html} = live(conn, "/users/#{user.id}/edit")
|
||||
|
||||
assert new_html =~ "Set Password"
|
||||
assert edit_html =~ "Change Password"
|
||||
end
|
||||
end
|
||||
end
|
||||
375
test/mv_web/user_live/index_test.exs
Normal file
375
test/mv_web/user_live/index_test.exs
Normal file
|
|
@ -0,0 +1,375 @@
|
|||
defmodule MvWeb.UserLive.IndexTest do
|
||||
use MvWeb.ConnCase, async: true
|
||||
import Phoenix.LiveViewTest
|
||||
|
||||
describe "basic functionality" do
|
||||
test "shows translated title in German", %{conn: conn} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
conn = Plug.Test.init_test_session(conn, locale: "de")
|
||||
{:ok, _view, html} = live(conn, "/users")
|
||||
assert html =~ "Benutzer auflisten"
|
||||
end
|
||||
|
||||
test "shows translated title in English", %{conn: conn} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
Gettext.put_locale(MvWeb.Gettext, "en")
|
||||
{:ok, _view, html} = live(conn, "/users")
|
||||
assert html =~ "Listing Users"
|
||||
end
|
||||
|
||||
test "shows New User button", %{conn: conn} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/users")
|
||||
assert html =~ "New User"
|
||||
end
|
||||
|
||||
test "displays users in a table", %{conn: conn} do
|
||||
# Create test users
|
||||
_user1 = create_test_user(%{email: "alice@example.com", oidc_id: "alice123"})
|
||||
_user2 = create_test_user(%{email: "bob@example.com", oidc_id: "bob456"})
|
||||
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/users")
|
||||
|
||||
assert html =~ "alice@example.com"
|
||||
assert html =~ "bob@example.com"
|
||||
assert html =~ "alice123"
|
||||
assert html =~ "bob456"
|
||||
end
|
||||
|
||||
test "shows correct action links", %{conn: conn} do
|
||||
user = create_test_user(%{email: "test@example.com"})
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/users")
|
||||
|
||||
assert html =~ "Edit"
|
||||
assert html =~ "Delete"
|
||||
assert html =~ ~r/href="[^"]*\/users\/#{user.id}\/edit"/
|
||||
end
|
||||
end
|
||||
|
||||
describe "sorting functionality" do
|
||||
setup do
|
||||
# Create users with different emails for sorting tests
|
||||
user_a = create_test_user(%{email: "alpha@example.com", oidc_id: "alpha"})
|
||||
user_z = create_test_user(%{email: "zulu@example.com", oidc_id: "zulu"})
|
||||
user_m = create_test_user(%{email: "mike@example.com", oidc_id: "mike"})
|
||||
|
||||
%{users: [user_a, user_z, user_m]}
|
||||
end
|
||||
|
||||
test "initially sorts by email ascending", %{conn: conn} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/users")
|
||||
|
||||
# Should show ascending indicator (up arrow)
|
||||
assert html =~ "hero-chevron-up"
|
||||
assert html =~ ~s(aria-sort="ascending")
|
||||
|
||||
# Test actual sort order: alpha should appear before mike, mike before zulu
|
||||
alpha_pos = html |> :binary.match("alpha@example.com") |> elem(0)
|
||||
mike_pos = html |> :binary.match("mike@example.com") |> elem(0)
|
||||
zulu_pos = html |> :binary.match("zulu@example.com") |> elem(0)
|
||||
|
||||
assert alpha_pos < mike_pos, "alpha@example.com should appear before mike@example.com"
|
||||
assert mike_pos < zulu_pos, "mike@example.com should appear before zulu@example.com"
|
||||
end
|
||||
|
||||
test "can sort email descending by clicking sort button", %{conn: conn} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/users")
|
||||
|
||||
# Click on email sort button and get rendered result
|
||||
html = view |> element("button[phx-value-field='email']") |> render_click()
|
||||
|
||||
# Should now show descending indicator (down arrow)
|
||||
assert html =~ "hero-chevron-down"
|
||||
assert html =~ ~s(aria-sort="descending")
|
||||
|
||||
# Test actual sort order reversed: zulu should now appear before mike, mike before alpha
|
||||
alpha_pos = html |> :binary.match("alpha@example.com") |> elem(0)
|
||||
mike_pos = html |> :binary.match("mike@example.com") |> elem(0)
|
||||
zulu_pos = html |> :binary.match("zulu@example.com") |> elem(0)
|
||||
|
||||
assert zulu_pos < mike_pos, "zulu@example.com should appear before mike@example.com when sorted desc"
|
||||
assert mike_pos < alpha_pos, "mike@example.com should appear before alpha@example.com when sorted desc"
|
||||
end
|
||||
|
||||
test "toggles back to ascending when clicking sort button twice", %{conn: conn} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/users")
|
||||
|
||||
# Click twice to toggle: asc -> desc -> asc
|
||||
view |> element("button[phx-value-field='email']") |> render_click()
|
||||
html = view |> element("button[phx-value-field='email']") |> render_click()
|
||||
|
||||
# Should be back to ascending
|
||||
assert html =~ "hero-chevron-up"
|
||||
assert html =~ ~s(aria-sort="ascending")
|
||||
|
||||
# Should be back to original ascending order
|
||||
alpha_pos = html |> :binary.match("alpha@example.com") |> elem(0)
|
||||
mike_pos = html |> :binary.match("mike@example.com") |> elem(0)
|
||||
zulu_pos = html |> :binary.match("zulu@example.com") |> elem(0)
|
||||
|
||||
assert alpha_pos < mike_pos, "Should be back to ascending: alpha before mike"
|
||||
assert mike_pos < zulu_pos, "Should be back to ascending: mike before zulu"
|
||||
end
|
||||
|
||||
test "shows sort direction icons", %{conn: conn} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/users")
|
||||
|
||||
# Initially ascending - should show up arrow
|
||||
html = render(view)
|
||||
assert html =~ "hero-chevron-up"
|
||||
|
||||
# After clicking, should show down arrow
|
||||
view |> element("button[phx-value-field='email']") |> render_click()
|
||||
html = render(view)
|
||||
assert html =~ "hero-chevron-down"
|
||||
end
|
||||
end
|
||||
|
||||
describe "checkbox selection functionality" do
|
||||
setup do
|
||||
user1 = create_test_user(%{email: "user1@example.com", oidc_id: "user1"})
|
||||
user2 = create_test_user(%{email: "user2@example.com", oidc_id: "user2"})
|
||||
%{users: [user1, user2]}
|
||||
end
|
||||
|
||||
test "shows select all checkbox", %{conn: conn} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/users")
|
||||
|
||||
assert html =~ ~s(name="select_all")
|
||||
assert html =~ ~s(phx-click="select_all")
|
||||
end
|
||||
|
||||
test "shows individual user checkboxes", %{conn: conn, users: [user1, user2]} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/users")
|
||||
|
||||
assert html =~ ~s(name="#{user1.id}")
|
||||
assert html =~ ~s(name="#{user2.id}")
|
||||
assert html =~ ~s(phx-click="select_user")
|
||||
end
|
||||
|
||||
test "can select individual users", %{conn: conn, users: [user1, user2]} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/users")
|
||||
|
||||
# Initially, individual checkboxes should exist but not be checked
|
||||
assert view |> element("input[type='checkbox'][name='#{user1.id}']") |> has_element?()
|
||||
assert view |> element("input[type='checkbox'][name='#{user2.id}']") |> has_element?()
|
||||
|
||||
# Initially, select_all should not be checked (since no individual items are selected)
|
||||
refute view |> element("input[type='checkbox'][name='select_all'][checked]") |> has_element?()
|
||||
|
||||
# Select first user checkbox
|
||||
html = view |> element("input[type='checkbox'][name='#{user1.id}']") |> render_click()
|
||||
|
||||
# The select_all checkbox should still not be checked (not all users selected)
|
||||
refute view |> element("input[type='checkbox'][name='select_all'][checked]") |> has_element?()
|
||||
|
||||
# Page should still function normally
|
||||
assert html =~ "Email"
|
||||
assert html =~ to_string(user1.email)
|
||||
end
|
||||
|
||||
test "can deselect individual users", %{conn: conn, users: [user1, _user2]} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/users")
|
||||
|
||||
# Select user first
|
||||
view |> element("input[type='checkbox'][name='#{user1.id}']") |> render_click()
|
||||
|
||||
# Then deselect user
|
||||
html = view |> element("input[type='checkbox'][name='#{user1.id}']") |> render_click()
|
||||
|
||||
# Select all should not be checked after deselecting individual user
|
||||
refute view |> element("input[type='checkbox'][name='select_all'][checked]") |> has_element?()
|
||||
|
||||
# Page should still function normally
|
||||
assert html =~ "Email"
|
||||
assert html =~ to_string(user1.email)
|
||||
end
|
||||
|
||||
test "select all functionality selects all users", %{conn: conn, users: [user1, user2]} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/users")
|
||||
|
||||
# Initially no checkboxes should be checked
|
||||
refute view |> element("input[type='checkbox'][name='select_all'][checked]") |> has_element?()
|
||||
refute view |> element("input[type='checkbox'][name='#{user1.id}'][checked]") |> has_element?()
|
||||
refute view |> element("input[type='checkbox'][name='#{user2.id}'][checked]") |> has_element?()
|
||||
|
||||
# Click select all
|
||||
html = view |> element("input[type='checkbox'][name='select_all']") |> render_click()
|
||||
|
||||
# After selecting all, the select_all checkbox should be checked
|
||||
assert view |> element("input[type='checkbox'][name='select_all'][checked]") |> has_element?()
|
||||
|
||||
# Page should still function normally and show all users
|
||||
assert html =~ "Email"
|
||||
assert html =~ to_string(user1.email)
|
||||
assert html =~ to_string(user2.email)
|
||||
end
|
||||
|
||||
test "deselect all functionality deselects all users", %{conn: conn, users: [user1, user2]} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/users")
|
||||
|
||||
# Select all first
|
||||
view |> element("input[type='checkbox'][name='select_all']") |> render_click()
|
||||
|
||||
# Verify that select_all is checked
|
||||
assert view |> element("input[type='checkbox'][name='select_all'][checked]") |> has_element?()
|
||||
|
||||
# Then deselect all
|
||||
html = view |> element("input[type='checkbox'][name='select_all']") |> render_click()
|
||||
|
||||
# After deselecting all, no checkboxes should be checked
|
||||
refute view |> element("input[type='checkbox'][name='select_all'][checked]") |> has_element?()
|
||||
refute view |> element("input[type='checkbox'][name='#{user1.id}'][checked]") |> has_element?()
|
||||
refute view |> element("input[type='checkbox'][name='#{user2.id}'][checked]") |> has_element?()
|
||||
|
||||
# Page should still function normally
|
||||
assert html =~ "Email"
|
||||
assert html =~ to_string(user1.email)
|
||||
assert html =~ to_string(user2.email)
|
||||
end
|
||||
|
||||
test "select all automatically checks when all individual users are selected", %{conn: conn, users: [user1, user2]} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/users")
|
||||
|
||||
# Initially nothing should be checked
|
||||
refute view |> element("input[type='checkbox'][name='select_all'][checked]") |> has_element?()
|
||||
|
||||
# Select first user
|
||||
view |> element("input[type='checkbox'][name='#{user1.id}']") |> render_click()
|
||||
# Select all should still not be checked (only 1 of 2+ users selected)
|
||||
refute view |> element("input[type='checkbox'][name='select_all'][checked]") |> has_element?()
|
||||
|
||||
# Select second user
|
||||
html = view |> element("input[type='checkbox'][name='#{user2.id}']") |> render_click()
|
||||
|
||||
# Now select all should be automatically checked (all individual users are selected)
|
||||
# Note: This test might need adjustment based on actual implementation
|
||||
# The logic depends on whether authenticated user is included in the count
|
||||
assert html =~ "Email"
|
||||
assert html =~ to_string(user1.email)
|
||||
assert html =~ to_string(user2.email)
|
||||
end
|
||||
end
|
||||
|
||||
describe "delete functionality" do
|
||||
test "can delete a user", %{conn: conn} do
|
||||
_user = create_test_user(%{email: "delete-me@example.com"})
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/users")
|
||||
|
||||
# Confirm user is displayed
|
||||
assert render(view) =~ "delete-me@example.com"
|
||||
|
||||
# Click the first delete button to test the functionality
|
||||
view |> element("tbody tr:first-child a[data-confirm]") |> render_click()
|
||||
|
||||
# The page should still render (basic functionality test)
|
||||
html = render(view)
|
||||
assert html =~ "Email" # Table header should still be there
|
||||
end
|
||||
|
||||
test "shows delete confirmation", %{conn: conn} do
|
||||
_user = create_test_user(%{email: "confirm-delete@example.com"})
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/users")
|
||||
|
||||
# Check that delete link has confirmation attribute
|
||||
assert html =~ ~s(data-confirm="Are you sure?")
|
||||
end
|
||||
end
|
||||
|
||||
describe "navigation" do
|
||||
test "clicking on user row navigates to user show page", %{conn: conn} do
|
||||
user = create_test_user(%{email: "navigate@example.com"})
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/users")
|
||||
|
||||
# This test would need to check row click behavior
|
||||
# The actual navigation would happen via JavaScript
|
||||
html = render(view)
|
||||
assert html =~ ~s(/users/#{user.id})
|
||||
end
|
||||
|
||||
test "edit link points to correct edit page", %{conn: conn} do
|
||||
user = create_test_user(%{email: "edit-me@example.com"})
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/users")
|
||||
|
||||
assert html =~ ~s(/users/#{user.id}/edit)
|
||||
end
|
||||
|
||||
test "new user button points to correct new page", %{conn: conn} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/users")
|
||||
|
||||
assert html =~ ~s(/users/new)
|
||||
end
|
||||
end
|
||||
|
||||
describe "translations" do
|
||||
test "shows German translations for selection", %{conn: conn} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
conn = Plug.Test.init_test_session(conn, locale: "de")
|
||||
{:ok, _view, html} = live(conn, "/users")
|
||||
|
||||
assert html =~ "Alle Benutzer auswählen"
|
||||
assert html =~ "Benutzer auswählen"
|
||||
end
|
||||
|
||||
test "shows English translations for selection", %{conn: conn} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
Gettext.put_locale(MvWeb.Gettext, "en")
|
||||
{:ok, _view, html} = live(conn, "/users")
|
||||
|
||||
# Note: English translations might be empty strings by default
|
||||
# This test would verify the structure is there
|
||||
assert html =~ ~s(aria-label=) # Checking that aria-label attributes exist
|
||||
end
|
||||
end
|
||||
|
||||
describe "edge cases" do
|
||||
test "handles empty user list gracefully", %{conn: conn} do
|
||||
# Don't create any users besides the authenticated one
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/users")
|
||||
|
||||
# Should still show the table structure
|
||||
assert html =~ "Email"
|
||||
assert html =~ "OIDC ID"
|
||||
# Should show the authenticated user at minimum
|
||||
assert html =~ "user@example.com"
|
||||
end
|
||||
|
||||
test "handles users with missing OIDC ID", %{conn: conn} do
|
||||
_user = create_test_user(%{email: "no-oidc@example.com", oidc_id: nil})
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/users")
|
||||
|
||||
assert html =~ "no-oidc@example.com"
|
||||
# Should handle nil OIDC ID gracefully
|
||||
end
|
||||
|
||||
test "handles very long email addresses", %{conn: conn} do
|
||||
long_email = "very.long.email.address.that.might.break.layouts@example.com"
|
||||
_user = create_test_user(%{email: long_email})
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, _view, html} = live(conn, "/users")
|
||||
|
||||
assert html =~ long_email
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -33,16 +33,54 @@ defmodule MvWeb.ConnCase do
|
|||
|
||||
@doc """
|
||||
Creates a test user and returns the user struct.
|
||||
Accepts attrs to override default values.
|
||||
|
||||
Password handling:
|
||||
- If `hashed_password` is provided in attrs, it's used directly
|
||||
- If `password` is provided in attrs, it gets hashed automatically
|
||||
- If neither is provided, uses default password "password"
|
||||
|
||||
## Examples
|
||||
|
||||
create_test_user() # Default user with unique email
|
||||
create_test_user(%{email: "custom@example.com"}) # Custom email
|
||||
create_test_user(%{password: "secret123"}) # Custom password (gets hashed)
|
||||
create_test_user(%{hashed_password: "$2b$..."}) # Pre-hashed password
|
||||
"""
|
||||
def create_test_user(attrs \\ %{}) do
|
||||
email = "user@example.com"
|
||||
password = "password"
|
||||
{:ok, hashed_password} = AshAuthentication.BcryptProvider.hash(password)
|
||||
# Generate unique values to avoid conflicts
|
||||
unique_id = System.unique_integer([:positive])
|
||||
|
||||
default_attrs = %{
|
||||
email: "user#{unique_id}@example.com",
|
||||
oidc_id: "oidc#{unique_id}"
|
||||
}
|
||||
|
||||
# Merge provided attrs with defaults
|
||||
user_attrs = Map.merge(default_attrs, attrs)
|
||||
|
||||
# Handle password/hashed_password
|
||||
final_attrs = cond do
|
||||
# If hashed_password is already provided, use it as-is
|
||||
Map.has_key?(user_attrs, :hashed_password) ->
|
||||
user_attrs
|
||||
|
||||
# If password is provided, hash it
|
||||
Map.has_key?(user_attrs, :password) ->
|
||||
password = Map.get(user_attrs, :password)
|
||||
{:ok, hashed_password} = AshAuthentication.BcryptProvider.hash(password)
|
||||
user_attrs
|
||||
|> Map.delete(:password) # Remove plain password
|
||||
|> Map.put(:hashed_password, hashed_password)
|
||||
|
||||
# Neither provided, use default password
|
||||
true ->
|
||||
password = "password"
|
||||
{:ok, hashed_password} = AshAuthentication.BcryptProvider.hash(password)
|
||||
Map.put(user_attrs, :hashed_password, hashed_password)
|
||||
end
|
||||
|
||||
Ash.Seed.seed!(Mv.Accounts.User, %{
|
||||
email: email,
|
||||
hashed_password: hashed_password
|
||||
})
|
||||
Ash.Seed.seed!(Mv.Accounts.User, final_attrs)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
|
@ -57,8 +95,9 @@ defmodule MvWeb.ConnCase do
|
|||
|
||||
@doc """
|
||||
Signs in a user via OIDC and returns a connection with the user authenticated.
|
||||
By default creates a user with "user@example.com" for consistency.
|
||||
"""
|
||||
def conn_with_oidc_user(conn, user_attrs \\ %{}) do
|
||||
def conn_with_oidc_user(conn, user_attrs \\ %{email: "user@example.com"}) do
|
||||
user = create_test_user(user_attrs)
|
||||
sign_in_user_via_oidc(conn, user)
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue