diff --git a/assets/css/app.css b/assets/css/app.css
index 1abadc7..0417463 100644
--- a/assets/css/app.css
+++ b/assets/css/app.css
@@ -5,7 +5,6 @@
@source "../css";
@source "../js";
@source "../../lib/mv_web";
-@source "../../deps/ash_authentication_phoenix";
/* A Tailwind plugin that makes "hero-#{ICON}" classes available.
The heroicons installation itself is managed by your mix.exs */
diff --git a/config/config.exs b/config/config.exs
index 43c8cf8..29a4211 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -76,25 +76,24 @@ config :esbuild,
version: "0.17.11",
mv: [
args:
- ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
+ ~w(js/app.js --bundle --target=es2022 --outdir=../priv/static/assets/js --external:/fonts/* --external:/images/*),
cd: Path.expand("../assets", __DIR__),
env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
]
# Configure tailwind (the version is required)
config :tailwind,
- version: "3.4.3",
+ version: "4.0.9",
mv: [
args: ~w(
- --config=tailwind.config.js
- --input=css/app.css
- --output=../priv/static/assets/app.css
+ --input=assets/css/app.css
+ --output=priv/static/assets/css/app.css
),
- cd: Path.expand("../assets", __DIR__)
+ cd: Path.expand("..", __DIR__)
]
# Configures Elixir's Logger
-config :logger, :console,
+config :logger, :default_formatter,
format: "$time $metadata[$level] $message\n",
metadata: [:request_id]
diff --git a/config/dev.exs b/config/dev.exs
index 17b4ce1..51ed2f1 100644
--- a/config/dev.exs
+++ b/config/dev.exs
@@ -17,10 +17,10 @@ config :mv, Mv.Repo,
# The watchers configuration can be used to run external
# watchers to your application. For example, we can use it
# to bundle .js and .css sources.
-# Binding to loopback ipv4 address prevents access from other machines.
config :mv, MvWeb.Endpoint,
+ # Binding to loopback ipv4 address prevents access from other machines.
# Change to `ip: {0, 0, 0, 0}` to allow access from other machines.
- http: [ip: {127, 0, 0, 1}, port: 4000],
+ http: [ip: {127, 0, 0, 1}, port: String.to_integer(System.get_env("PORT") || "4000")],
check_origin: false,
code_reloader: true,
debug_errors: true,
@@ -56,10 +56,11 @@ config :mv, MvWeb.Endpoint,
# Watch static and templates for browser reloading.
config :mv, MvWeb.Endpoint,
live_reload: [
+ web_console_logger: true,
patterns: [
~r"priv/static/(?!uploads/).*(js|css|png|jpeg|jpg|gif|svg)$",
~r"priv/gettext/.*(po)$",
- ~r"lib/mv_web/(controllers|live|components)/.*(ex|heex)$"
+ ~r"lib/mv_web/(?:controllers|live|components|router)/?.*\.(ex|heex)$"
]
]
@@ -67,7 +68,7 @@ config :mv, MvWeb.Endpoint,
config :mv, dev_routes: true
# Do not include metadata nor timestamps in development logs
-config :logger, :console, format: "[$level] $message\n"
+config :logger, :default_formatter, format: "[$level] $message\n"
# Set a higher stacktrace during development. Avoid configuring such
# in production as building large stacktraces may be expensive.
@@ -77,7 +78,8 @@ config :phoenix, :stacktrace_depth, 20
config :phoenix, :plug_init_mode, :runtime
config :phoenix_live_view,
- # Include HEEx debug annotations as HTML comments in rendered markup
+ # Include HEEx debug annotations as HTML comments in rendered markup.
+ # Changing this configuration will require mix clean and a full recompile.
debug_heex_annotations: true,
# Enable helpful, but potentially expensive runtime checks
enable_expensive_runtime_checks: true
diff --git a/config/runtime.exs b/config/runtime.exs
index e8ab249..464d8cd 100644
--- a/config/runtime.exs
+++ b/config/runtime.exs
@@ -116,7 +116,7 @@ if config_env() == :prod do
# domain: System.get_env("MAILGUN_DOMAIN")
#
# For this example you need include a HTTP client required by Swoosh API client.
- # Swoosh supports Hackney and Finch out of the box:
+ # Swoosh supports Hackney, Req and Finch out of the box:
#
# config :swoosh, :api_client, Swoosh.ApiClient.Hackney
#
diff --git a/lib/mv/application.ex b/lib/mv/application.ex
index e0bf462..b77107e 100644
--- a/lib/mv/application.ex
+++ b/lib/mv/application.ex
@@ -12,8 +12,6 @@ defmodule Mv.Application do
Mv.Repo,
{DNSCluster, query: Application.get_env(:mv, :dns_cluster_query) || :ignore},
{Phoenix.PubSub, name: Mv.PubSub},
- # Start the Finch HTTP client for sending emails
- {Finch, name: Mv.Finch},
{AshAuthentication.Supervisor, otp_app: :my},
# Start a worker by calling: Mv.Worker.start_link(arg)
# {Mv.Worker, arg},
diff --git a/lib/mv_web.ex b/lib/mv_web.ex
index 4254449..46e4e8b 100644
--- a/lib/mv_web.ex
+++ b/lib/mv_web.ex
@@ -38,9 +38,7 @@ defmodule MvWeb do
def controller do
quote do
- use Phoenix.Controller,
- formats: [:html, :json],
- layouts: [html: MvWeb.Layouts]
+ use Phoenix.Controller, formats: [:html, :json]
use Gettext, backend: MvWeb.Gettext
@@ -52,10 +50,10 @@ defmodule MvWeb do
def live_view do
quote do
- use Phoenix.LiveView,
- layout: {MvWeb.Layouts, :app}
+ use Phoenix.LiveView
on_mount MvWeb.LiveHelpers
+
unquote(html_helpers())
end
end
@@ -91,8 +89,9 @@ defmodule MvWeb do
# Core UI components
import MvWeb.CoreComponents
- # Shortcut for generating JS commands
+ # Common modules used in templates
alias Phoenix.LiveView.JS
+ alias MvWeb.Layouts
# Routes generation with the ~p sigil
unquote(verified_routes())
diff --git a/lib/mv_web/components/core_components.ex b/lib/mv_web/components/core_components.ex
index c35f1ce..656d3c0 100644
--- a/lib/mv_web/components/core_components.ex
+++ b/lib/mv_web/components/core_components.ex
@@ -3,92 +3,34 @@ defmodule MvWeb.CoreComponents do
Provides core UI components.
At first glance, this module may seem daunting, but its goal is to provide
- core building blocks for your application, such as modals, tables, and
- forms. The components consist mostly of markup and are well-documented
+ core building blocks for your application, such as tables, forms, and
+ inputs. The components consist mostly of markup and are well-documented
with doc strings and declarative assigns. You may customize and style
them in any way you want, based on your application growth and needs.
- The default components use Tailwind CSS, a utility-first CSS framework.
- See the [Tailwind CSS documentation](https://tailwindcss.com) to learn
- how to customize them or feel free to swap in another framework altogether.
+ The foundation for styling is Tailwind CSS, a utility-first CSS framework,
+ augmented with daisyUI, a Tailwind CSS plugin that provides UI components
+ and themes. Here are useful references:
+
+ * [daisyUI](https://daisyui.com/docs/intro/) - a good place to get
+ started and see the available components.
+
+ * [Tailwind CSS](https://tailwindcss.com) - the foundational framework
+ we build on. You will use it for layout, sizing, flexbox, grid, and
+ spacing.
+
+ * [Heroicons](https://heroicons.com) - see `icon/1` for usage.
+
+ * [Phoenix.Component](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html) -
+ the component system used by Phoenix. Some components, such as `<.link>`
+ and `<.form>`, are defined there.
- Icons are provided by [heroicons](https://heroicons.com). See `icon/1` for usage.
"""
use Phoenix.Component
use Gettext, backend: MvWeb.Gettext
alias Phoenix.LiveView.JS
- @doc """
- Renders a modal.
-
- ## Examples
-
- <.modal id="confirm-modal">
- This is a modal.
-
-
- JS commands may be passed to the `:on_cancel` to configure
- the closing/cancel event, for example:
-
- <.modal id="confirm" on_cancel={JS.navigate(~p"/posts")}>
- This is another modal.
-
-
- """
- attr :id, :string, required: true
- attr :show, :boolean, default: false
- attr :on_cancel, JS, default: %JS{}
- slot :inner_block, required: true
-
- def modal(assigns) do
- ~H"""
-
-
-
-
-
- <.focus_wrap
- id={"#{@id}-container"}
- phx-window-keydown={JS.exec("data-cancel", to: "##{@id}")}
- phx-key="escape"
- phx-click-away={JS.exec("data-cancel", to: "##{@id}")}
- class="shadow-zinc-700/10 ring-zinc-700/10 relative hidden rounded-2xl bg-white p-14 shadow-lg ring-1 transition"
- >
-
-
- <.icon name="hero-x-mark-solid" class="h-5 w-5" />
-
-
-
- {render_slot(@inner_block)}
-
-
-
-
-
-
- """
- end
-
@doc """
Renders flash notices.
@@ -114,132 +56,59 @@ defmodule MvWeb.CoreComponents do
id={@id}
phx-click={JS.push("lv:clear-flash", value: %{key: @kind}) |> hide("##{@id}")}
role="alert"
- class={[
- "fixed top-2 right-2 mr-2 w-80 sm:w-96 z-50 rounded-lg p-3 ring-1",
- @kind == :info && "bg-emerald-50 text-emerald-800 ring-emerald-500 fill-cyan-900",
- @kind == :error && "bg-rose-50 text-rose-900 shadow-md ring-rose-500 fill-rose-900"
- ]}
+ class="toast toast-top toast-end z-50"
{@rest}
>
-
- <.icon :if={@kind == :info} name="hero-information-circle-mini" class="h-4 w-4" />
- <.icon :if={@kind == :error} name="hero-exclamation-circle-mini" class="h-4 w-4" />
- {@title}
-
- {msg}
-
- <.icon name="hero-x-mark-solid" class="h-5 w-5 opacity-40 group-hover:opacity-70" />
-
-
- """
- end
-
- @doc """
- Shows the flash group with standard titles and content.
-
- ## Examples
-
- <.flash_group flash={@flash} />
- """
- attr :flash, :map, required: true, doc: "the map of flash messages"
- attr :id, :string, default: "flash-group", doc: "the optional id of flash container"
-
- def flash_group(assigns) do
- ~H"""
-
- <.flash kind={:info} title={gettext("Success!")} flash={@flash} />
- <.flash kind={:error} title={gettext("Error!")} flash={@flash} />
- <.flash
- id="client-error"
- kind={:error}
- title={gettext("We can't find the internet")}
- phx-disconnected={show(".phx-client-error #client-error")}
- phx-connected={hide("#client-error")}
- hidden
- >
- {gettext("Attempting to reconnect")}
- <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
-
-
- <.flash
- id="server-error"
- kind={:error}
- title={gettext("Something went wrong!")}
- phx-disconnected={show(".phx-server-error #server-error")}
- phx-connected={hide("#server-error")}
- hidden
- >
- {gettext("Hang in there while we get back on track")}
- <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
-
-
- """
- end
-
- @doc """
- Renders a simple form.
-
- ## Examples
-
- <.simple_form for={@form} phx-change="validate" phx-submit="save">
- <.input field={@form[:email]} label="Email"/>
- <.input field={@form[:username]} label="Username" />
- <:actions>
- <.button>Save
-
-
- """
- attr :for, :any, required: true, doc: "the data structure for the form"
- attr :as, :any, default: nil, doc: "the server side parameter to collect all input under"
-
- attr :rest, :global,
- include: ~w(autocomplete name rel action enctype method novalidate target multipart),
- doc: "the arbitrary HTML attributes to apply to the form tag"
-
- slot :inner_block, required: true
- slot :actions, doc: "the slot for form actions, such as a submit button"
-
- def simple_form(assigns) do
- ~H"""
- <.form :let={f} for={@for} as={@as} {@rest}>
-
- {render_slot(@inner_block, f)}
-
- {render_slot(action, f)}
+
+ <.icon :if={@kind == :info} name="hero-information-circle" class="size-5 shrink-0" />
+ <.icon :if={@kind == :error} name="hero-exclamation-circle" class="size-5 shrink-0" />
+
+
+
+ <.icon name="hero-x-mark" class="size-5 opacity-40 group-hover:opacity-70" />
+
-
+
"""
end
@doc """
- Renders a button.
+ Renders a button with navigation support.
## Examples
<.button>Send!
- <.button phx-click="go" class="ml-2">Send!
+ <.button phx-click="go" variant="primary">Send!
+ <.button navigate={~p"/"}>Home
"""
- attr :type, :string, default: nil
- attr :class, :string, default: nil
- attr :rest, :global, include: ~w(disabled form name value)
-
+ attr :rest, :global, include: ~w(href navigate patch method)
+ attr :variant, :string, values: ~w(primary)
slot :inner_block, required: true
- def button(assigns) do
- ~H"""
-
- {render_slot(@inner_block)}
-
- """
+ def button(%{rest: rest} = assigns) do
+ variants = %{"primary" => "btn-primary", nil => "btn-primary btn-soft"}
+ assigns = assign(assigns, :class, Map.fetch!(variants, assigns[:variant]))
+
+ if rest[:href] || rest[:navigate] || rest[:patch] do
+ ~H"""
+ <.link class={["btn", @class]} {@rest}>
+ {render_slot(@inner_block)}
+
+ """
+ else
+ ~H"""
+
+ {render_slot(@inner_block)}
+
+ """
+ end
end
@doc """
@@ -276,7 +145,7 @@ defmodule MvWeb.CoreComponents do
attr :type, :string,
default: "text",
values: ~w(checkbox color date datetime-local email file month number password
- range search select tel text textarea time url week)
+ search select tel text textarea time url week)
attr :field, Phoenix.HTML.FormField,
doc: "a form field struct retrieved from the form, for example: @form[:email]"
@@ -286,6 +155,8 @@ defmodule MvWeb.CoreComponents do
attr :prompt, :string, default: nil, doc: "the prompt for select inputs"
attr :options, :list, doc: "the options to pass to Phoenix.HTML.Form.options_for_select/2"
attr :multiple, :boolean, default: false, doc: "the multiple flag for select inputs"
+ attr :class, :string, default: nil, doc: "the input class to use over defaults"
+ attr :error_class, :string, default: nil, doc: "the input error class to use over defaults"
attr :rest, :global,
include: ~w(accept autocomplete capture cols disabled form list max maxlength min minlength
@@ -309,108 +180,95 @@ defmodule MvWeb.CoreComponents do
end)
~H"""
-
-
+
+
-
- {@label}
+
+ {@label}
+
<.error :for={msg <- @errors}>{msg}
-
+
"""
end
def input(%{type: "select"} = assigns) do
~H"""
-
- <.label for={@id}>{@label}
-
- {@prompt}
- {Phoenix.HTML.Form.options_for_select(@options, @value)}
-
+
+
+ {@label}
+
+ {@prompt}
+ {Phoenix.HTML.Form.options_for_select(@options, @value)}
+
+
<.error :for={msg <- @errors}>{msg}
-
+
"""
end
def input(%{type: "textarea"} = assigns) do
~H"""
-
- <.label for={@id}>{@label}
-
+
+
+ {@label}
+
+
<.error :for={msg <- @errors}>{msg}
-
+
"""
end
# All other inputs text, datetime-local, url, password, etc. are handled here...
def input(assigns) do
~H"""
-
- <.label for={@id}>{@label}
-
+
+
+ {@label}
+
+
<.error :for={msg <- @errors}>{msg}
-
+
"""
end
- @doc """
- Renders a label.
- """
- attr :for, :string, default: nil
- slot :inner_block, required: true
-
- def label(assigns) do
+ # Helper used by inputs to generate form errors
+ defp error(assigns) do
~H"""
-
- {render_slot(@inner_block)}
-
- """
- end
-
- @doc """
- Generates a generic error message.
- """
- slot :inner_block, required: true
-
- def error(assigns) do
- ~H"""
-
- <.icon name="hero-exclamation-circle-mini" class="mt-0.5 h-5 w-5 flex-none" />
+
+ <.icon name="hero-exclamation-circle" class="size-5" />
{render_slot(@inner_block)}
"""
@@ -427,12 +285,12 @@ defmodule MvWeb.CoreComponents do
def header(assigns) do
~H"""
-
+
-
+
{render_slot(@inner_block)}
-
+
{render_slot(@subtitle)}
@@ -473,49 +331,34 @@ defmodule MvWeb.CoreComponents do
end
~H"""
-
-
-
-
- {col[:label]}
-
- {gettext("Actions")}
-
-
-
-
-
-
-
-
-
- {render_slot(col, @row_item.(row))}
-
-
-
-
-
-
-
- {render_slot(action, @row_item.(row))}
-
-
-
-
-
-
-
+
+
+
+ {col[:label]}
+
+ {gettext("Actions")}
+
+
+
+
+
+
+ {render_slot(col, @row_item.(row))}
+
+
+
+ <%= for action <- @action do %>
+ {render_slot(action, @row_item.(row))}
+ <% end %>
+
+
+
+
+
"""
end
@@ -535,38 +378,14 @@ defmodule MvWeb.CoreComponents do
def list(assigns) do
~H"""
-
-
-
-
{item.title}
-
{render_slot(item)}
+
+
+
+
{item.title}
+
{render_slot(item)}
-
-
- """
- end
-
- @doc """
- Renders a back navigation link.
-
- ## Examples
-
- <.back navigate={~p"/posts"}>Back to posts
- """
- attr :navigate, :any, required: true
- slot :inner_block, required: true
-
- def back(assigns) do
- ~H"""
-
- <.link
- navigate={@navigate}
- class="text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700"
- >
- <.icon name="hero-arrow-left-solid" class="h-3 w-3" />
- {render_slot(@inner_block)}
-
-
+
+
"""
end
@@ -581,15 +400,15 @@ defmodule MvWeb.CoreComponents do
width, height, and background color classes.
Icons are extracted from the `deps/heroicons` directory and bundled within
- your compiled app.css by the plugin in your `assets/tailwind.config.js`.
+ your compiled app.css by the plugin in `assets/vendor/heroicons.js`.
## Examples
- <.icon name="hero-x-mark-solid" />
- <.icon name="hero-arrow-path" class="ml-1 w-3 h-3 animate-spin" />
+ <.icon name="hero-x-mark" />
+ <.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" />
"""
attr :name, :string, required: true
- attr :class, :string, default: nil
+ attr :class, :string, default: "size-4"
def icon(%{name: "hero-" <> _} = assigns) do
~H"""
@@ -604,7 +423,7 @@ defmodule MvWeb.CoreComponents do
to: selector,
time: 300,
transition:
- {"transition-all transform ease-out duration-300",
+ {"transition-all ease-out duration-300",
"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95",
"opacity-100 translate-y-0 sm:scale-100"}
)
@@ -615,37 +434,11 @@ defmodule MvWeb.CoreComponents do
to: selector,
time: 200,
transition:
- {"transition-all transform ease-in duration-200",
- "opacity-100 translate-y-0 sm:scale-100",
+ {"transition-all ease-in duration-200", "opacity-100 translate-y-0 sm:scale-100",
"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"}
)
end
- def show_modal(js \\ %JS{}, id) when is_binary(id) do
- js
- |> JS.show(to: "##{id}")
- |> JS.show(
- to: "##{id}-bg",
- time: 300,
- transition: {"transition-all transform ease-out duration-300", "opacity-0", "opacity-100"}
- )
- |> show("##{id}-container")
- |> JS.add_class("overflow-hidden", to: "body")
- |> JS.focus_first(to: "##{id}-content")
- end
-
- def hide_modal(js \\ %JS{}, id) do
- js
- |> JS.hide(
- to: "##{id}-bg",
- transition: {"transition-all transform ease-in duration-200", "opacity-100", "opacity-0"}
- )
- |> hide("##{id}-container")
- |> JS.hide(to: "##{id}", transition: {"block", "block", "hidden"})
- |> JS.remove_class("overflow-hidden", to: "body")
- |> JS.pop_focus()
- end
-
@doc """
Translates an error message using gettext.
"""
diff --git a/lib/mv_web/components/layouts.ex b/lib/mv_web/components/layouts.ex
index 1ced765..ba8ec67 100644
--- a/lib/mv_web/components/layouts.ex
+++ b/lib/mv_web/components/layouts.ex
@@ -40,6 +40,15 @@ defmodule MvWeb.Layouts do
+
+
+
Website
diff --git a/lib/mv_web/components/layouts/app.html.heex b/lib/mv_web/components/layouts/app.html.heex
deleted file mode 100644
index 54258db..0000000
--- a/lib/mv_web/components/layouts/app.html.heex
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
-
-
-
-
- v{Application.spec(:phoenix, :vsn)}
-
-
-
-
-
-
-
- <.flash_group flash={@flash} />
- {@inner_content}
-
-
diff --git a/lib/mv_web/components/layouts/root.html.heex b/lib/mv_web/components/layouts/root.html.heex
index 9857506..5ee0fef 100644
--- a/lib/mv_web/components/layouts/root.html.heex
+++ b/lib/mv_web/components/layouts/root.html.heex
@@ -1,5 +1,5 @@
-
+
{Application.get_env(:live_debugger, :live_debugger_tags)}
@@ -9,11 +9,29 @@
<.live_title default="Mv" suffix=" ยท Phoenix Framework">
{assigns[:page_title]}
-
-
+
-
+
{@inner_content}
diff --git a/lib/mv_web/endpoint.ex b/lib/mv_web/endpoint.ex
index 090e54c..97dcae4 100644
--- a/lib/mv_web/endpoint.ex
+++ b/lib/mv_web/endpoint.ex
@@ -17,12 +17,13 @@ defmodule MvWeb.Endpoint do
# Serve at "/" the static files from "priv/static" directory.
#
- # You should set gzip to true if you are running phx.digest
- # when deploying your static files in production.
+ # When code reloading is disabled (e.g., in production),
+ # the `gzip` option is enabled to serve compressed
+ # static files generated by running `phx.digest`.
plug Plug.Static,
at: "/",
from: :mv,
- gzip: false,
+ gzip: not code_reloading?,
only: MvWeb.static_paths()
if Code.ensure_loaded?(Tidewave) do
diff --git a/lib/mv_web/member_live/form_component.ex b/lib/mv_web/live/member_live/form.ex
similarity index 80%
rename from lib/mv_web/member_live/form_component.ex
rename to lib/mv_web/live/member_live/form.ex
index 5535d1a..a526fc3 100644
--- a/lib/mv_web/member_live/form_component.ex
+++ b/lib/mv_web/live/member_live/form.ex
@@ -1,43 +1,18 @@
-defmodule MvWeb.MemberLive.FormComponent do
- use MvWeb, :live_component
-
- @impl true
- def mount(socket) do
- {:ok, property_types} = Mv.Membership.list_property_types()
-
- initial_properties =
- Enum.map(property_types, fn pt ->
- %{
- "property_type_id" => pt.id,
- "value" => %{
- "type" => pt.value_type,
- "value" => nil,
- "_union_type" => Atom.to_string(pt.value_type)
- }
- }
- end)
-
- {:ok, assign(socket, property_types: property_types, initial_properties: initial_properties)}
- end
+defmodule MvWeb.MemberLive.Form do
+ use MvWeb, :live_view
@impl true
def render(assigns) do
~H"""
-
+
<.header>
- {@title}
+ {@page_title}
<:subtitle>
{gettext("Use this form to manage member records and their properties.")}
- <.simple_form
- for={@form}
- id="member-form"
- phx-target={@myself}
- phx-change="validate"
- phx-submit="save"
- >
+ <.form for={@form} id="member-form" phx-change="validate" phx-submit="save">
<.input field={@form[:first_name]} label={gettext("First Name")} required />
<.input field={@form[:last_name]} label={gettext("Last Name")} required />
<.input field={@form[:email]} label={gettext("Email")} required type="email" />
@@ -71,22 +46,53 @@ defmodule MvWeb.MemberLive.FormComponent do
/>
- <:actions>
- <.button phx-disable-with={gettext("Saving...")}>{gettext("Save Member")}
-
-
-
+ <.button phx-disable-with={gettext("Saving...")} variant="primary">
+ {gettext("Save Member")}
+
+ <.button navigate={return_path(@return_to, @member)}>{gettext("Cancel")}
+
+
"""
end
@impl true
- def update(assigns, socket) do
+ def mount(params, _session, socket) do
+ {:ok, property_types} = Mv.Membership.list_property_types()
+
+ initial_properties =
+ Enum.map(property_types, fn pt ->
+ %{
+ "property_type_id" => pt.id,
+ "value" => %{
+ "type" => pt.value_type,
+ "value" => nil,
+ "_union_type" => Atom.to_string(pt.value_type)
+ }
+ }
+ end)
+
+ member =
+ case params["id"] do
+ nil -> nil
+ id -> Ash.get!(Mv.Membership.Member, id)
+ end
+
+ action = if is_nil(member), do: "New", else: "Edit"
+ page_title = action <> " " <> "Member"
+
{:ok,
socket
- |> assign(assigns)
+ |> assign(:return_to, return_to(params["return_to"]))
+ |> assign(:property_types, property_types)
+ |> assign(:initial_properties, initial_properties)
+ |> assign(member: member)
+ |> assign(:page_title, page_title)
|> assign_form()}
end
+ defp return_to("show"), do: "show"
+ defp return_to(_), do: "index"
+
@impl true
def handle_event("validate", %{"member" => member_params}, socket) do
{:noreply, assign(socket, form: AshPhoenix.Form.validate(socket.assigns.form, member_params))}
@@ -107,7 +113,7 @@ defmodule MvWeb.MemberLive.FormComponent do
socket =
socket
|> put_flash(:info, gettext("Member %{action} successfully", action: action))
- |> push_patch(to: socket.assigns.patch)
+ |> push_navigate(to: return_path(socket.assigns.return_to, member))
{:noreply, socket}
@@ -175,4 +181,7 @@ defmodule MvWeb.MemberLive.FormComponent do
assign(socket, form: to_form(form))
end
+
+ defp return_path("index", _member), do: ~p"/members"
+ defp return_path("show", member), do: ~p"/members/#{member.id}"
end
diff --git a/lib/mv_web/live/member_live/index.ex b/lib/mv_web/live/member_live/index.ex
new file mode 100644
index 0000000..1cff898
--- /dev/null
+++ b/lib/mv_web/live/member_live/index.ex
@@ -0,0 +1,65 @@
+defmodule MvWeb.MemberLive.Index do
+ use MvWeb, :live_view
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+
+ <.header>
+ {gettext("Listing Members")}
+ <:actions>
+ <.button variant="primary" navigate={~p"/members/new"}>
+ <.icon name="hero-plus" /> {gettext("New Member")}
+
+
+
+
+ <.table
+ id="members"
+ rows={@streams.members}
+ row_click={fn {_id, member} -> JS.navigate(~p"/members/#{member}") end}
+ >
+
+ <:col :let={{_id, member}} label={gettext("First Name")}>{member.first_name}
+ <:col :let={{_id, member}} label={gettext("Last Name")}>{member.last_name}
+ <:col :let={{_id, member}} label={gettext("Email")}>{member.email}
+ <:col :let={{_id, member}} label={gettext("City")}>{member.city}
+ <:col :let={{_id, member}} label={gettext("Join Date")}>{member.join_date}
+
+ <:action :let={{_id, member}}>
+
+ <.link navigate={~p"/members/#{member}"}>{gettext("Show")}
+
+
+ <.link navigate={~p"/members/#{member}/edit"}>{gettext("Edit")}
+
+
+ <:action :let={{id, member}}>
+ <.link
+ phx-click={JS.push("delete", value: %{id: member.id}) |> hide("##{id}")}
+ data-confirm={gettext("Are you sure?")}
+ >
+ {gettext("Delete")}
+
+
+
+
+ """
+ end
+
+ @impl true
+ def mount(_params, _session, socket) do
+ {:ok,
+ socket
+ |> assign(:page_title, gettext("Listing Members"))
+ |> stream(:members, Ash.read!(Mv.Membership.Member))}
+ end
+
+ @impl true
+ def handle_event("delete", %{"id" => id}, socket) do
+ member = Ash.get!(Mv.Membership.Member, id)
+ Ash.destroy!(member)
+
+ {:noreply, stream_delete(socket, :members, member)}
+ end
+end
diff --git a/lib/mv_web/live/member_live/show.ex b/lib/mv_web/live/member_live/show.ex
new file mode 100644
index 0000000..304709c
--- /dev/null
+++ b/lib/mv_web/live/member_live/show.ex
@@ -0,0 +1,82 @@
+defmodule MvWeb.MemberLive.Show do
+ use MvWeb, :live_view
+ import Ash.Query
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+
+ <.header>
+ {@member.first_name} {@member.last_name}
+ <:subtitle>{gettext("This is a member record from your database.")}
+
+ <:actions>
+ <.button navigate={~p"/members"}>
+ <.icon name="hero-arrow-left" />
+
+ <.button variant="primary" navigate={~p"/members/#{@member}/edit?return_to=show"}>
+ <.icon name="hero-pencil-square" /> {gettext("Edit Member")}
+
+
+
+
+ <.list>
+ <:item title={gettext("Id")}>{@member.id}
+ <:item title={gettext("First Name")}>{@member.first_name}
+ <:item title={gettext("Last Name")}>{@member.last_name}
+ <:item title={gettext("Email")}>{@member.email}
+ <:item title={gettext("Birth Date")}>{@member.birth_date}
+ <:item title={gettext("Paid")}>
+ {if @member.paid, do: gettext("Yes"), else: gettext("No")}
+
+ <:item title={gettext("Phone Number")}>{@member.phone_number}
+ <:item title={gettext("Join Date")}>{@member.join_date}
+ <:item title={gettext("Exit Date")}>{@member.exit_date}
+ <:item title={gettext("Notes")}>{@member.notes}
+ <:item title={gettext("City")}>{@member.city}
+ <:item title={gettext("Street")}>{@member.street}
+ <:item title={gettext("House Number")}>{@member.house_number}
+ <:item title={gettext("Postal Code")}>{@member.postal_code}
+
+
+ {gettext("Custom Properties")}
+ <.generic_list items={
+ Enum.map(@member.properties, fn p ->
+ {
+ # name
+ p.property_type && p.property_type.name,
+ # value
+ case p.value do
+ %{value: v} -> v
+ v -> v
+ end
+ }
+ end)
+ } />
+
+ """
+ end
+
+ @impl true
+ def mount(_params, _session, socket) do
+ {:ok, socket}
+ end
+
+ @impl true
+ def handle_params(%{"id" => id}, _, socket) do
+ query =
+ Mv.Membership.Member
+ |> filter(id == ^id)
+ |> load(properties: [:property_type])
+
+ member = Ash.read_one!(query)
+
+ {:noreply,
+ socket
+ |> assign(:page_title, page_title(socket.assigns.live_action))
+ |> assign(:member, member)}
+ end
+
+ defp page_title(:show), do: gettext("Show Member")
+ defp page_title(:edit), do: gettext("Edit Member")
+end
diff --git a/lib/mv_web/live/property_live/form.ex b/lib/mv_web/live/property_live/form.ex
new file mode 100644
index 0000000..42814a3
--- /dev/null
+++ b/lib/mv_web/live/property_live/form.ex
@@ -0,0 +1,251 @@
+defmodule MvWeb.PropertyLive.Form do
+ use MvWeb, :live_view
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+
+ <.header>
+ {@page_title}
+ <:subtitle>{gettext("Use this form to manage property records in your database.")}
+
+
+ <.form for={@form} id="property-form" phx-change="validate" phx-submit="save">
+
+ <.input
+ field={@form[:property_type_id]}
+ type="select"
+ label={gettext("Property type")}
+ options={property_type_options(@property_types)}
+ prompt={gettext("Choose a property type")}
+ />
+
+
+ <.input
+ field={@form[:member_id]}
+ type="select"
+ label={gettext("Member")}
+ options={member_options(@members)}
+ prompt={gettext("Choose a member")}
+ />
+
+
+ <%= if @selected_property_type do %>
+ <.union_value_input form={@form} property_type={@selected_property_type} />
+ <% else %>
+
+ {gettext("Please select a property type first")}
+
+ <% end %>
+
+ <.button phx-disable-with={gettext("Saving...")} variant="primary">
+ {gettext("Save Property")}
+
+ <.button navigate={return_path(@return_to, @property)}>{gettext("Cancel")}
+
+
+ """
+ end
+
+ # Helper function for Union-Value Input
+ defp union_value_input(assigns) do
+ # Extract the current value from the Property
+ current_value = extract_current_value(assigns.form.data, assigns.property_type.value_type)
+ assigns = assign(assigns, :current_value, current_value)
+
+ ~H"""
+
+
+ {gettext("Value")}
+
+
+ <%= case @property_type.value_type do %>
+ <% :string -> %>
+ <.inputs_for :let={value_form} field={@form[:value]}>
+ <.input field={value_form[:value]} type="text" label="" value={@current_value} />
+
+
+ <% :integer -> %>
+ <.inputs_for :let={value_form} field={@form[:value]}>
+ <.input field={value_form[:value]} type="number" label="" value={@current_value} />
+
+
+ <% :boolean -> %>
+ <.inputs_for :let={value_form} field={@form[:value]}>
+ <.input field={value_form[:value]} type="checkbox" label="" checked={@current_value} />
+
+
+ <% :date -> %>
+ <.inputs_for :let={value_form} field={@form[:value]}>
+ <.input
+ field={value_form[:value]}
+ type="date"
+ label=""
+ value={format_date_value(@current_value)}
+ />
+
+
+ <% :email -> %>
+ <.inputs_for :let={value_form} field={@form[:value]}>
+ <.input field={value_form[:value]} type="email" label="" value={@current_value} />
+
+
+ <% _ -> %>
+
+ {gettext("Unsupported value type: %{type}", type: @property_type.value_type)}
+
+ <% end %>
+
+ """
+ end
+
+ # Helper function to extract the current value from the Property
+ defp extract_current_value(
+ %Mv.Membership.Property{value: %Ash.Union{value: value}},
+ _value_type
+ ) do
+ value
+ end
+
+ defp extract_current_value(_data, _value_type) do
+ nil
+ end
+
+ # Helper function to format Date values for HTML input
+ defp format_date_value(%Date{} = date) do
+ Date.to_iso8601(date)
+ end
+
+ defp format_date_value(nil), do: ""
+
+ defp format_date_value(date) when is_binary(date) do
+ case Date.from_iso8601(date) do
+ {:ok, parsed_date} -> Date.to_iso8601(parsed_date)
+ _ -> ""
+ end
+ end
+
+ defp format_date_value(_), do: ""
+
+ @impl true
+ def mount(params, _session, socket) do
+ property =
+ case params["id"] do
+ nil -> nil
+ id -> Ash.get!(Mv.Membership.Property, id) |> Ash.load!([:property_type])
+ end
+
+ action = if is_nil(property), do: "New", else: "Edit"
+ page_title = action <> " " <> "Property"
+
+ # Load all PropertyTypes and Members for the selection fields
+ property_types = Ash.read!(Mv.Membership.PropertyType)
+ members = Ash.read!(Mv.Membership.Member)
+
+ {:ok,
+ socket
+ |> assign(:return_to, return_to(params["return_to"]))
+ |> assign(property: property)
+ |> assign(:page_title, page_title)
+ |> assign(:property_types, property_types)
+ |> assign(:members, members)
+ |> assign(:selected_property_type, property && property.property_type)
+ |> assign_form()}
+ end
+
+ defp return_to("show"), do: "show"
+ defp return_to(_), do: "index"
+
+ @impl true
+ def handle_event("validate", %{"property" => property_params}, socket) do
+ # Find the selected PropertyType
+ selected_property_type =
+ case property_params["property_type_id"] do
+ "" -> nil
+ nil -> nil
+ id -> Enum.find(socket.assigns.property_types, &(&1.id == id))
+ end
+
+ # Set the Union type based on the selected PropertyType
+ updated_params =
+ if selected_property_type do
+ union_type = to_string(selected_property_type.value_type)
+ put_in(property_params, ["value", "_union_type"], union_type)
+ else
+ property_params
+ end
+
+ {:noreply,
+ socket
+ |> assign(:selected_property_type, selected_property_type)
+ |> assign(form: AshPhoenix.Form.validate(socket.assigns.form, updated_params))}
+ end
+
+ def handle_event("save", %{"property" => property_params}, socket) do
+ # Set the Union type based on the selected PropertyType
+ updated_params =
+ if socket.assigns.selected_property_type do
+ union_type = to_string(socket.assigns.selected_property_type.value_type)
+ put_in(property_params, ["value", "_union_type"], union_type)
+ else
+ property_params
+ end
+
+ case AshPhoenix.Form.submit(socket.assigns.form, params: updated_params) do
+ {:ok, property} ->
+ notify_parent({:saved, property})
+
+ action =
+ case socket.assigns.form.source.type do
+ :create -> gettext("create")
+ :update -> gettext("update")
+ other -> to_string(other)
+ end
+
+ socket =
+ socket
+ |> put_flash(:info, gettext("Property %{action} successfully", action: action))
+ |> push_navigate(to: return_path(socket.assigns.return_to, property))
+
+ {:noreply, socket}
+
+ {:error, form} ->
+ {:noreply, assign(socket, form: form)}
+ end
+ end
+
+ defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
+
+ defp assign_form(%{assigns: %{property: property}} = socket) do
+ form =
+ if property do
+ # Determine the Union type based on the property_type
+ union_type = property.property_type && property.property_type.value_type
+
+ params =
+ if union_type do
+ %{"value" => %{"_union_type" => to_string(union_type)}}
+ else
+ %{}
+ end
+
+ AshPhoenix.Form.for_update(property, :update, as: "property", params: params)
+ else
+ AshPhoenix.Form.for_create(Mv.Membership.Property, :create, as: "property")
+ end
+
+ assign(socket, form: to_form(form))
+ end
+
+ defp return_path("index", _property), do: ~p"/properties"
+ defp return_path("show", property), do: ~p"/properties/#{property.id}"
+
+ # Helper functions for selection options
+ defp property_type_options(property_types) do
+ Enum.map(property_types, &{&1.name, &1.id})
+ end
+
+ defp member_options(members) do
+ Enum.map(members, &{"#{&1.first_name} #{&1.last_name}", &1.id})
+ end
+end
diff --git a/lib/mv_web/live/property_live/index.ex b/lib/mv_web/live/property_live/index.ex
new file mode 100644
index 0000000..7e27344
--- /dev/null
+++ b/lib/mv_web/live/property_live/index.ex
@@ -0,0 +1,60 @@
+defmodule MvWeb.PropertyLive.Index do
+ use MvWeb, :live_view
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+
+ <.header>
+ Listing Properties
+ <:actions>
+ <.button variant="primary" navigate={~p"/properties/new"}>
+ <.icon name="hero-plus" /> New Property
+
+
+
+
+ <.table
+ id="properties"
+ rows={@streams.properties}
+ row_click={fn {_id, property} -> JS.navigate(~p"/properties/#{property}") end}
+ >
+ <:col :let={{_id, property}} label="Id">{property.id}
+
+ <:action :let={{_id, property}}>
+
+ <.link navigate={~p"/properties/#{property}"}>Show
+
+
+ <.link navigate={~p"/properties/#{property}/edit"}>Edit
+
+
+ <:action :let={{id, property}}>
+ <.link
+ phx-click={JS.push("delete", value: %{id: property.id}) |> hide("##{id}")}
+ data-confirm="Are you sure?"
+ >
+ Delete
+
+
+
+
+ """
+ end
+
+ @impl true
+ def mount(_params, _session, socket) do
+ {:ok,
+ socket
+ |> assign(:page_title, "Listing Properties")
+ |> stream(:properties, Ash.read!(Mv.Membership.Property))}
+ end
+
+ @impl true
+ def handle_event("delete", %{"id" => id}, socket) do
+ property = Ash.get!(Mv.Membership.Property, id)
+ Ash.destroy!(property)
+
+ {:noreply, stream_delete(socket, :properties, property)}
+ end
+end
diff --git a/lib/mv_web/live/property_live/show.ex b/lib/mv_web/live/property_live/show.ex
new file mode 100644
index 0000000..6d9bb1a
--- /dev/null
+++ b/lib/mv_web/live/property_live/show.ex
@@ -0,0 +1,44 @@
+defmodule MvWeb.PropertyLive.Show do
+ use MvWeb, :live_view
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+
+ <.header>
+ Property {@property.id}
+ <:subtitle>This is a property record from your database.
+
+ <:actions>
+ <.button navigate={~p"/properties"}>
+ <.icon name="hero-arrow-left" />
+
+ <.button variant="primary" navigate={~p"/properties/#{@property}/edit?return_to=show"}>
+ <.icon name="hero-pencil-square" /> Edit Property
+
+
+
+
+ <.list>
+ <:item title="Id">{@property.id}
+
+
+ """
+ end
+
+ @impl true
+ def mount(_params, _session, socket) do
+ {:ok, socket}
+ end
+
+ @impl true
+ def handle_params(%{"id" => id}, _, socket) do
+ {:noreply,
+ socket
+ |> assign(:page_title, page_title(socket.assigns.live_action))
+ |> assign(:property, Ash.get!(Mv.Membership.Property, id))}
+ end
+
+ defp page_title(:show), do: "Show Property"
+ defp page_title(:edit), do: "Edit Property"
+end
diff --git a/lib/mv_web/live/property_type_live/form.ex b/lib/mv_web/live/property_type_live/form.ex
new file mode 100644
index 0000000..87cac94
--- /dev/null
+++ b/lib/mv_web/live/property_type_live/form.ex
@@ -0,0 +1,105 @@
+defmodule MvWeb.PropertyTypeLive.Form do
+ use MvWeb, :live_view
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+
+ <.header>
+ {@page_title}
+ <:subtitle>
+ {gettext("Use this form to manage property_type records in your database.")}
+
+
+
+ <.form for={@form} id="property_type-form" phx-change="validate" phx-submit="save">
+ <.input field={@form[:name]} type="text" label={gettext("Name")} />
+ <.input
+ field={@form[:value_type]}
+ type="select"
+ label={gettext("Value type")}
+ options={
+ Ash.Resource.Info.attribute(Mv.Membership.PropertyType, :value_type).constraints[:one_of]
+ }
+ />
+ <.input field={@form[:description]} type="text" label={gettext("Description")} />
+ <.input field={@form[:immutable]} type="checkbox" label={gettext("Immutable")} />
+ <.input field={@form[:required]} type="checkbox" label={gettext("Required")} />
+
+ <.button phx-disable-with={gettext("Saving...")} variant="primary">
+ {gettext("Save Property type")}
+
+ <.button navigate={return_path(@return_to, @property_type)}>{gettext("Cancel")}
+
+
+ """
+ end
+
+ @impl true
+ def mount(params, _session, socket) do
+ property_type =
+ case params["id"] do
+ nil -> nil
+ id -> Ash.get!(Mv.Membership.PropertyType, id)
+ end
+
+ action = if is_nil(property_type), do: "New", else: "Edit"
+ page_title = action <> " " <> "Property type"
+
+ {:ok,
+ socket
+ |> assign(:return_to, return_to(params["return_to"]))
+ |> assign(property_type: property_type)
+ |> assign(:page_title, page_title)
+ |> assign_form()}
+ end
+
+ defp return_to("show"), do: "show"
+ defp return_to(_), do: "index"
+
+ @impl true
+ def handle_event("validate", %{"property_type" => property_type_params}, socket) do
+ {:noreply,
+ assign(socket, form: AshPhoenix.Form.validate(socket.assigns.form, property_type_params))}
+ end
+
+ def handle_event("save", %{"property_type" => property_type_params}, socket) do
+ case AshPhoenix.Form.submit(socket.assigns.form, params: property_type_params) do
+ {:ok, property_type} ->
+ notify_parent({:saved, property_type})
+
+ action =
+ case socket.assigns.form.source.type do
+ :create -> gettext("create")
+ :update -> gettext("update")
+ other -> to_string(other)
+ end
+
+ socket =
+ socket
+ |> put_flash(:info, gettext("Property type %{action} successfully", action: action))
+ |> push_navigate(to: return_path(socket.assigns.return_to, property_type))
+
+ {:noreply, socket}
+
+ {:error, form} ->
+ {:noreply, assign(socket, form: form)}
+ end
+ end
+
+ defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
+
+ defp assign_form(%{assigns: %{property_type: property_type}} = socket) do
+ form =
+ if property_type do
+ AshPhoenix.Form.for_update(property_type, :update, as: "property_type")
+ else
+ AshPhoenix.Form.for_create(Mv.Membership.PropertyType, :create, as: "property_type")
+ end
+
+ assign(socket, form: to_form(form))
+ end
+
+ defp return_path("index", _property_type), do: ~p"/property_types"
+ defp return_path("show", property_type), do: ~p"/property_types/#{property_type.id}"
+end
diff --git a/lib/mv_web/live/property_type_live/index.ex b/lib/mv_web/live/property_type_live/index.ex
new file mode 100644
index 0000000..ed9ff7d
--- /dev/null
+++ b/lib/mv_web/live/property_type_live/index.ex
@@ -0,0 +1,64 @@
+defmodule MvWeb.PropertyTypeLive.Index do
+ use MvWeb, :live_view
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+
+ <.header>
+ Listing Property types
+ <:actions>
+ <.button variant="primary" navigate={~p"/property_types/new"}>
+ <.icon name="hero-plus" /> New Property type
+
+
+
+
+ <.table
+ id="property_types"
+ rows={@streams.property_types}
+ row_click={fn {_id, property_type} -> JS.navigate(~p"/property_types/#{property_type}") end}
+ >
+ <:col :let={{_id, property_type}} label="Id">{property_type.id}
+
+ <:col :let={{_id, property_type}} label="Name">{property_type.name}
+
+ <:col :let={{_id, property_type}} label="Description">{property_type.description}
+
+ <:action :let={{_id, property_type}}>
+
+ <.link navigate={~p"/property_types/#{property_type}"}>Show
+
+
+ <.link navigate={~p"/property_types/#{property_type}/edit"}>Edit
+
+
+ <:action :let={{id, property_type}}>
+ <.link
+ phx-click={JS.push("delete", value: %{id: property_type.id}) |> hide("##{id}")}
+ data-confirm="Are you sure?"
+ >
+ Delete
+
+
+
+
+ """
+ end
+
+ @impl true
+ def mount(_params, _session, socket) do
+ {:ok,
+ socket
+ |> assign(:page_title, "Listing Property types")
+ |> stream(:property_types, Ash.read!(Mv.Membership.PropertyType))}
+ end
+
+ @impl true
+ def handle_event("delete", %{"id" => id}, socket) do
+ property_type = Ash.get!(Mv.Membership.PropertyType, id)
+ Ash.destroy!(property_type)
+
+ {:noreply, stream_delete(socket, :property_types, property_type)}
+ end
+end
diff --git a/lib/mv_web/live/property_type_live/show.ex b/lib/mv_web/live/property_type_live/show.ex
new file mode 100644
index 0000000..027baa6
--- /dev/null
+++ b/lib/mv_web/live/property_type_live/show.ex
@@ -0,0 +1,43 @@
+defmodule MvWeb.PropertyTypeLive.Show do
+ use MvWeb, :live_view
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+
+ <.header>
+ Property type {@property_type.id}
+ <:subtitle>This is a property_type record from your database.
+
+ <:actions>
+ <.button navigate={~p"/property_types"}>
+ <.icon name="hero-arrow-left" />
+
+ <.button
+ variant="primary"
+ navigate={~p"/property_types/#{@property_type}/edit?return_to=show"}
+ >
+ <.icon name="hero-pencil-square" /> Edit Property type
+
+
+
+
+ <.list>
+ <:item title="Id">{@property_type.id}
+
+ <:item title="Name">{@property_type.name}
+
+ <:item title="Description">{@property_type.description}
+
+
+ """
+ end
+
+ @impl true
+ def mount(%{"id" => id}, _session, socket) do
+ {:ok,
+ socket
+ |> assign(:page_title, "Show Property type")
+ |> assign(:property_type, Ash.get!(Mv.Membership.PropertyType, id))}
+ end
+end
diff --git a/lib/mv_web/member_live/index.ex b/lib/mv_web/member_live/index.ex
deleted file mode 100644
index 452ebab..0000000
--- a/lib/mv_web/member_live/index.ex
+++ /dev/null
@@ -1,104 +0,0 @@
-defmodule MvWeb.MemberLive.Index do
- use MvWeb, :live_view
-
- @impl true
- def render(assigns) do
- ~H"""
- <.header>
- {gettext("Listing Members")}
- <:actions>
- <.link patch={~p"/members/new"}>
- <.button>{gettext("New Member")}
-
-
-
-
- <.table
- id="members"
- rows={@streams.members}
- row_click={fn {_id, member} -> JS.navigate(~p"/members/#{member}") end}
- >
-
- <:col :let={{_id, member}} label={gettext("First Name")}>{member.first_name}
- <:col :let={{_id, member}} label={gettext("Last Name")}>{member.last_name}
- <:col :let={{_id, member}} label={gettext("Email")}>{member.email}
- <:col :let={{_id, member}} label={gettext("City")}>{member.city}
- <:col :let={{_id, member}} label={gettext("Join Date")}>{member.join_date}
-
- <:action :let={{_id, member}}>
-
- <.link navigate={~p"/members/#{member}"}>{gettext("Show")}
-
-
- <.link patch={~p"/members/#{member}/edit"}>{gettext("Edit")}
-
-
- <:action :let={{id, member}}>
- <.link
- phx-click={JS.push("delete", value: %{id: member.id}) |> hide("##{id}")}
- data-confirm={gettext("Are you sure?")}
- >
- {gettext("Delete")}
-
-
-
-
- <.modal
- :if={@live_action in [:new, :edit]}
- id="member-modal"
- show
- on_cancel={JS.patch(~p"/members")}
- >
- <.live_component
- module={MvWeb.MemberLive.FormComponent}
- id={(@member && @member.id) || :new}
- title={@page_title}
- action={@live_action}
- member={@member}
- patch={~p"/members"}
- />
-
- """
- end
-
- @impl true
- def mount(_params, _session, socket) do
- {:ok, stream(socket, :members, Ash.read!(Mv.Membership.Member))}
- end
-
- @impl true
- def handle_params(params, _url, socket) do
- {:noreply, apply_action(socket, socket.assigns.live_action, params)}
- end
-
- defp apply_action(socket, :edit, %{"id" => id}) do
- socket
- |> assign(:page_title, gettext("Edit Member"))
- |> assign(:member, Ash.get!(Mv.Membership.Member, id))
- end
-
- defp apply_action(socket, :new, _params) do
- socket
- |> assign(:page_title, gettext("New Member"))
- |> assign(:member, nil)
- end
-
- defp apply_action(socket, :index, _params) do
- socket
- |> assign(:page_title, gettext("Listing Members"))
- |> assign(:member, nil)
- end
-
- @impl true
- def handle_info({MvWeb.MemberLive.FormComponent, {:saved, member}}, socket) do
- {:noreply, stream_insert(socket, :members, member)}
- end
-
- @impl true
- def handle_event("delete", %{"id" => id}, socket) do
- member = Ash.get!(Mv.Membership.Member, id)
- Ash.destroy!(member)
-
- {:noreply, stream_delete(socket, :members, member)}
- end
-end
diff --git a/lib/mv_web/member_live/show.ex b/lib/mv_web/member_live/show.ex
deleted file mode 100644
index 612abd6..0000000
--- a/lib/mv_web/member_live/show.ex
+++ /dev/null
@@ -1,94 +0,0 @@
-defmodule MvWeb.MemberLive.Show do
- use MvWeb, :live_view
- import Ash.Query
-
- @impl true
- def render(assigns) do
- ~H"""
- <.header>
- {@member.first_name} {@member.last_name}
- <:subtitle>{gettext("This is a member record from your database.")}
-
- <:actions>
- <.link patch={~p"/members/#{@member}/show/edit"} phx-click={JS.push_focus()}>
- <.button>{gettext("Edit member")}
-
-
-
-
- <.list>
- <:item title={gettext("Id")}>{@member.id}
- <:item title={gettext("First Name")}>{@member.first_name}
- <:item title={gettext("Last Name")}>{@member.last_name}
- <:item title={gettext("Email")}>{@member.email}
- <:item title={gettext("Birth Date")}>{@member.birth_date}
- <:item title={gettext("Paid")}>
- {if @member.paid, do: gettext("Yes"), else: gettext("No")}
-
- <:item title={gettext("Phone Number")}>{@member.phone_number}
- <:item title={gettext("Join Date")}>{@member.join_date}
- <:item title={gettext("Exit Date")}>{@member.exit_date}
- <:item title={gettext("Notes")}>{@member.notes}
- <:item title={gettext("City")}>{@member.city}
- <:item title={gettext("Street")}>{@member.street}
- <:item title={gettext("House Number")}>{@member.house_number}
- <:item title={gettext("Postal Code")}>{@member.postal_code}
-
-
- {gettext("Custom Properties")}
- <.generic_list items={
- Enum.map(@member.properties, fn p ->
- {
- # name
- p.property_type && p.property_type.name,
- # value
- case p.value do
- %{value: v} -> v
- v -> v
- end
- }
- end)
- } />
- <.back navigate={~p"/members"}>{gettext("Back to members")}
-
- <.modal
- :if={@live_action == :edit}
- id="member-modal"
- show
- on_cancel={JS.patch(~p"/members/#{@member}")}
- >
- <.live_component
- module={MvWeb.MemberLive.FormComponent}
- id={@member.id}
- title={@page_title}
- action={@live_action}
- member={@member}
- patch={~p"/members/#{@member}"}
- />
-
- """
- end
-
- @impl true
- def mount(_params, _session, socket) do
- {:ok, socket}
- end
-
- @impl true
- def handle_params(%{"id" => id}, _, socket) do
- query =
- Mv.Membership.Member
- |> filter(id == ^id)
- |> load(properties: [:property_type])
-
- member = Ash.read_one!(query)
-
- {:noreply,
- socket
- |> assign(:page_title, page_title(socket.assigns.live_action))
- |> assign(:member, member)}
- end
-
- defp page_title(:show), do: gettext("Show Member")
- defp page_title(:edit), do: gettext("Edit Member")
-end
diff --git a/lib/mv_web/property_live/form_component.ex b/lib/mv_web/property_live/form_component.ex
deleted file mode 100644
index 73461be..0000000
--- a/lib/mv_web/property_live/form_component.ex
+++ /dev/null
@@ -1,77 +0,0 @@
-defmodule MvWeb.PropertyLive.FormComponent do
- use MvWeb, :live_component
-
- @impl true
- def render(assigns) do
- ~H"""
-
- <.header>
- {@title}
- <:subtitle>Use this form to manage property records in your database.
-
-
- <.simple_form
- for={@form}
- id="property-form"
- phx-target={@myself}
- phx-change="validate"
- phx-submit="save"
- >
- <.input field={@form[:value]} type="text" label="Value" /><.input
- field={@form[:member_id]}
- type="text"
- label="Member"
- /><.input field={@form[:property_type_id]} type="text" label="Property type" />
-
- <:actions>
- <.button phx-disable-with="Saving...">Save Property
-
-
-
- """
- end
-
- @impl true
- def update(assigns, socket) do
- {:ok,
- socket
- |> assign(assigns)
- |> assign_form()}
- end
-
- @impl true
- def handle_event("validate", %{"property" => property_params}, socket) do
- {:noreply,
- assign(socket, form: AshPhoenix.Form.validate(socket.assigns.form, property_params))}
- end
-
- def handle_event("save", %{"property" => property_params}, socket) do
- case AshPhoenix.Form.submit(socket.assigns.form, params: property_params) do
- {:ok, property} ->
- notify_parent({:saved, property})
-
- socket =
- socket
- |> put_flash(:info, "Property #{socket.assigns.form.source.type}d successfully")
- |> push_patch(to: socket.assigns.patch)
-
- {:noreply, socket}
-
- {:error, form} ->
- {:noreply, assign(socket, form: form)}
- end
- end
-
- defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
-
- defp assign_form(%{assigns: %{property: property}} = socket) do
- form =
- if property do
- AshPhoenix.Form.for_update(property, :update, as: "property")
- else
- AshPhoenix.Form.for_create(Mv.Membership.Property, :create, as: "property")
- end
-
- assign(socket, form: to_form(form))
- end
-end
diff --git a/lib/mv_web/property_live/index.ex b/lib/mv_web/property_live/index.ex
deleted file mode 100644
index 4e43aee..0000000
--- a/lib/mv_web/property_live/index.ex
+++ /dev/null
@@ -1,99 +0,0 @@
-defmodule MvWeb.PropertyLive.Index do
- use MvWeb, :live_view
-
- @impl true
- def render(assigns) do
- ~H"""
- <.header>
- Listing Properties
- <:actions>
- <.link patch={~p"/properties/new"}>
- <.button>New Property
-
-
-
-
- <.table
- id="properties"
- rows={@streams.properties}
- row_click={fn {_id, property} -> JS.navigate(~p"/properties/#{property}") end}
- >
- <:col :let={{_id, property}} label="Id">{property.id}
-
- <:action :let={{_id, property}}>
-
- <.link navigate={~p"/properties/#{property}"}>Show
-
-
- <.link patch={~p"/properties/#{property}/edit"}>Edit
-
-
- <:action :let={{id, property}}>
- <.link
- phx-click={JS.push("delete", value: %{id: property.id}) |> hide("##{id}")}
- data-confirm="Are you sure?"
- >
- Delete
-
-
-
-
- <.modal
- :if={@live_action in [:new, :edit]}
- id="property-modal"
- show
- on_cancel={JS.patch(~p"/properties")}
- >
- <.live_component
- module={MvWeb.PropertyLive.FormComponent}
- id={(@property && @property.id) || :new}
- title={@page_title}
- action={@live_action}
- property={@property}
- patch={~p"/properties"}
- />
-
- """
- end
-
- @impl true
- def mount(_params, _session, socket) do
- {:ok, stream(socket, :properties, Ash.read!(Mv.Membership.Property))}
- end
-
- @impl true
- def handle_params(params, _url, socket) do
- {:noreply, apply_action(socket, socket.assigns.live_action, params)}
- end
-
- defp apply_action(socket, :edit, %{"id" => id}) do
- socket
- |> assign(:page_title, "Edit Property")
- |> assign(:property, Ash.get!(Mv.Membership.Property, id))
- end
-
- defp apply_action(socket, :new, _params) do
- socket
- |> assign(:page_title, "New Property")
- |> assign(:property, nil)
- end
-
- defp apply_action(socket, :index, _params) do
- socket
- |> assign(:page_title, "Listing Properties")
- |> assign(:property, nil)
- end
-
- @impl true
- def handle_info({MvWeb.PropertyLive.FormComponent, {:saved, property}}, socket) do
- {:noreply, stream_insert(socket, :properties, property)}
- end
-
- @impl true
- def handle_event("delete", %{"id" => id}, socket) do
- property = Ash.get!(Mv.Membership.Property, id)
- Ash.destroy!(property)
-
- {:noreply, stream_delete(socket, :properties, property)}
- end
-end
diff --git a/lib/mv_web/property_live/show.ex b/lib/mv_web/property_live/show.ex
deleted file mode 100644
index e1a5a65..0000000
--- a/lib/mv_web/property_live/show.ex
+++ /dev/null
@@ -1,57 +0,0 @@
-defmodule MvWeb.PropertyLive.Show do
- use MvWeb, :live_view
-
- @impl true
- def render(assigns) do
- ~H"""
- <.header>
- Property {@property.id}
- <:subtitle>This is a property record from your database.
-
- <:actions>
- <.link patch={~p"/properties/#{@property}/show/edit"} phx-click={JS.push_focus()}>
- <.button>Edit property
-
-
-
-
- <.list>
- <:item title="Id">{@property.id}
-
-
- <.back navigate={~p"/properties"}>Back to properties
-
- <.modal
- :if={@live_action == :edit}
- id="property-modal"
- show
- on_cancel={JS.patch(~p"/properties/#{@property}")}
- >
- <.live_component
- module={MvWeb.PropertyLive.FormComponent}
- id={@property.id}
- title={@page_title}
- action={@live_action}
- property={@property}
- patch={~p"/properties/#{@property}"}
- />
-
- """
- end
-
- @impl true
- def mount(_params, _session, socket) do
- {:ok, socket}
- end
-
- @impl true
- def handle_params(%{"id" => id}, _, socket) do
- {:noreply,
- socket
- |> assign(:page_title, page_title(socket.assigns.live_action))
- |> assign(:property, Ash.get!(Mv.Membership.Property, id))}
- end
-
- defp page_title(:show), do: "Show Property"
- defp page_title(:edit), do: "Edit Property"
-end
diff --git a/lib/mv_web/property_type_live/form_component.ex b/lib/mv_web/property_type_live/form_component.ex
deleted file mode 100644
index 1504f2a..0000000
--- a/lib/mv_web/property_type_live/form_component.ex
+++ /dev/null
@@ -1,81 +0,0 @@
-defmodule MvWeb.PropertyTypeLive.FormComponent do
- use MvWeb, :live_component
-
- @impl true
- def render(assigns) do
- ~H"""
-
- <.header>
- {@title}
- <:subtitle>Use this form to manage property_type records in your database.
-
-
- <.simple_form
- for={@form}
- id="property_type-form"
- phx-target={@myself}
- phx-change="validate"
- phx-submit="save"
- >
- <.input field={@form[:name]} type="text" label="Name" /><.input
- field={@form[:type]}
- type="text"
- label="Type"
- /><.input field={@form[:description]} type="text" label="Description" /><.input
- field={@form[:immutable]}
- type="checkbox"
- label="Immutable"
- /><.input field={@form[:required]} type="checkbox" label="Required" />
-
- <:actions>
- <.button phx-disable-with="Saving...">Save Property type
-
-
-
- """
- end
-
- @impl true
- def update(assigns, socket) do
- {:ok,
- socket
- |> assign(assigns)
- |> assign_form()}
- end
-
- @impl true
- def handle_event("validate", %{"property_type" => property_type_params}, socket) do
- {:noreply,
- assign(socket, form: AshPhoenix.Form.validate(socket.assigns.form, property_type_params))}
- end
-
- def handle_event("save", %{"property_type" => property_type_params}, socket) do
- case AshPhoenix.Form.submit(socket.assigns.form, params: property_type_params) do
- {:ok, property_type} ->
- notify_parent({:saved, property_type})
-
- socket =
- socket
- |> put_flash(:info, "Property type #{socket.assigns.form.source.type}d successfully")
- |> push_patch(to: socket.assigns.patch)
-
- {:noreply, socket}
-
- {:error, form} ->
- {:noreply, assign(socket, form: form)}
- end
- end
-
- defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
-
- defp assign_form(%{assigns: %{property_type: property_type}} = socket) do
- form =
- if property_type do
- AshPhoenix.Form.for_update(property_type, :update, as: "property_type")
- else
- AshPhoenix.Form.for_create(Mv.Membership.PropertyType, :create, as: "property_type")
- end
-
- assign(socket, form: to_form(form))
- end
-end
diff --git a/lib/mv_web/property_type_live/index.ex b/lib/mv_web/property_type_live/index.ex
deleted file mode 100644
index f100fae..0000000
--- a/lib/mv_web/property_type_live/index.ex
+++ /dev/null
@@ -1,103 +0,0 @@
-defmodule MvWeb.PropertyTypeLive.Index do
- use MvWeb, :live_view
-
- @impl true
- def render(assigns) do
- ~H"""
- <.header>
- Listing Property types
- <:actions>
- <.link patch={~p"/property_types/new"}>
- <.button>New Property type
-
-
-
-
- <.table
- id="property_types"
- rows={@streams.property_types}
- row_click={fn {_id, property_type} -> JS.navigate(~p"/property_types/#{property_type}") end}
- >
- <:col :let={{_id, property_type}} label="Id">{property_type.id}
-
- <:col :let={{_id, property_type}} label="Name">{property_type.name}
-
- <:col :let={{_id, property_type}} label="Description">{property_type.description}
-
- <:action :let={{_id, property_type}}>
-
- <.link navigate={~p"/property_types/#{property_type}"}>Show
-
-
- <.link patch={~p"/property_types/#{property_type}/edit"}>Edit
-
-
- <:action :let={{id, property_type}}>
- <.link
- phx-click={JS.push("delete", value: %{id: property_type.id}) |> hide("##{id}")}
- data-confirm="Are you sure?"
- >
- Delete
-
-
-
-
- <.modal
- :if={@live_action in [:new, :edit]}
- id="property_type-modal"
- show
- on_cancel={JS.patch(~p"/property_types")}
- >
- <.live_component
- module={MvWeb.PropertyTypeLive.FormComponent}
- id={(@property_type && @property_type.id) || :new}
- title={@page_title}
- action={@live_action}
- property_type={@property_type}
- patch={~p"/property_types"}
- />
-
- """
- end
-
- @impl true
- def mount(_params, _session, socket) do
- {:ok, stream(socket, :property_types, Ash.read!(Mv.Membership.PropertyType))}
- end
-
- @impl true
- def handle_params(params, _url, socket) do
- {:noreply, apply_action(socket, socket.assigns.live_action, params)}
- end
-
- defp apply_action(socket, :edit, %{"id" => id}) do
- socket
- |> assign(:page_title, "Edit Property type")
- |> assign(:property_type, Ash.get!(Mv.Membership.PropertyType, id))
- end
-
- defp apply_action(socket, :new, _params) do
- socket
- |> assign(:page_title, "New Property type")
- |> assign(:property_type, nil)
- end
-
- defp apply_action(socket, :index, _params) do
- socket
- |> assign(:page_title, "Listing Property types")
- |> assign(:property_type, nil)
- end
-
- @impl true
- def handle_info({MvWeb.PropertyTypeLive.FormComponent, {:saved, property_type}}, socket) do
- {:noreply, stream_insert(socket, :property_types, property_type)}
- end
-
- @impl true
- def handle_event("delete", %{"id" => id}, socket) do
- property_type = Ash.get!(Mv.Membership.PropertyType, id)
- Ash.destroy!(property_type)
-
- {:noreply, stream_delete(socket, :property_types, property_type)}
- end
-end
diff --git a/lib/mv_web/property_type_live/show.ex b/lib/mv_web/property_type_live/show.ex
deleted file mode 100644
index b39021c..0000000
--- a/lib/mv_web/property_type_live/show.ex
+++ /dev/null
@@ -1,61 +0,0 @@
-defmodule MvWeb.PropertyTypeLive.Show do
- use MvWeb, :live_view
-
- @impl true
- def render(assigns) do
- ~H"""
- <.header>
- Property type {@property_type.id}
- <:subtitle>This is a property_type record from your database.
-
- <:actions>
- <.link patch={~p"/property_types/#{@property_type}/show/edit"} phx-click={JS.push_focus()}>
- <.button>Edit property_type
-
-
-
-
- <.list>
- <:item title="Id">{@property_type.id}
-
- <:item title="Name">{@property_type.name}
-
- <:item title="Description">{@property_type.description}
-
-
- <.back navigate={~p"/property_types"}>Back to property_types
-
- <.modal
- :if={@live_action == :edit}
- id="property_type-modal"
- show
- on_cancel={JS.patch(~p"/property_types/#{@property_type}")}
- >
- <.live_component
- module={MvWeb.PropertyTypeLive.FormComponent}
- id={@property_type.id}
- title={@page_title}
- action={@live_action}
- property_type={@property_type}
- patch={~p"/property_types/#{@property_type}"}
- />
-
- """
- end
-
- @impl true
- def mount(_params, _session, socket) do
- {:ok, socket}
- end
-
- @impl true
- def handle_params(%{"id" => id}, _, socket) do
- {:noreply,
- socket
- |> assign(:page_title, page_title(socket.assigns.live_action))
- |> assign(:property_type, Ash.get!(Mv.Membership.PropertyType, id))}
- end
-
- defp page_title(:show), do: "Show Property type"
- defp page_title(:edit), do: "Edit Property type"
-end
diff --git a/lib/mv_web/router.ex b/lib/mv_web/router.ex
index 595a2da..75210b0 100644
--- a/lib/mv_web/router.ex
+++ b/lib/mv_web/router.ex
@@ -50,20 +50,20 @@ defmodule MvWeb.Router do
get "/", PageController, :home
live "/members", MemberLive.Index, :index
- live "/members/new", MemberLive.Index, :new
- live "/members/:id/edit", MemberLive.Index, :edit
+ live "/members/new", MemberLive.Form, :new
+ live "/members/:id/edit", MemberLive.Form, :edit
live "/members/:id", MemberLive.Show, :show
live "/members/:id/show/edit", MemberLive.Show, :edit
live "/property_types", PropertyTypeLive.Index, :index
- live "/property_types/new", PropertyTypeLive.Index, :new
- live "/property_types/:id/edit", PropertyTypeLive.Index, :edit
+ live "/property_types/new", PropertyTypeLive.Form, :new
+ live "/property_types/:id/edit", PropertyTypeLive.Form, :edit
live "/property_types/:id", PropertyTypeLive.Show, :show
live "/property_types/:id/show/edit", PropertyTypeLive.Show, :edit
live "/properties", PropertyLive.Index, :index
- live "/properties/new", PropertyLive.Index, :new
- live "/properties/:id/edit", PropertyLive.Index, :edit
+ live "/properties/new", PropertyLive.Form, :new
+ live "/properties/:id/edit", PropertyLive.Form, :edit
live "/properties/:id", PropertyLive.Show, :show
live "/properties/:id/show/edit", PropertyLive.Show, :edit
diff --git a/mix.exs b/mix.exs
index c16e11b..143bbfc 100644
--- a/mix.exs
+++ b/mix.exs
@@ -5,12 +5,13 @@ defmodule Mv.MixProject do
[
app: :mv,
version: "0.1.0",
- elixir: "~> 1.14",
+ elixir: "~> 1.15",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
consolidate_protocols: Mix.env() != :dev,
aliases: aliases(),
- deps: deps()
+ deps: deps(),
+ listeners: [Phoenix.CodeReloader]
]
end
@@ -44,31 +45,31 @@ defmodule Mv.MixProject do
{:ash_authentication, "~> 4.9"},
{:ash_authentication_phoenix, "~> 2.10"},
{:igniter, "~> 0.6", only: [:dev, :test]},
- {:phoenix, "~> 1.7.20"},
+ {:phoenix, "~> 1.8.0-rc.3", override: true},
{:phoenix_ecto, "~> 4.5"},
{:ecto_sql, "~> 3.10"},
{:postgrex, ">= 0.0.0"},
{:phoenix_html, "~> 4.1"},
{:phoenix_live_reload, "~> 1.2", only: :dev},
- {:phoenix_live_view, "~> 1.0.0"},
+ {:phoenix_live_view, "~> 1.0.9"},
{:floki, ">= 0.30.0", only: :test},
{:phoenix_live_dashboard, "~> 0.8.3"},
- {:esbuild, "~> 0.10", runtime: Mix.env() == :dev},
- {:tailwind, "~> 0.2", runtime: Mix.env() == :dev},
+ {:esbuild, "~> 0.9", runtime: Mix.env() == :dev},
+ {:tailwind, "~> 0.3", runtime: Mix.env() == :dev},
{:heroicons,
github: "tailwindlabs/heroicons",
- tag: "v2.2.0",
+ tag: "v2.1.1",
sparse: "optimized",
app: false,
compile: false,
depth: 1},
- {:swoosh, "~> 1.5"},
- {:finch, "~> 0.13"},
+ {:swoosh, "~> 1.16"},
+ {:req, "~> 0.5"},
{:telemetry_metrics, "~> 1.0"},
{:telemetry_poller, "~> 1.0"},
{:gettext, "~> 0.26"},
{:jason, "~> 1.2"},
- {:dns_cluster, "~> 0.2.0"},
+ {:dns_cluster, "~> 0.1.1"},
{:bandit, "~> 1.5"},
{:mix_audit, "~> 2.1", only: [:dev, :test], runtime: false},
{:sobelow, "~> 0.14", only: [:dev, :test], runtime: false},
diff --git a/mix.lock b/mix.lock
index 51b4604..84f9bd1 100644
--- a/mix.lock
+++ b/mix.lock
@@ -16,7 +16,7 @@
"credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"},
"db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"},
"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
- "dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"},
+ "dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"},
"ecto": {:hex, :ecto, "3.12.6", "8bf762dc5b87d85b7aca7ad5fe31ef8142a84cea473a3381eb933bd925751300", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4c0cba01795463eebbcd9e4b5ef53c1ee8e68b9c482baef2a80de5a61e7a57fe"},
"ecto_commons": {:hex, :ecto_commons, "0.3.6", "7b1d9e59396cf8c8cbe5a26d50d03f9b6d0fe6c640210dd503622f276f1e59bb", [:mix], [{:burnex, "~> 3.0", [hex: :burnex, repo: "hexpm", optional: true]}, {:ecto, "~> 3.4", [hex: :ecto, repo: "hexpm", optional: false]}, {:ex_phone_number, "~> 0.2", [hex: :ex_phone_number, repo: "hexpm", optional: false]}, {:luhn, "~> 0.3.0", [hex: :luhn, repo: "hexpm", optional: false]}], "hexpm", "3f12981a1e398f206c5d2014e7b732b7ec91b110b9cb84875cb5b28fc75d7a0a"},
"ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"},
@@ -30,7 +30,7 @@
"floki": {:hex, :floki, "0.37.1", "d7aaee758c8a5b4a7495799a4260754fec5530d95b9c383c03b27359dea117cf", [:mix], [], "hexpm", "673d040cb594d31318d514590246b6dd587ed341d3b67e17c1c0eb8ce7ca6f04"},
"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]},
+ "heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "88ab3a0d790e6a47404cba02800a6b25d2afae50", [tag: "v2.1.1", sparse: "optimized", depth: 1]},
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
"igniter": {:hex, :igniter, "0.6.7", "4e183afc59d89289e223c4282fd3e9bb39b82e28d0aa6d3369f70fbd3e21a243", [: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", "43b0a584dc84fd1320772c87047355b604ed2bcdd25392b17f7da8bdd09b61ac"},
"inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"},
@@ -47,7 +47,7 @@
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
"owl": {:hex, :owl, "0.12.2", "65906b525e5c3ef51bab6cba7687152be017aebe1da077bb719a5ee9f7e60762", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "6398efa9e1fea70a04d24231e10dcd66c1ac1aa2da418d20ef5357ec61de2880"},
- "phoenix": {:hex, :phoenix, "1.7.21", "14ca4f1071a5f65121217d6b57ac5712d1857e40a0833aff7a691b7870fc9a3b", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "336dce4f86cba56fed312a7d280bf2282c720abb6074bdb1b61ec8095bdd0bc9"},
+ "phoenix": {:hex, :phoenix, "1.8.0-rc.3", "6ae19e57b9c109556f1b8abdb992d96d443b0ae28e03b604f3dc6c75d9f7d35f", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "419422afc33e965c0dbf181cbedc77b4cfd024dac0db7d9d2287656043d48e24"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.6.4", "dcf3483ab45bab4c15e3a47c34451392f64e433846b08469f5d16c2a4cd70052", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "f5b8584c36ccc9b903948a696fc9b8b81102c79c7c0c751a9f00cdec55d5f2d7"},
"phoenix_html": {:hex, :phoenix_html, "4.2.1", "35279e2a39140068fc03f8874408d58eef734e488fc142153f055c5454fd1c08", [:mix], [], "hexpm", "cff108100ae2715dd959ae8f2a8cef8e20b593f8dfd031c9cba92702cf23e053"},
"phoenix_html_helpers": {:hex, :phoenix_html_helpers, "1.0.1", "7eed85c52eff80a179391036931791ee5d2f713d76a81d0d2c6ebafe1e11e5ec", [:mix], [{:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cffd2385d1fa4f78b04432df69ab8da63dc5cf63e07b713a4dcf36a3740e3090"},
@@ -57,7 +57,7 @@
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
"phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"},
- "plug": {:hex, :plug, "1.18.0", "d78df36c41f7e798f2edf1f33e1727eae438e9dd5d809a9997c463a108244042", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "819f9e176d51e44dc38132e132fe0accaf6767eab7f0303431e404da8476cfa2"},
+ "plug": {:hex, :plug, "1.18.1", "5067f26f7745b7e31bc3368bc1a2b818b9779faa959b49c934c17730efc911cf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "57a57db70df2b422b564437d2d33cf8d33cd16339c1edb190cd11b1a3a546cc2"},
"plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"},
"postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"},
"reactor": {:hex, :reactor, "0.15.4", "ef0c56a901c132529a14ab59fed0ccb4fcecb24308fb189a94c908255d4fdafc", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "783bf62fd0c72ded033afabdb8b6190b7048769771a2a97256e6f0bf4fb0a891"},
@@ -70,8 +70,8 @@
"spitfire": {:hex, :spitfire, "0.2.1", "29e154873f05444669c7453d3d931820822cbca5170e88f0f8faa1de74a79b47", [:mix], [], "hexpm", "6eeed75054a38341b2e1814d41bb0a250564092358de2669fdb57ff88141d91b"},
"splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"},
"stream_data": {:hex, :stream_data, "1.2.0", "58dd3f9e88afe27dc38bef26fce0c84a9e7a96772b2925c7b32cd2435697a52b", [:mix], [], "hexpm", "eb5c546ee3466920314643edf68943a5b14b32d1da9fe01698dc92b73f89a9ed"},
- "swoosh": {:hex, :swoosh, "1.19.2", "b2325aa7cd2bcd63ba023fa07a73dfc4f80660a592d40912975a879966ed9b7b", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5.10 or ~> 0.6 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cab7ef7c2c94c68fe21d3da26f6b86db118fdf4e7024ccb5842a4972c1056837"},
"sweet_xml": {:hex, :sweet_xml, "0.7.5", "803a563113981aaac202a1dbd39771562d0ad31004ddbfc9b5090bdcd5605277", [:mix], [], "hexpm", "193b28a9b12891cae351d81a0cead165ffe67df1b73fe5866d10629f4faefb12"},
+ "swoosh": {:hex, :swoosh, "1.19.2", "b2325aa7cd2bcd63ba023fa07a73dfc4f80660a592d40912975a879966ed9b7b", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5.10 or ~> 0.6 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cab7ef7c2c94c68fe21d3da26f6b86db118fdf4e7024ccb5842a4972c1056837"},
"tailwind": {:hex, :tailwind, "0.3.1", "a89d2835c580748c7a975ad7dd3f2ea5e63216dc16d44f9df492fbd12c094bed", [:mix], [], "hexpm", "98a45febdf4a87bc26682e1171acdedd6317d0919953c353fcd1b4f9f4b676a2"},
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
"telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"},
diff --git a/test/mv_web/member_live/index_test.exs b/test/mv_web/member_live/index_test.exs
index ce47a43..e3e77dc 100644
--- a/test/mv_web/member_live/index_test.exs
+++ b/test/mv_web/member_live/index_test.exs
@@ -35,8 +35,7 @@ defmodule MvWeb.MemberLive.IndexTest do
test "shows translated flash message after creating a member 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, "/members")
- view |> element("a", "Neues Mitglied") |> render_click()
+ {:ok, form_view, _html} = live(conn, "/members/new")
form_data = %{
"member[first_name]" => "Max",
@@ -44,15 +43,20 @@ defmodule MvWeb.MemberLive.IndexTest do
"member[email]" => "max@example.com"
}
- view |> form("#member-form", form_data) |> render_submit()
- assert has_element?(view, "#flash-group", "Mitglied erstellt erfolgreich")
+ # Submit form and follow the redirect to get the flash message
+ {:ok, index_view, _html} =
+ form_view
+ |> form("#member-form", form_data)
+ |> render_submit()
+ |> follow_redirect(conn, "/members")
+
+ assert has_element?(index_view, "#flash-group", "Mitglied erstellt erfolgreich")
end
test "shows translated flash message after creating a member in English", %{conn: conn} do
conn = conn_with_oidc_user(conn)
conn = Plug.Test.init_test_session(conn, locale: "en")
- {:ok, view, _html} = live(conn, "/members")
- view |> element("a", "New Member") |> render_click()
+ {:ok, form_view, _html} = live(conn, "/members/new")
form_data = %{
"member[first_name]" => "Max",
@@ -60,7 +64,13 @@ defmodule MvWeb.MemberLive.IndexTest do
"member[email]" => "max@example.com"
}
- view |> form("#member-form", form_data) |> render_submit()
- assert has_element?(view, "#flash-group", "Member create successfully")
+ # Submit form and follow the redirect to get the flash message
+ {:ok, index_view, _html} =
+ form_view
+ |> form("#member-form", form_data)
+ |> render_submit()
+ |> follow_redirect(conn, "/members")
+
+ assert has_element?(index_view, "#flash-group", "Member create successfully")
end
end