lib/core_web/views/email_helpers.ex

defmodule Legendary.CoreWeb.EmailHelpers do
  @moduledoc """
  HTML helpers for emails.
  """

  import Phoenix.LiveView.Helpers, only: [sigil_H: 2]
  import ShorterMaps

  def framework_styles do
    %{
      background: %{
        color: "#222222",
      },
      body: %{
        font_family: "sans-serif",
        font_size: "15px",
        line_height: "20px",
        text_color: "#555555",
      },
      button: %{
        border_radius: "4px",
        border: "1px solid #000000",
        background: "#222222",
        color: "#ffffff",
        font_family: "sans-serif",
        font_size: "15px",
        line_height: "15px",
        text_decoration: "none",
        padding: "13px 17px",
        display: "block",
      },
      column: %{
        background: "#FFFFFF",
        padding: "0 10px 40px 10px",
      },
      footer: %{
        padding: "20px",
        font_family: "sans-serif",
        font_size: "12px",
        line_height: "15px",
        text_align: "center",
        color: "#ffffff",
      },
      global: %{
        width: 600,
      },
      h1: %{
        margin: "0 0 10px 0",
        font_family: "sans-serif",
        font_size: "25px",
        line_height: "30px",
        color: "#333333",
        font_weight: "normal",
      },
      h2: %{
        margin: "0 0 10px 0",
        font_family: "sans-serif",
        font_size: "18px",
        line_height: "22px",
        color: "#333333",
        font_weight: "bold",
      },
      header: %{
        padding: "20px 0",
        text_align: "center",
      },
      hero_image: %{
        background: "#dddddd",
        display: "block",
        margin: "auto",
      },
      inner_column: %{
        padding: "10px 10px 0",
      },
      li: %{
        margin: "0 0 0 10px",
      },
      last_li: %{
        margin: "0 0 10px 30px",
      },
      spacer: %{
        height: "40",
        font_size: "0px",
        line_height: "0px",
      },
      ul: %{
        margin: "0 0 10px 0",
        padding: "0",
      },
    }
  end

  def framework_styles(group) do
    Map.get(framework_styles(), group, %{})
  end

  def application_styles(group) do
    styles = Application.get_env(:core, :email, %{}) |> Map.get(:styles, %{})

    Map.get(styles, group, %{})
  end

  def effective_styles(group, overrides \\ %{}) do
    group
    |> framework_styles()
    |> Legendary.Core.MapUtils.deep_merge(application_styles(group))
    |> Map.merge(overrides)
  end

  def preview(do: content) do
    assigns = ~M{content}

    ~H"""
    <div style="max-height:0; overflow:hidden; mso-hide:all;" aria-hidden="true">
      <%= content %>
    </div>
    <!-- Visually Hidden Preheader Text : END -->

    <!-- Create white space after the desired preview text so email clients don’t pull other distracting text into the inbox preview. Extend as necessary. -->
    <!-- Preview Text Spacing Hack : BEGIN -->
    <div style="display: none; font-size: 1px; line-height: 1px; max-height: 0px; max-width: 0px; opacity: 0; overflow: hidden; mso-hide: all; font-family: sans-serif;">
        &zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;
        &zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;
        &zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;
        &zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;
        &zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;
        &zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;
    </div>
    """
  end

  def header(do: content) do
    assigns = ~M{content}

    ~H"""
    <tr>
      <td style={map_style(effective_styles(:header))}>
        <%= content %>
      </td>
    </tr>
    """
  end

  def spacer do
    style = effective_styles(:spacer)

    assigns = ~M{style}

    ~H"""
    <tr>
      <td aria-hidden="true" height={style[:height]} style={map_style(style)}>
        &nbsp;
      </td>
    </tr>
    """
  end

  def row(do: content) do
    assigns = ~M{content}

    ~H"""
    <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
      <tr>
        <%= content %>
      </tr>
    </table>
    """
  end

  @spec col(number, keyword, [{:do, any}, ...]) :: {:safe, [...]}
  def col(n, opts, do: content) do
    {of, _opts} = Keyword.pop!(opts, :of)
    width = n * 100.0 / of

    assigns = ~M{of, width, content}

    ~H"""
    <td valign="top" width={"#{width}%"} style={map_style(effective_styles(:column))}>
      <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
        <tr>
          <td style={map_style(effective_styles(:body)) <> " " <> map_style(effective_styles(:inner_column))}>
            <%= content %>
          </td>
        </tr>
      </table>
    </td>
    """
  end

  def hero_image(opts) do
    {src, _rest_opts} = Keyword.pop!(opts, :src)

    assigns = ~M{src}

    ~H"""
    <%= row do %>
      <%= col 1, of: 1 do %>
        <img
          src={src}
          width={effective_styles(:global)[:width]}
          height="auto"
          alt="alt_text"
          border="0"
          style={"#{map_style(effective_styles(:body))} width: 100%; max-width: #{effective_styles(:global)[:width]}; height: auto; #{map_style(effective_styles(:hero_image))}"}
          class="g-img"
        >
      <% end %>
    <% end %>
    """
  end

  def h1(do: content) do
    assigns = ~M{content}

    ~H"""
      <h1 style={map_style(effective_styles(:h1))}>
        <%= content %>
      </h1>
    """
  end

  def h2(do: content) do
    assigns = ~M{content}

    ~H"""
    <h2 style={map_style(effective_styles(:h2))}>
      <%= content %>
    </h2>
    """
  end

  def p(do: content) do
    assigns = ~M{content}

    ~H"""
    <p style={map_style(effective_styles(:body))}>
      <%= content %>
    </p>
    """
  end

  def styled_button(opts, do: content), do: button(opts, do: content)

  def button(opts, do: content) do
    {overrides, opts_without_style} = Keyword.pop(opts, :style, %{})
    {href, _rest_opts} = Keyword.pop!(opts_without_style, :href)

    style = effective_styles(:button, overrides)
    cell_style = style |> Map.take([:border_radius, :background])

    assigns = ~M{cell_style, content, href, style}

    ~H"""
      <%= wrapper do %>
        <td
          class="button-td button-td-primary"
          style={map_style(cell_style)}
        >
          <a
            class="button-a button-a-primary"
            href={href}
            style={map_style(style)}
          >
            <%= content %>
          </a>
        </td>
      <% end %>
    """
  end

  def ul(opts) do
    {items, _rest_opts} = Keyword.pop!(opts, :items)

    item_count = Enum.count(items)

    item_tags =
      items
      |> Enum.with_index()
      |> Enum.map(fn {item, index} ->
        li_for_ul(index, item_count, item)
      end)

    assigns = ~M{item_tags}

    ~H"""
    <ul style={map_style(effective_styles(:ul))}>
      <%= for li <- item_tags  do %>
        <%= li %>
      <% end %>
    </ul>
    """
  end

  def footer do
    assigns = %{}
    ~H"""
    <%= wrapper do %>
      <td style={map_style(effective_styles(:footer))}>
        <%= Legendary.I18n.t! "en", "email.company.name" %><br>
        <span class="unstyle-auto-detected-links">
          <%= Legendary.I18n.t! "en", "email.company.address" %><br>
          <%= Legendary.I18n.t! "en", "email.company.phone" %>
        </span>
        <br><br>
      </td>
    <% end %>
    """
  end

  defp li_for_ul(index, list_length, content) do
    style_type = if index == list_length - 1, do: :last_li, else: :li
    assigns = ~M{content, style_type}

    ~H"""
      <li style={map_style(effective_styles(style_type))}>
        <%= content %>
      </li>
    """
  end

  defp wrapper(do: content) do
    assigns = ~M{content}

    ~H"""
    <table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" style="margin: auto;">
      <tr>
        <%= content %>
      </tr>
    </table>
    """
  end

  defp map_style(map) do
    map
    |> Enum.map(fn {key, value} ->
      new_key =
        key
        |> Atom.to_string()
        |> String.replace("_", "-")
      "#{new_key}: #{value};"
    end)
    |> Enum.join("\n")
  end
end