feat: migration to phoenix 1.8 - merge changed files
This commit is contained in:
parent
50832da885
commit
0334260de5
13 changed files with 233 additions and 453 deletions
|
|
@ -5,7 +5,6 @@
|
||||||
@source "../css";
|
@source "../css";
|
||||||
@source "../js";
|
@source "../js";
|
||||||
@source "../../lib/mv_web";
|
@source "../../lib/mv_web";
|
||||||
@source "../../deps/ash_authentication_phoenix";
|
|
||||||
|
|
||||||
/* A Tailwind plugin that makes "hero-#{ICON}" classes available.
|
/* A Tailwind plugin that makes "hero-#{ICON}" classes available.
|
||||||
The heroicons installation itself is managed by your mix.exs */
|
The heroicons installation itself is managed by your mix.exs */
|
||||||
|
|
|
||||||
|
|
@ -76,25 +76,24 @@ config :esbuild,
|
||||||
version: "0.17.11",
|
version: "0.17.11",
|
||||||
mv: [
|
mv: [
|
||||||
args:
|
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__),
|
cd: Path.expand("../assets", __DIR__),
|
||||||
env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
|
env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
|
||||||
]
|
]
|
||||||
|
|
||||||
# Configure tailwind (the version is required)
|
# Configure tailwind (the version is required)
|
||||||
config :tailwind,
|
config :tailwind,
|
||||||
version: "3.4.3",
|
version: "4.0.9",
|
||||||
mv: [
|
mv: [
|
||||||
args: ~w(
|
args: ~w(
|
||||||
--config=tailwind.config.js
|
--input=assets/css/app.css
|
||||||
--input=css/app.css
|
--output=priv/static/assets/css/app.css
|
||||||
--output=../priv/static/assets/app.css
|
|
||||||
),
|
),
|
||||||
cd: Path.expand("../assets", __DIR__)
|
cd: Path.expand("..", __DIR__)
|
||||||
]
|
]
|
||||||
|
|
||||||
# Configures Elixir's Logger
|
# Configures Elixir's Logger
|
||||||
config :logger, :console,
|
config :logger, :default_formatter,
|
||||||
format: "$time $metadata[$level] $message\n",
|
format: "$time $metadata[$level] $message\n",
|
||||||
metadata: [:request_id]
|
metadata: [:request_id]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,10 @@ config :mv, Mv.Repo,
|
||||||
# The watchers configuration can be used to run external
|
# The watchers configuration can be used to run external
|
||||||
# watchers to your application. For example, we can use it
|
# watchers to your application. For example, we can use it
|
||||||
# to bundle .js and .css sources.
|
# to bundle .js and .css sources.
|
||||||
# Binding to loopback ipv4 address prevents access from other machines.
|
|
||||||
config :mv, MvWeb.Endpoint,
|
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.
|
# 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,
|
check_origin: false,
|
||||||
code_reloader: true,
|
code_reloader: true,
|
||||||
debug_errors: true,
|
debug_errors: true,
|
||||||
|
|
@ -56,10 +56,11 @@ config :mv, MvWeb.Endpoint,
|
||||||
# Watch static and templates for browser reloading.
|
# Watch static and templates for browser reloading.
|
||||||
config :mv, MvWeb.Endpoint,
|
config :mv, MvWeb.Endpoint,
|
||||||
live_reload: [
|
live_reload: [
|
||||||
|
web_console_logger: true,
|
||||||
patterns: [
|
patterns: [
|
||||||
~r"priv/static/(?!uploads/).*(js|css|png|jpeg|jpg|gif|svg)$",
|
~r"priv/static/(?!uploads/).*(js|css|png|jpeg|jpg|gif|svg)$",
|
||||||
~r"priv/gettext/.*(po)$",
|
~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
|
config :mv, dev_routes: true
|
||||||
|
|
||||||
# Do not include metadata nor timestamps in development logs
|
# 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
|
# Set a higher stacktrace during development. Avoid configuring such
|
||||||
# in production as building large stacktraces may be expensive.
|
# 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, :plug_init_mode, :runtime
|
||||||
|
|
||||||
config :phoenix_live_view,
|
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,
|
debug_heex_annotations: true,
|
||||||
# Enable helpful, but potentially expensive runtime checks
|
# Enable helpful, but potentially expensive runtime checks
|
||||||
enable_expensive_runtime_checks: true
|
enable_expensive_runtime_checks: true
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ if config_env() == :prod do
|
||||||
# domain: System.get_env("MAILGUN_DOMAIN")
|
# domain: System.get_env("MAILGUN_DOMAIN")
|
||||||
#
|
#
|
||||||
# For this example you need include a HTTP client required by Swoosh API client.
|
# 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
|
# config :swoosh, :api_client, Swoosh.ApiClient.Hackney
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,6 @@ defmodule Mv.Application do
|
||||||
Mv.Repo,
|
Mv.Repo,
|
||||||
{DNSCluster, query: Application.get_env(:mv, :dns_cluster_query) || :ignore},
|
{DNSCluster, query: Application.get_env(:mv, :dns_cluster_query) || :ignore},
|
||||||
{Phoenix.PubSub, name: Mv.PubSub},
|
{Phoenix.PubSub, name: Mv.PubSub},
|
||||||
# Start the Finch HTTP client for sending emails
|
|
||||||
{Finch, name: Mv.Finch},
|
|
||||||
{AshAuthentication.Supervisor, otp_app: :my},
|
{AshAuthentication.Supervisor, otp_app: :my},
|
||||||
# Start a worker by calling: Mv.Worker.start_link(arg)
|
# Start a worker by calling: Mv.Worker.start_link(arg)
|
||||||
# {Mv.Worker, arg},
|
# {Mv.Worker, arg},
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,7 @@ defmodule MvWeb do
|
||||||
|
|
||||||
def controller do
|
def controller do
|
||||||
quote do
|
quote do
|
||||||
use Phoenix.Controller,
|
use Phoenix.Controller, formats: [:html, :json]
|
||||||
formats: [:html, :json],
|
|
||||||
layouts: [html: MvWeb.Layouts]
|
|
||||||
|
|
||||||
use Gettext, backend: MvWeb.Gettext
|
use Gettext, backend: MvWeb.Gettext
|
||||||
|
|
||||||
|
|
@ -52,10 +50,10 @@ defmodule MvWeb do
|
||||||
|
|
||||||
def live_view do
|
def live_view do
|
||||||
quote do
|
quote do
|
||||||
use Phoenix.LiveView,
|
use Phoenix.LiveView
|
||||||
layout: {MvWeb.Layouts, :app}
|
|
||||||
|
|
||||||
on_mount MvWeb.LiveHelpers
|
on_mount MvWeb.LiveHelpers
|
||||||
|
|
||||||
unquote(html_helpers())
|
unquote(html_helpers())
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -91,8 +89,9 @@ defmodule MvWeb do
|
||||||
# Core UI components
|
# Core UI components
|
||||||
import MvWeb.CoreComponents
|
import MvWeb.CoreComponents
|
||||||
|
|
||||||
# Shortcut for generating JS commands
|
# Common modules used in templates
|
||||||
alias Phoenix.LiveView.JS
|
alias Phoenix.LiveView.JS
|
||||||
|
alias MvWeb.Layouts
|
||||||
|
|
||||||
# Routes generation with the ~p sigil
|
# Routes generation with the ~p sigil
|
||||||
unquote(verified_routes())
|
unquote(verified_routes())
|
||||||
|
|
|
||||||
|
|
@ -3,92 +3,34 @@ defmodule MvWeb.CoreComponents do
|
||||||
Provides core UI components.
|
Provides core UI components.
|
||||||
|
|
||||||
At first glance, this module may seem daunting, but its goal is to provide
|
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
|
core building blocks for your application, such as tables, forms, and
|
||||||
forms. The components consist mostly of markup and are well-documented
|
inputs. The components consist mostly of markup and are well-documented
|
||||||
with doc strings and declarative assigns. You may customize and style
|
with doc strings and declarative assigns. You may customize and style
|
||||||
them in any way you want, based on your application growth and needs.
|
them in any way you want, based on your application growth and needs.
|
||||||
|
|
||||||
The default components use Tailwind CSS, a utility-first CSS framework.
|
The foundation for styling is Tailwind CSS, a utility-first CSS framework,
|
||||||
See the [Tailwind CSS documentation](https://tailwindcss.com) to learn
|
augmented with daisyUI, a Tailwind CSS plugin that provides UI components
|
||||||
how to customize them or feel free to swap in another framework altogether.
|
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 Phoenix.Component
|
||||||
use Gettext, backend: MvWeb.Gettext
|
use Gettext, backend: MvWeb.Gettext
|
||||||
|
|
||||||
alias Phoenix.LiveView.JS
|
alias Phoenix.LiveView.JS
|
||||||
|
|
||||||
@doc """
|
|
||||||
Renders a modal.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
<.modal id="confirm-modal">
|
|
||||||
This is a modal.
|
|
||||||
</.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.
|
|
||||||
</.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"""
|
|
||||||
<div
|
|
||||||
id={@id}
|
|
||||||
phx-mounted={@show && show_modal(@id)}
|
|
||||||
phx-remove={hide_modal(@id)}
|
|
||||||
data-cancel={JS.exec(@on_cancel, "phx-remove")}
|
|
||||||
class="relative z-50 hidden"
|
|
||||||
>
|
|
||||||
<div id={"#{@id}-bg"} class="bg-zinc-50/90 fixed inset-0 transition-opacity" aria-hidden="true" />
|
|
||||||
<div
|
|
||||||
class="fixed inset-0 overflow-y-auto"
|
|
||||||
aria-labelledby={"#{@id}-title"}
|
|
||||||
aria-describedby={"#{@id}-description"}
|
|
||||||
role="dialog"
|
|
||||||
aria-modal="true"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<div class="flex min-h-full items-center justify-center">
|
|
||||||
<div class="w-full max-w-3xl p-4 sm:p-6 lg:py-8">
|
|
||||||
<.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"
|
|
||||||
>
|
|
||||||
<div class="absolute top-6 right-5">
|
|
||||||
<button
|
|
||||||
phx-click={JS.exec("data-cancel", to: "##{@id}")}
|
|
||||||
type="button"
|
|
||||||
class="-m-3 flex-none p-3 opacity-20 hover:opacity-40"
|
|
||||||
aria-label={gettext("close")}
|
|
||||||
>
|
|
||||||
<.icon name="hero-x-mark-solid" class="h-5 w-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div id={"#{@id}-content"}>
|
|
||||||
{render_slot(@inner_block)}
|
|
||||||
</div>
|
|
||||||
</.focus_wrap>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Renders flash notices.
|
Renders flash notices.
|
||||||
|
|
||||||
|
|
@ -114,132 +56,59 @@ defmodule MvWeb.CoreComponents do
|
||||||
id={@id}
|
id={@id}
|
||||||
phx-click={JS.push("lv:clear-flash", value: %{key: @kind}) |> hide("##{@id}")}
|
phx-click={JS.push("lv:clear-flash", value: %{key: @kind}) |> hide("##{@id}")}
|
||||||
role="alert"
|
role="alert"
|
||||||
class={[
|
class="toast toast-top toast-end z-50"
|
||||||
"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"
|
|
||||||
]}
|
|
||||||
{@rest}
|
{@rest}
|
||||||
>
|
>
|
||||||
<p :if={@title} class="flex items-center gap-1.5 text-sm font-semibold leading-6">
|
<div class={[
|
||||||
<.icon :if={@kind == :info} name="hero-information-circle-mini" class="h-4 w-4" />
|
"alert w-80 sm:w-96 max-w-80 sm:max-w-96 text-wrap",
|
||||||
<.icon :if={@kind == :error} name="hero-exclamation-circle-mini" class="h-4 w-4" />
|
@kind == :info && "alert-info",
|
||||||
{@title}
|
@kind == :error && "alert-error"
|
||||||
</p>
|
]}>
|
||||||
<p class="mt-2 text-sm leading-5">{msg}</p>
|
<.icon :if={@kind == :info} name="hero-information-circle" class="size-5 shrink-0" />
|
||||||
<button type="button" class="group absolute top-1 right-1 p-2" aria-label={gettext("close")}>
|
<.icon :if={@kind == :error} name="hero-exclamation-circle" class="size-5 shrink-0" />
|
||||||
<.icon name="hero-x-mark-solid" class="h-5 w-5 opacity-40 group-hover:opacity-70" />
|
<div>
|
||||||
</button>
|
<p :if={@title} class="font-semibold">{@title}</p>
|
||||||
</div>
|
<p>{msg}</p>
|
||||||
"""
|
|
||||||
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"""
|
|
||||||
<div id={@id}>
|
|
||||||
<.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>
|
|
||||||
|
|
||||||
<.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" />
|
|
||||||
</.flash>
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
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</.button>
|
|
||||||
</:actions>
|
|
||||||
</.simple_form>
|
|
||||||
"""
|
|
||||||
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}>
|
|
||||||
<div class="mt-10 space-y-8 bg-white">
|
|
||||||
{render_slot(@inner_block, f)}
|
|
||||||
<div :for={action <- @actions} class="mt-2 flex items-center justify-between gap-6">
|
|
||||||
{render_slot(action, f)}
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex-1" />
|
||||||
|
<button type="button" class="group self-start cursor-pointer" aria-label={gettext("close")}>
|
||||||
|
<.icon name="hero-x-mark" class="size-5 opacity-40 group-hover:opacity-70" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</.form>
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Renders a button.
|
Renders a button with navigation support.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
<.button>Send!</.button>
|
<.button>Send!</.button>
|
||||||
<.button phx-click="go" class="ml-2">Send!</.button>
|
<.button phx-click="go" variant="primary">Send!</.button>
|
||||||
|
<.button navigate={~p"/"}>Home</.button>
|
||||||
"""
|
"""
|
||||||
attr :type, :string, default: nil
|
attr :rest, :global, include: ~w(href navigate patch method)
|
||||||
attr :class, :string, default: nil
|
attr :variant, :string, values: ~w(primary)
|
||||||
attr :rest, :global, include: ~w(disabled form name value)
|
|
||||||
|
|
||||||
slot :inner_block, required: true
|
slot :inner_block, required: true
|
||||||
|
|
||||||
def button(assigns) do
|
def button(%{rest: rest} = assigns) do
|
||||||
~H"""
|
variants = %{"primary" => "btn-primary", nil => "btn-primary btn-soft"}
|
||||||
<button
|
assigns = assign(assigns, :class, Map.fetch!(variants, assigns[:variant]))
|
||||||
type={@type}
|
|
||||||
class={[
|
if rest[:href] || rest[:navigate] || rest[:patch] do
|
||||||
"phx-submit-loading:opacity-75 rounded-lg bg-zinc-900 hover:bg-zinc-700 py-2 px-3",
|
~H"""
|
||||||
"text-sm font-semibold leading-6 text-white active:text-white/80",
|
<.link class={["btn", @class]} {@rest}>
|
||||||
@class
|
{render_slot(@inner_block)}
|
||||||
]}
|
</.link>
|
||||||
{@rest}
|
"""
|
||||||
>
|
else
|
||||||
{render_slot(@inner_block)}
|
~H"""
|
||||||
</button>
|
<button class={["btn", @class]} {@rest}>
|
||||||
"""
|
{render_slot(@inner_block)}
|
||||||
|
</button>
|
||||||
|
"""
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
@ -276,7 +145,7 @@ defmodule MvWeb.CoreComponents do
|
||||||
attr :type, :string,
|
attr :type, :string,
|
||||||
default: "text",
|
default: "text",
|
||||||
values: ~w(checkbox color date datetime-local email file month number password
|
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,
|
attr :field, Phoenix.HTML.FormField,
|
||||||
doc: "a form field struct retrieved from the form, for example: @form[:email]"
|
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 :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 :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 :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,
|
attr :rest, :global,
|
||||||
include: ~w(accept autocomplete capture cols disabled form list max maxlength min minlength
|
include: ~w(accept autocomplete capture cols disabled form list max maxlength min minlength
|
||||||
|
|
@ -309,108 +180,95 @@ defmodule MvWeb.CoreComponents do
|
||||||
end)
|
end)
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div>
|
<fieldset class="fieldset mb-2">
|
||||||
<label class="flex items-center gap-4 text-sm leading-6 text-zinc-600">
|
<label>
|
||||||
<input type="hidden" name={@name} value="false" disabled={@rest[:disabled]} />
|
<input type="hidden" name={@name} value="false" disabled={@rest[:disabled]} />
|
||||||
<input
|
<span class="label">
|
||||||
type="checkbox"
|
<input
|
||||||
id={@id}
|
type="checkbox"
|
||||||
name={@name}
|
id={@id}
|
||||||
value="true"
|
name={@name}
|
||||||
checked={@checked}
|
value="true"
|
||||||
class="rounded border-zinc-300 text-zinc-900 focus:ring-0"
|
checked={@checked}
|
||||||
{@rest}
|
class={@class || "checkbox checkbox-sm"}
|
||||||
/>
|
{@rest}
|
||||||
{@label}
|
/>{@label}
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<.error :for={msg <- @errors}>{msg}</.error>
|
<.error :for={msg <- @errors}>{msg}</.error>
|
||||||
</div>
|
</fieldset>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
def input(%{type: "select"} = assigns) do
|
def input(%{type: "select"} = assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div>
|
<fieldset class="fieldset mb-2">
|
||||||
<.label for={@id}>{@label}</.label>
|
<label>
|
||||||
<select
|
<span :if={@label} class="label mb-1">{@label}</span>
|
||||||
id={@id}
|
<select
|
||||||
name={@name}
|
id={@id}
|
||||||
class="mt-2 block w-full rounded-md border border-gray-300 bg-white shadow-sm focus:border-zinc-400 focus:ring-0 sm:text-sm"
|
name={@name}
|
||||||
multiple={@multiple}
|
class={[@class || "w-full select", @errors != [] && (@error_class || "select-error")]}
|
||||||
{@rest}
|
multiple={@multiple}
|
||||||
>
|
{@rest}
|
||||||
<option :if={@prompt} value="">{@prompt}</option>
|
>
|
||||||
{Phoenix.HTML.Form.options_for_select(@options, @value)}
|
<option :if={@prompt} value="">{@prompt}</option>
|
||||||
</select>
|
{Phoenix.HTML.Form.options_for_select(@options, @value)}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
<.error :for={msg <- @errors}>{msg}</.error>
|
<.error :for={msg <- @errors}>{msg}</.error>
|
||||||
</div>
|
</fieldset>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
def input(%{type: "textarea"} = assigns) do
|
def input(%{type: "textarea"} = assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div>
|
<fieldset class="fieldset mb-2">
|
||||||
<.label for={@id}>{@label}</.label>
|
<label>
|
||||||
<textarea
|
<span :if={@label} class="label mb-1">{@label}</span>
|
||||||
id={@id}
|
<textarea
|
||||||
name={@name}
|
id={@id}
|
||||||
class={[
|
name={@name}
|
||||||
"mt-2 block w-full rounded-lg text-zinc-900 focus:ring-0 sm:text-sm sm:leading-6 min-h-[6rem]",
|
class={[
|
||||||
@errors == [] && "border-zinc-300 focus:border-zinc-400",
|
@class || "w-full textarea",
|
||||||
@errors != [] && "border-rose-400 focus:border-rose-400"
|
@errors != [] && (@error_class || "textarea-error")
|
||||||
]}
|
]}
|
||||||
{@rest}
|
{@rest}
|
||||||
>{Phoenix.HTML.Form.normalize_value("textarea", @value)}</textarea>
|
>{Phoenix.HTML.Form.normalize_value("textarea", @value)}</textarea>
|
||||||
|
</label>
|
||||||
<.error :for={msg <- @errors}>{msg}</.error>
|
<.error :for={msg <- @errors}>{msg}</.error>
|
||||||
</div>
|
</fieldset>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
# All other inputs text, datetime-local, url, password, etc. are handled here...
|
# All other inputs text, datetime-local, url, password, etc. are handled here...
|
||||||
def input(assigns) do
|
def input(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div>
|
<fieldset class="fieldset mb-2">
|
||||||
<.label for={@id}>{@label}</.label>
|
<label>
|
||||||
<input
|
<span :if={@label} class="label mb-1">{@label}</span>
|
||||||
type={@type}
|
<input
|
||||||
name={@name}
|
type={@type}
|
||||||
id={@id}
|
name={@name}
|
||||||
value={Phoenix.HTML.Form.normalize_value(@type, @value)}
|
id={@id}
|
||||||
class={[
|
value={Phoenix.HTML.Form.normalize_value(@type, @value)}
|
||||||
"mt-2 block w-full rounded-lg text-zinc-900 focus:ring-0 sm:text-sm sm:leading-6",
|
class={[
|
||||||
@errors == [] && "border-zinc-300 focus:border-zinc-400",
|
@class || "w-full input",
|
||||||
@errors != [] && "border-rose-400 focus:border-rose-400"
|
@errors != [] && (@error_class || "input-error")
|
||||||
]}
|
]}
|
||||||
{@rest}
|
{@rest}
|
||||||
/>
|
/>
|
||||||
|
</label>
|
||||||
<.error :for={msg <- @errors}>{msg}</.error>
|
<.error :for={msg <- @errors}>{msg}</.error>
|
||||||
</div>
|
</fieldset>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
# Helper used by inputs to generate form errors
|
||||||
Renders a label.
|
defp error(assigns) do
|
||||||
"""
|
|
||||||
attr :for, :string, default: nil
|
|
||||||
slot :inner_block, required: true
|
|
||||||
|
|
||||||
def label(assigns) do
|
|
||||||
~H"""
|
~H"""
|
||||||
<label for={@for} class="block text-sm font-semibold leading-6 text-zinc-800">
|
<p class="mt-1.5 flex gap-2 items-center text-sm text-error">
|
||||||
{render_slot(@inner_block)}
|
<.icon name="hero-exclamation-circle" class="size-5" />
|
||||||
</label>
|
|
||||||
"""
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Generates a generic error message.
|
|
||||||
"""
|
|
||||||
slot :inner_block, required: true
|
|
||||||
|
|
||||||
def error(assigns) do
|
|
||||||
~H"""
|
|
||||||
<p class="mt-3 flex gap-3 text-sm leading-6 text-rose-600">
|
|
||||||
<.icon name="hero-exclamation-circle-mini" class="mt-0.5 h-5 w-5 flex-none" />
|
|
||||||
{render_slot(@inner_block)}
|
{render_slot(@inner_block)}
|
||||||
</p>
|
</p>
|
||||||
"""
|
"""
|
||||||
|
|
@ -427,12 +285,12 @@ defmodule MvWeb.CoreComponents do
|
||||||
|
|
||||||
def header(assigns) do
|
def header(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<header class={[@actions != [] && "flex items-center justify-between gap-6", @class]}>
|
<header class={[@actions != [] && "flex items-center justify-between gap-6", "pb-4", @class]}>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-lg font-semibold leading-8 text-zinc-800">
|
<h1 class="text-lg font-semibold leading-8">
|
||||||
{render_slot(@inner_block)}
|
{render_slot(@inner_block)}
|
||||||
</h1>
|
</h1>
|
||||||
<p :if={@subtitle != []} class="mt-2 text-sm leading-6 text-zinc-600">
|
<p :if={@subtitle != []} class="text-sm text-base-content/70">
|
||||||
{render_slot(@subtitle)}
|
{render_slot(@subtitle)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -473,49 +331,34 @@ defmodule MvWeb.CoreComponents do
|
||||||
end
|
end
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div class="overflow-y-auto px-4 sm:overflow-visible sm:px-0">
|
<table class="table table-zebra">
|
||||||
<table class="w-[40rem] mt-11 sm:w-full">
|
<thead>
|
||||||
<thead class="text-sm text-left leading-6 text-zinc-500">
|
<tr>
|
||||||
<tr>
|
<th :for={col <- @col}>{col[:label]}</th>
|
||||||
<th :for={col <- @col} class="p-0 pb-4 pr-6 font-normal">{col[:label]}</th>
|
<th :if={@action != []}>
|
||||||
<th :if={@action != []} class="relative p-0 pb-4">
|
<span class="sr-only">{gettext("Actions")}</span>
|
||||||
<span class="sr-only">{gettext("Actions")}</span>
|
</th>
|
||||||
</th>
|
</tr>
|
||||||
</tr>
|
</thead>
|
||||||
</thead>
|
<tbody id={@id} phx-update={is_struct(@rows, Phoenix.LiveView.LiveStream) && "stream"}>
|
||||||
<tbody
|
<tr :for={row <- @rows} id={@row_id && @row_id.(row)}>
|
||||||
id={@id}
|
<td
|
||||||
phx-update={match?(%Phoenix.LiveView.LiveStream{}, @rows) && "stream"}
|
:for={col <- @col}
|
||||||
class="relative divide-y divide-zinc-100 border-t border-zinc-200 text-sm leading-6 text-zinc-700"
|
phx-click={@row_click && @row_click.(row)}
|
||||||
>
|
class={@row_click && "hover:cursor-pointer"}
|
||||||
<tr :for={row <- @rows} id={@row_id && @row_id.(row)} class="group hover:bg-zinc-50">
|
>
|
||||||
<td
|
{render_slot(col, @row_item.(row))}
|
||||||
:for={{col, i} <- Enum.with_index(@col)}
|
</td>
|
||||||
phx-click={@row_click && @row_click.(row)}
|
<td :if={@action != []} class="w-0 font-semibold">
|
||||||
class={["relative p-0", @row_click && "hover:cursor-pointer"]}
|
<div class="flex gap-4">
|
||||||
>
|
<%= for action <- @action do %>
|
||||||
<div class="block py-4 pr-6">
|
{render_slot(action, @row_item.(row))}
|
||||||
<span class="absolute -inset-y-px right-0 -left-4 group-hover:bg-zinc-50 sm:rounded-l-xl" />
|
<% end %>
|
||||||
<span class={["relative", i == 0 && "font-semibold text-zinc-900"]}>
|
</div>
|
||||||
{render_slot(col, @row_item.(row))}
|
</td>
|
||||||
</span>
|
</tr>
|
||||||
</div>
|
</tbody>
|
||||||
</td>
|
</table>
|
||||||
<td :if={@action != []} class="relative w-14 p-0">
|
|
||||||
<div class="relative whitespace-nowrap py-4 text-right text-sm font-medium">
|
|
||||||
<span class="absolute -inset-y-px -right-4 left-0 group-hover:bg-zinc-50 sm:rounded-r-xl" />
|
|
||||||
<span
|
|
||||||
:for={action <- @action}
|
|
||||||
class="relative ml-4 font-semibold leading-6 text-zinc-900 hover:text-zinc-700"
|
|
||||||
>
|
|
||||||
{render_slot(action, @row_item.(row))}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -535,38 +378,14 @@ defmodule MvWeb.CoreComponents do
|
||||||
|
|
||||||
def list(assigns) do
|
def list(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="mt-14">
|
<ul class="list">
|
||||||
<dl class="-my-4 divide-y divide-zinc-100">
|
<li :for={item <- @item} class="list-row">
|
||||||
<div :for={item <- @item} class="flex gap-4 py-4 text-sm leading-6 sm:gap-8">
|
<div>
|
||||||
<dt class="w-1/4 flex-none text-zinc-500">{item.title}</dt>
|
<div class="font-bold">{item.title}</div>
|
||||||
<dd class="text-zinc-700">{render_slot(item)}</dd>
|
<div>{render_slot(item)}</div>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</li>
|
||||||
</div>
|
</ul>
|
||||||
"""
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Renders a back navigation link.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
<.back navigate={~p"/posts"}>Back to posts</.back>
|
|
||||||
"""
|
|
||||||
attr :navigate, :any, required: true
|
|
||||||
slot :inner_block, required: true
|
|
||||||
|
|
||||||
def back(assigns) do
|
|
||||||
~H"""
|
|
||||||
<div class="mt-16">
|
|
||||||
<.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)}
|
|
||||||
</.link>
|
|
||||||
</div>
|
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -581,15 +400,15 @@ defmodule MvWeb.CoreComponents do
|
||||||
width, height, and background color classes.
|
width, height, and background color classes.
|
||||||
|
|
||||||
Icons are extracted from the `deps/heroicons` directory and bundled within
|
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
|
## Examples
|
||||||
|
|
||||||
<.icon name="hero-x-mark-solid" />
|
<.icon name="hero-x-mark" />
|
||||||
<.icon name="hero-arrow-path" class="ml-1 w-3 h-3 animate-spin" />
|
<.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" />
|
||||||
"""
|
"""
|
||||||
attr :name, :string, required: true
|
attr :name, :string, required: true
|
||||||
attr :class, :string, default: nil
|
attr :class, :string, default: "size-4"
|
||||||
|
|
||||||
def icon(%{name: "hero-" <> _} = assigns) do
|
def icon(%{name: "hero-" <> _} = assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
|
|
@ -604,7 +423,7 @@ defmodule MvWeb.CoreComponents do
|
||||||
to: selector,
|
to: selector,
|
||||||
time: 300,
|
time: 300,
|
||||||
transition:
|
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-0 translate-y-4 sm:translate-y-0 sm:scale-95",
|
||||||
"opacity-100 translate-y-0 sm:scale-100"}
|
"opacity-100 translate-y-0 sm:scale-100"}
|
||||||
)
|
)
|
||||||
|
|
@ -615,37 +434,11 @@ defmodule MvWeb.CoreComponents do
|
||||||
to: selector,
|
to: selector,
|
||||||
time: 200,
|
time: 200,
|
||||||
transition:
|
transition:
|
||||||
{"transition-all transform ease-in duration-200",
|
{"transition-all ease-in duration-200", "opacity-100 translate-y-0 sm:scale-100",
|
||||||
"opacity-100 translate-y-0 sm:scale-100",
|
|
||||||
"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"}
|
"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"}
|
||||||
)
|
)
|
||||||
end
|
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 """
|
@doc """
|
||||||
Translates an error message using gettext.
|
Translates an error message using gettext.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,15 @@ defmodule MvWeb.Layouts do
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-none">
|
<div class="flex-none">
|
||||||
<ul class="flex flex-column px-1 space-x-4 items-center">
|
<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>
|
<li>
|
||||||
<a href="https://phoenixframework.org/" class="btn btn-ghost">Website</a>
|
<a href="https://phoenixframework.org/" class="btn btn-ghost">Website</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
<header class="px-4 sm:px-6 lg:px-8">
|
|
||||||
<div class="flex items-center justify-between border-b border-zinc-100 py-3 text-sm">
|
|
||||||
<div class="flex items-center gap-4">
|
|
||||||
<a href="/">
|
|
||||||
<img src={~p"/images/logo.svg"} width="36" />
|
|
||||||
</a>
|
|
||||||
<p class="bg-brand/5 text-brand rounded-full px-2 font-medium leading-6">
|
|
||||||
v{Application.spec(:phoenix, :vsn)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-4 font-semibold leading-6 text-zinc-900">
|
|
||||||
<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="rounded border px-2 py-1">
|
|
||||||
<option value="de" selected={Gettext.get_locale() == "de"}>Deutsch</option>
|
|
||||||
<option value="en" selected={Gettext.get_locale() == "en"}>English</option>
|
|
||||||
</select>
|
|
||||||
</form>
|
|
||||||
<a href="https://twitter.com/elixirphoenix" class="hover:text-zinc-700">
|
|
||||||
@elixirphoenix
|
|
||||||
</a>
|
|
||||||
<a href="https://github.com/phoenixframework/phoenix" class="hover:text-zinc-700">
|
|
||||||
GitHub
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://hexdocs.pm/phoenix/overview.html"
|
|
||||||
class="rounded-lg bg-zinc-100 px-2 py-1 hover:bg-zinc-200/80"
|
|
||||||
>
|
|
||||||
Get Started <span aria-hidden="true">→</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<main class="px-4 py-20 sm:px-6 lg:px-8">
|
|
||||||
<div class="mx-auto max-w-2xl">
|
|
||||||
<.flash_group flash={@flash} />
|
|
||||||
{@inner_content}
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" class="[scrollbar-gutter:stable]">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
{Application.get_env(:live_debugger, :live_debugger_tags)}
|
{Application.get_env(:live_debugger, :live_debugger_tags)}
|
||||||
|
|
||||||
|
|
@ -9,11 +9,29 @@
|
||||||
<.live_title default="Mv" suffix=" · Phoenix Framework">
|
<.live_title default="Mv" suffix=" · Phoenix Framework">
|
||||||
{assigns[:page_title]}
|
{assigns[:page_title]}
|
||||||
</.live_title>
|
</.live_title>
|
||||||
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
|
<link phx-track-static rel="stylesheet" href={~p"/assets/css/app.css"} />
|
||||||
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
|
<script defer phx-track-static type="text/javascript" src={~p"/assets/js/app.js"}>
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
(() => {
|
||||||
|
const setTheme = (theme) => {
|
||||||
|
if (theme === "system") {
|
||||||
|
localStorage.removeItem("phx:theme");
|
||||||
|
document.documentElement.removeAttribute("data-theme");
|
||||||
|
} else {
|
||||||
|
localStorage.setItem("phx:theme", theme);
|
||||||
|
document.documentElement.setAttribute("data-theme", theme);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (!document.documentElement.hasAttribute("data-theme")) {
|
||||||
|
setTheme(localStorage.getItem("phx:theme") || "system");
|
||||||
|
}
|
||||||
|
window.addEventListener("storage", (e) => e.key === "phx:theme" && setTheme(e.newValue || "system"));
|
||||||
|
window.addEventListener("phx:set-theme", ({ detail: { theme } }) => setTheme(theme));
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-white">
|
<body>
|
||||||
{@inner_content}
|
{@inner_content}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,13 @@ defmodule MvWeb.Endpoint do
|
||||||
|
|
||||||
# Serve at "/" the static files from "priv/static" directory.
|
# Serve at "/" the static files from "priv/static" directory.
|
||||||
#
|
#
|
||||||
# You should set gzip to true if you are running phx.digest
|
# When code reloading is disabled (e.g., in production),
|
||||||
# when deploying your static files in production.
|
# the `gzip` option is enabled to serve compressed
|
||||||
|
# static files generated by running `phx.digest`.
|
||||||
plug Plug.Static,
|
plug Plug.Static,
|
||||||
at: "/",
|
at: "/",
|
||||||
from: :mv,
|
from: :mv,
|
||||||
gzip: false,
|
gzip: not code_reloading?,
|
||||||
only: MvWeb.static_paths()
|
only: MvWeb.static_paths()
|
||||||
|
|
||||||
if Code.ensure_loaded?(Tidewave) do
|
if Code.ensure_loaded?(Tidewave) do
|
||||||
|
|
|
||||||
19
mix.exs
19
mix.exs
|
|
@ -5,12 +5,13 @@ defmodule Mv.MixProject do
|
||||||
[
|
[
|
||||||
app: :mv,
|
app: :mv,
|
||||||
version: "0.1.0",
|
version: "0.1.0",
|
||||||
elixir: "~> 1.14",
|
elixir: "~> 1.15",
|
||||||
elixirc_paths: elixirc_paths(Mix.env()),
|
elixirc_paths: elixirc_paths(Mix.env()),
|
||||||
start_permanent: Mix.env() == :prod,
|
start_permanent: Mix.env() == :prod,
|
||||||
consolidate_protocols: Mix.env() != :dev,
|
consolidate_protocols: Mix.env() != :dev,
|
||||||
aliases: aliases(),
|
aliases: aliases(),
|
||||||
deps: deps()
|
deps: deps(),
|
||||||
|
listeners: [Phoenix.CodeReloader]
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -44,26 +45,26 @@ defmodule Mv.MixProject do
|
||||||
{:ash_authentication, "~> 4.9"},
|
{:ash_authentication, "~> 4.9"},
|
||||||
{:ash_authentication_phoenix, "~> 2.10"},
|
{:ash_authentication_phoenix, "~> 2.10"},
|
||||||
{:igniter, "~> 0.6", only: [:dev, :test]},
|
{:igniter, "~> 0.6", only: [:dev, :test]},
|
||||||
{:phoenix, "~> 1.7.20"},
|
{:phoenix, "~> 1.8.0-rc.3", override: true},
|
||||||
{:phoenix_ecto, "~> 4.5"},
|
{:phoenix_ecto, "~> 4.5"},
|
||||||
{:ecto_sql, "~> 3.10"},
|
{:ecto_sql, "~> 3.10"},
|
||||||
{:postgrex, ">= 0.0.0"},
|
{:postgrex, ">= 0.0.0"},
|
||||||
{:phoenix_html, "~> 4.1"},
|
{:phoenix_html, "~> 4.1"},
|
||||||
{:phoenix_live_reload, "~> 1.2", only: :dev},
|
{: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},
|
{:floki, ">= 0.30.0", only: :test},
|
||||||
{:phoenix_live_dashboard, "~> 0.8.3"},
|
{:phoenix_live_dashboard, "~> 0.8.3"},
|
||||||
{:esbuild, "~> 0.10", runtime: Mix.env() == :dev},
|
{:esbuild, "~> 0.9", runtime: Mix.env() == :dev},
|
||||||
{:tailwind, "~> 0.2", runtime: Mix.env() == :dev},
|
{:tailwind, "~> 0.3", runtime: Mix.env() == :dev},
|
||||||
{:heroicons,
|
{:heroicons,
|
||||||
github: "tailwindlabs/heroicons",
|
github: "tailwindlabs/heroicons",
|
||||||
tag: "v2.2.0",
|
tag: "v2.1.1",
|
||||||
sparse: "optimized",
|
sparse: "optimized",
|
||||||
app: false,
|
app: false,
|
||||||
compile: false,
|
compile: false,
|
||||||
depth: 1},
|
depth: 1},
|
||||||
{:swoosh, "~> 1.5"},
|
{:swoosh, "~> 1.16"},
|
||||||
{:finch, "~> 0.20"},
|
{:req, "~> 0.5"},
|
||||||
{:telemetry_metrics, "~> 1.0"},
|
{:telemetry_metrics, "~> 1.0"},
|
||||||
{:telemetry_poller, "~> 1.0"},
|
{:telemetry_poller, "~> 1.0"},
|
||||||
{:gettext, "~> 0.26"},
|
{:gettext, "~> 0.26"},
|
||||||
|
|
|
||||||
4
mix.lock
4
mix.lock
|
|
@ -30,7 +30,7 @@
|
||||||
"floki": {:hex, :floki, "0.38.0", "62b642386fa3f2f90713f6e231da0fa3256e41ef1089f83b6ceac7a3fd3abf33", [:mix], [], "hexpm", "a5943ee91e93fb2d635b612caf5508e36d37548e84928463ef9dd986f0d1abd9"},
|
"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"},
|
"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"},
|
"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"},
|
"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"},
|
"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"},
|
"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_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
|
||||||
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
|
"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"},
|
"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.5", "c4ef322acd15a574a8b1a08eff0ee0a85e73096b53ce1403b6563709f15e1cea", [: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", "26ec3208eef407f31b748cadd044045c6fd485fbff168e35963d2f9dfff28d4b"},
|
"phoenix_ecto": {:hex, :phoenix_ecto, "4.6.5", "c4ef322acd15a574a8b1a08eff0ee0a85e73096b53ce1403b6563709f15e1cea", [: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", "26ec3208eef407f31b748cadd044045c6fd485fbff168e35963d2f9dfff28d4b"},
|
||||||
"phoenix_html": {:hex, :phoenix_html, "4.2.1", "35279e2a39140068fc03f8874408d58eef734e488fc142153f055c5454fd1c08", [:mix], [], "hexpm", "cff108100ae2715dd959ae8f2a8cef8e20b593f8dfd031c9cba92702cf23e053"},
|
"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"},
|
"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"},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue