defmodule HaytniWeb.Shared do @moduledoc ~S""" Contains shared stuffs between Haytni base plugins. Mainly handle a message view/template which is intended to replace "abusive" `Phoenix.Controller.put_flash/3`. `put_flash` stores messages in session and may have several issues: * it can conflict if you use multiple tabs or instances of a same browser * excessive reads/writes (depending on the backend) * incompatible with HTTP caching """ use HaytniWeb, :controller @doc ~S""" Returns the path to the login page. """ @spec session_path(conn_or_endpoint :: Plug.Conn.t | module, module :: module) :: String.t def session_path(conn_or_endpoint, module) do haytni_path(conn_or_endpoint, module, &(:"haytni_#{&1}_session_path"), :new) end @doc """ Returns the path to a Haytni controller Example: iex> #{__MODULE__}.haytni_path(conn, YourApp.Haytni, &(:"haytni_\#{&1}_invitation_path"), :new, [[invitation: "ABCD", email: "me@mydomain.com"]]) "/invitations/new?invitation=ABCD&email=me%40mydomain.com" """ @spec haytni_path(conn_or_endpoint :: Plug.Conn.t | module, module :: module, fun :: (atom -> atom), action :: atom, args :: Keyword.t) :: String.t def haytni_path(conn_or_endpoint, module, fun, action, args \\ []) when is_function(fun, 1) and is_atom(action) and is_list(args) do apply(module.router(), fun.(module.scope()), [conn_or_endpoint, action] ++ args) end @doc ~S""" Momorize the original HTTP referer by adding it to a changeset. Have to be called on `new` or `edit` action (not `create` nor `update`). """ @spec add_referer_to_changeset(conn :: Plug.Conn.t, changeset :: Ecto.Changeset.t) :: Ecto.Changeset.t def add_referer_to_changeset(conn = %Plug.Conn{}, changeset = %Ecto.Changeset{}) do referer = case Plug.Conn.get_req_header(conn, "referer") do [referer] -> referer _ -> nil end Ecto.Changeset.put_change(changeset, :referer, referer) end @doc ~S""" Add a back/cancel link for SharedView. NOTE: we do not use `"javascript:history.back()"` as default since this may be inappropriate with Content Security Policy (CSP). But user is free to use it by indicating it as value for *default*. """ @spec back_link(conn :: Plug.Conn.t, struct :: struct, default :: String.t) :: Plug.Conn.t def back_link(conn = %Plug.Conn{}, struct = %_{}, default) do back_link = with( referer when not is_nil(referer) <- Map.get(struct, :referer), host = Keyword.get(Phoenix.Controller.endpoint_module(conn).config(:url), :host, "localhost"), %URI{host: ^host, scheme: scheme} when scheme in ~W[http https] <- URI.parse(referer) ) do referer else _ -> nil end || default || "/" conn |> assign(:back_link, back_link) end @doc ~S""" Add a next step link for SharedView. """ @spec next_step_link(conn :: Plug.Conn.t, href :: String.t, text :: String.t) :: Plug.Conn.t def next_step_link(conn = %Plug.Conn{}, href, text) do conn |> assign(:next_step_link_text, text) |> assign(:next_step_link_href, href) end @doc ~S""" Set connection to render SharedView/message.html. """ @spec render_message(conn :: Plug.Conn.t, module :: module, message :: String.t, type :: atom) :: Plug.Conn.t def render_message(conn = %Plug.Conn{}, module, message, type \\ :info) do conn |> assign(:type, type) |> assign(:message, message) |> HaytniWeb.Helpers.put_view(module, "SharedView") |> render("message.html") end end