diff --git a/.formatter.exs b/.formatter.exs index bafd8f8..f5dc12d 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,6 +1,6 @@ [ - import_deps: [:ecto, :phoenix], - inputs: ["*.{heex,ex,exs}", "priv/*/seeds.exs", "{config,lib,test}/**/*.{heex,ex,exs}"], + import_deps: [:ecto, :ecto_sql, :phoenix], + inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}", "priv/*/seeds.exs"], subdirectories: ["priv/*/migrations"], plugins: [Phoenix.LiveView.HTMLFormatter] ] diff --git a/config/dev.exs b/config/dev.exs index 0aa76cc..0b63d57 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -59,8 +59,7 @@ config :cannery, CanneryWeb.Endpoint, patterns: [ ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$", ~r"priv/gettext/.*(po)$", - ~r"lib/cannery_web/(live|views)/.*(ex)$", - ~r"lib/cannery_web/templates/.*(eex)$" + ~r"lib/cannery_web/*/.*(ex)$" ] ] diff --git a/config/runtime.exs b/config/runtime.exs index 7b19085..6ab8082 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -12,7 +12,7 @@ if System.get_env("PHX_SERVER") && System.get_env("RELEASE_NAME") do config :cannery, CanneryWeb.Endpoint, server: true end -config :cannery, CanneryWeb.ViewHelpers, shibao_mode: System.get_env("SHIBAO_MODE") == "true" +config :cannery, CanneryWeb.HTMLHelpers, shibao_mode: System.get_env("SHIBAO_MODE") == "true" # Set default locale config :gettext, :default_locale, System.get_env("LOCALE", "en_US") diff --git a/lib/cannery/accounts.ex b/lib/cannery/accounts.ex index 6cf6efc..ccc086b 100644 --- a/lib/cannery/accounts.ex +++ b/lib/cannery/accounts.ex @@ -374,8 +374,8 @@ defmodule Cannery.Accounts do @doc """ Deletes the signed token with the given context. """ - @spec delete_session_token(token :: String.t()) :: :ok - def delete_session_token(token) do + @spec delete_user_session_token(token :: String.t()) :: :ok + def delete_user_session_token(token) do UserToken.token_and_context_query(token, "session") |> Repo.delete_all() :ok end diff --git a/lib/cannery/accounts/email.ex b/lib/cannery/accounts/email.ex index 739cc23..bf52af7 100644 --- a/lib/cannery/accounts/email.ex +++ b/lib/cannery/accounts/email.ex @@ -3,14 +3,15 @@ defmodule Cannery.Email do Emails that can be sent using Swoosh. You can find the base email templates at - `lib/cannery_web/templates/layout/email.html.heex` for html emails and - `lib/cannery_web/templates/layout/email.txt.heex` for text emails. + `lib/cannery_web/components/layouts/email_html.html.heex` for html emails and + `lib/cannery_web/components/layouts/email_text.txt.eex` for text emails. """ - use Phoenix.Swoosh, view: CanneryWeb.EmailView, layout: {CanneryWeb.LayoutView, :email} + import Swoosh.Email import CanneryWeb.Gettext + import Phoenix.Template alias Cannery.Accounts.User - alias CanneryWeb.EmailView + alias CanneryWeb.{EmailHTML, Layouts} @typedoc """ Represents an HTML and text body email that can be sent @@ -28,21 +29,33 @@ defmodule Cannery.Email do def generate_email("welcome", user, %{"url" => url}) do user |> base_email(dgettext("emails", "Confirm your Cannery account")) - |> render_body("confirm_email.html", %{user: user, url: url}) - |> text_body(EmailView.render("confirm_email.txt", %{user: user, url: url})) + |> html_email(:confirm_email_html, %{user: user, url: url}) + |> text_email(:confirm_email_text, %{user: user, url: url}) end def generate_email("reset_password", user, %{"url" => url}) do user |> base_email(dgettext("emails", "Reset your Cannery password")) - |> render_body("reset_password.html", %{user: user, url: url}) - |> text_body(EmailView.render("reset_password.txt", %{user: user, url: url})) + |> html_email(:reset_password_html, %{user: user, url: url}) + |> text_email(:reset_password_text, %{user: user, url: url}) end def generate_email("update_email", user, %{"url" => url}) do user |> base_email(dgettext("emails", "Update your Cannery email")) - |> render_body("update_email.html", %{user: user, url: url}) - |> text_body(EmailView.render("update_email.txt", %{user: user, url: url})) + |> html_email(:update_email_html, %{user: user, url: url}) + |> text_email(:update_email_text, %{user: user, url: url}) + end + + defp html_email(email, atom, assigns) do + heex = apply(EmailHTML, atom, [assigns]) + html = render_to_string(Layouts, "email_html", "html", email: email, inner_content: heex) + email |> html_body(html) + end + + defp text_email(email, atom, assigns) do + heex = apply(EmailHTML, atom, [assigns]) + text = render_to_string(Layouts, "email_text", "text", email: email, inner_content: heex) + email |> text_body(text) end end diff --git a/lib/cannery_web.ex b/lib/cannery_web.ex index 66756e2..ec6ca34 100644 --- a/lib/cannery_web.ex +++ b/lib/cannery_web.ex @@ -1,53 +1,61 @@ defmodule CanneryWeb do @moduledoc """ The entrypoint for defining your web interface, such - as controllers, views, channels and so on. + as controllers, components, channels, and so on. This can be used in your application as: use CanneryWeb, :controller - use CanneryWeb, :view + use CanneryWeb, :html - The definitions below will be executed for every view, - controller, etc, so keep them short and clean, focused + The definitions below will be executed for every controller, + component, etc, so keep them short and clean, focused on imports, uses and aliases. Do NOT define functions inside the quoted expressions - below. Instead, define any helper function in modules - and import those modules here. + below. Instead, define additional modules and import + those modules here. """ - def controller do - quote do - use Phoenix.Controller, namespace: CanneryWeb + def static_paths, do: ~w(css js fonts images favicon.ico robots.txt) + def router do + quote do + use Phoenix.Router, helpers: false + + # Import common connection and controller functions to use in pipelines import Plug.Conn - import CanneryWeb.Gettext - alias CanneryWeb.Router.Helpers, as: Routes + import Phoenix.Controller + import Phoenix.LiveView.Router end end - def view do + def channel do quote do - use Phoenix.View, - root: "lib/cannery_web/templates", - namespace: CanneryWeb + use Phoenix.Channel + end + end - # Import convenience functions from controllers - import Phoenix.Controller, - only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1] + def controller do + quote do + use Phoenix.Controller, + formats: [:html, :json], + layouts: [html: CanneryWeb.Layouts] - # Include shared imports and aliases for views - unquote(view_helpers()) + # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse + import Plug.Conn + import CanneryWeb.Gettext + + unquote(verified_routes()) end end def live_view do quote do - use Phoenix.LiveView, layout: {CanneryWeb.LayoutView, :live} + use Phoenix.LiveView, + layout: {CanneryWeb.Layouts, :app} - on_mount CanneryWeb.InitAssigns - unquote(view_helpers()) + unquote(html_helpers()) end end @@ -55,49 +63,46 @@ defmodule CanneryWeb do quote do use Phoenix.LiveComponent - unquote(view_helpers()) + unquote(html_helpers()) end end - def component do + def html do quote do use Phoenix.Component - unquote(view_helpers()) + # Import convenience functions from controllers + import Phoenix.Controller, + only: [get_csrf_token: 0, view_module: 1, view_template: 1] + + # Include general helpers for rendering HTML + unquote(html_helpers()) end end - def router do + defp html_helpers do quote do - use Phoenix.Router - - import Phoenix.{Controller, LiveView.Router} - # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse - import Plug.Conn - end - end - - def channel do - quote do - use Phoenix.Channel - # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse - import CanneryWeb.Gettext - end - end - - defp view_helpers do - quote do - # Use all HTML functionality (forms, tags, etc) # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse use Phoenix.HTML - # Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc) - # Import basic rendering functionality (render, render_layout, etc) - import CanneryWeb.{ErrorHelpers, Gettext, CoreComponents, ViewHelpers} - import Phoenix.{Component, View} + # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse + import Phoenix.Component + import CanneryWeb.{ErrorHelpers, Gettext, CoreComponents, HTMLHelpers} - alias CanneryWeb.Endpoint - alias CanneryWeb.Router.Helpers, as: Routes + # Shortcut for generating JS commands + alias Phoenix.LiveView.JS + + # Routes generation with the ~p sigil + unquote(verified_routes()) + end + end + + def verified_routes do + quote do + use Phoenix.VerifiedRoutes, + endpoint: CanneryWeb.Endpoint, + router: CanneryWeb.Router, + statics: CanneryWeb.static_paths() end end diff --git a/lib/cannery_web/components/container_table_component.ex b/lib/cannery_web/components/container_table_component.ex index 3e6013e..e70e320 100644 --- a/lib/cannery_web/components/container_table_component.ex +++ b/lib/cannery_web/components/container_table_component.ex @@ -109,14 +109,12 @@ defmodule CanneryWeb.Components.ContainerTableComponent do end @spec get_value_for_key(atom(), Container.t(), extra_data :: map) :: any() - defp get_value_for_key(:name, %{id: id, name: container_name}, _extra_data) do - assigns = %{id: id, container_name: container_name} - + defp get_value_for_key(:name, %{name: container_name} = assigns, _extra_data) do {container_name, ~H"""
- <.link navigate={Routes.container_show_path(Endpoint, :show, @id)} class="link"> - <%= @container_name %> + <.link navigate={~p"/container/#{@id}"} class="link"> + <%= @name %>
"""} diff --git a/lib/cannery_web/components/core_components.ex b/lib/cannery_web/components/core_components.ex index 79cd9c0..64aa6df 100644 --- a/lib/cannery_web/components/core_components.ex +++ b/lib/cannery_web/components/core_components.ex @@ -3,11 +3,11 @@ defmodule CanneryWeb.CoreComponents do Provides core UI components. """ use Phoenix.Component - import CanneryWeb.{Gettext, ViewHelpers} + use CanneryWeb, :verified_routes + import CanneryWeb.{Gettext, HTMLHelpers} alias Cannery.{Accounts, Accounts.Invite, Accounts.User} alias Cannery.{Ammo, Ammo.Pack} alias Cannery.{Containers.Container, Containers.Tag} - alias CanneryWeb.{Endpoint, HomeLive} alias CanneryWeb.Router.Helpers, as: Routes alias Phoenix.LiveView.{JS, Rendered} @@ -29,13 +29,13 @@ defmodule CanneryWeb.CoreComponents do ## Examples - <.modal return_to={Routes.<%= schema.singular %>_index_path(Endpoint, :index)}> + <.modal return_to={~p"/\#{<%= schema.plural %>}"}> <.live_component module={<%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Live.FormComponent} id={@<%= schema.singular %>.id || :new} title={@page_title} action={@live_action} - return_to={Routes.<%= schema.singular %>_index_path(Endpoint, :index)} + return_to={~p"/\#{<%= schema.singular %>}"} <%= schema.singular %>: @<%= schema.singular %> /> diff --git a/lib/cannery_web/components/core_components/container_card.html.heex b/lib/cannery_web/components/core_components/container_card.html.heex index 8abc098..a4fd487 100644 --- a/lib/cannery_web/components/core_components/container_card.html.heex +++ b/lib/cannery_web/components/core_components/container_card.html.heex @@ -5,7 +5,7 @@ border border-gray-400 rounded-lg shadow-lg hover:shadow-md transition-all duration-300 ease-in-out" > - <.link navigate={Routes.container_show_path(Endpoint, :show, @container)} class="link"> + <.link navigate={~p"/container/#{@container}"} class="link">

<%= @container.name %>

diff --git a/lib/cannery_web/components/core_components/invite_card.html.heex b/lib/cannery_web/components/core_components/invite_card.html.heex index 34102e5..a34de49 100644 --- a/lib/cannery_web/components/core_components/invite_card.html.heex +++ b/lib/cannery_web/components/core_components/invite_card.html.heex @@ -23,7 +23,7 @@ <% end %> <.qr_code - content={Routes.user_registration_url(Endpoint, :new, invite: @invite.token)} + content={url(CanneryWeb.Endpoint, ~p"/users/register?invite=#{@invite.token}")} filename={@invite.name} /> @@ -36,7 +36,7 @@ id={"code-#{@invite.id}"} class="mx-2 my-1 text-xs px-4 py-2 rounded-lg text-center break-all text-gray-100 bg-primary-800" phx-no-format - ><%= Routes.user_registration_url(Endpoint, :new, invite: @invite.token) %> + ><%= url(CanneryWeb.Endpoint, ~p"/users/register?invite=#{@invite.token}") %> <%= if @code_actions, do: render_slot(@code_actions) %> diff --git a/lib/cannery_web/components/core_components/pack_card.html.heex b/lib/cannery_web/components/core_components/pack_card.html.heex index a39b6d2..3912264 100644 --- a/lib/cannery_web/components/core_components/pack_card.html.heex +++ b/lib/cannery_web/components/core_components/pack_card.html.heex @@ -5,7 +5,7 @@ border border-gray-400 rounded-lg shadow-lg hover:shadow-md transition-all duration-300 ease-in-out" > - <.link navigate={Routes.pack_show_path(Endpoint, :show, @pack)} class="mb-2 link"> + <.link navigate={~p"/ammo/show/#{@pack}"} class="mb-2 link">

<%= @pack.type.name %>

@@ -55,7 +55,7 @@ <%= gettext("Container:") %> - <.link navigate={Routes.container_show_path(Endpoint, :show, @container)} class="link"> + <.link navigate={~p"/container/#{@container}"} class="link"> <%= @container.name %> diff --git a/lib/cannery_web/components/core_components/topbar.html.heex b/lib/cannery_web/components/core_components/topbar.html.heex index 08997be..128ed84 100644 --- a/lib/cannery_web/components/core_components/topbar.html.heex +++ b/lib/cannery_web/components/core_components/topbar.html.heex @@ -1,12 +1,9 @@