A nice Dashboard UI

Course Index page

Code


Before going on with LiveComponents and other topics, let’s spend a few minutes on our UI. At the moment products and trades are rendered in a table, using the default phoenix.css.

Our goal is to have a dashboard that looks like the one below.

LiveView Course , Poeticoins Dashboard UI

Let’s copy some assets from the repo:

and remove the <header>...</header> from the root.html.leex layout.

Then, we refactor the CryptoDashboardLive template, using the classes defined in poeticoins.scss.

defmodule PoeticoinsWeb.CryptoDashboardLive do
  ...
  def render(assigns) do
    ~L"""
    <form action="#" phx-submit="add-product">...</form>

    <%= for product <- @products, trade = @trades[product] do%>

      <div class="product-component">
        <div class="currency-container">
          <img class="icon" src="" />
          <div class="crypto-name">
            <%= product.currency_pair %>
          </div>
        </div>

        <div class="price-container">
          <div class="price">
            <%= trade.price %>
          </div>
        </div>

        <div class="exchange-name">
          <%= product.exchange_name %>
        </div>

        <div class="trade-time">
          <%= trade.traded_at %>
        </div>
      </div>

    <% end %>
    """
  end
  ...
end
Product card

Great, we start to see something close to the wanted result.

It would be nice to show the crypto icon, a human readable date and time, the cryptocurrency name (like Bitcoin instead of BTC) and the fiat currency character (like $ instead of USD).

It’s convenient to write some helpers in a separate PoeticoinsWeb.ProductHelpers module, and import these functions into PoeticoinsWeb.CryptoDashboardLive.

#lib/poeticoins_web/product_helpers.ex
defmodule PoeticoinsWeb.ProductHelpers do

  def fiat_symbols

  def human_datetime(datetime)

  def crypto_icon(conn, product)

  def crypto_name(product)

  def fiat_character(product)

  def crypto_symbol(product)

  def fiat_symbol(product)

  defp crypto_and_fiat_symbols(product)

end

These are the functions we are going to implement. Lets’ start from the private one, crypto_and_fiat_symbols(product).

#lib/poeticoins_web/product_helpers.ex
defp crypto_and_fiat_symbols(%{exchange_name: "coinbase"} = product) do
  [crypto_symbol, fiat_symbol] =
    product.currency_pair
    |> String.split("-")
    |> Enum.map(&String.downcase/1)

  %{crypto_symbol: crypto_symbol, fiat_symbol: fiat_symbol}
end

defp crypto_and_fiat_symbols(%{exchange_name: "bitstamp"} = product) do
  crypto_symbol = String.slice(product.currency_pair, 0..2)
  fiat_symbol = String.slice(product.currency_pair, 3..6)
  %{crypto_symbol: crypto_symbol, fiat_symbol: fiat_symbol}
end

We have two clauses, one for each exchange. Given a product, we use this function to get the crypto and fiat symbols. The symbols, or ticker symbols, are the letters representing a particular asset. For example btc for Bitcoin and usd for US dollar.

iex> crypto_and_fiat_symbols(Product.new("coinbase", "BTC-USD"))
%{crypto_symbol: "btc", fiat_symbol: "usd"}

iex> crypto_and_fiat_symbols(Product.new("bitstamp", "btcusd"))
%{crypto_symbol: "btc", fiat_symbol: "usd"}

The next two functions, crypto_symbol/1 and fiat_symbol/1, just return respectively the product’s crypto_symbol and fiat_symbol.

#lib/poeticoins_web/product_helpers.ex
def crypto_symbol(product),
  do: crypto_and_fiat_symbols(product).crypto_symbol

def fiat_symbol(product),
  do: crypto_and_fiat_symbols(product).fiat_symbol

def fiat_symbols do
  ["eur", "usd"]
end

fiat_symbols/0 just returns the list of supported fiat symbols.

crypto_name/1 returns the product’s cryptocurrency full name.

#lib/poeticoins_web/product_helpers.ex
def crypto_name(product) do
  case crypto_and_fiat_symbols(product) do
    %{crypto_symbol: "btc"} -> "Bitcoin"
    %{crypto_symbol: "eth"} -> "Ethereum"
    %{crypto_symbol: "ltc"} -> "Litecoin"
  end
end

And fiat_character/1 returns the product’s fiat currency character.

#lib/poeticoins_web/product_helpers.ex
def fiat_character(product) do
  case crypto_and_fiat_symbols(product) do
    %{fiat_symbol: "usd"} -> "$"
    %{fiat_symbol: "eur"} -> "€"
  end
end

For the icon, we implement crypto_icon(conn, product) which returns the relative path of the product’s crypto icon.

#lib/poeticoins_web/product_helpers.ex
def crypto_icon(conn, product) do
  crypto_symbol = crypto_symbol(product)
  relative_path = Path.join("/images/cryptos", "#{crypto_symbol}.svg")
  PoeticoinsWeb.Router.Helpers.static_path(conn, relative_path)
end

And last, but not least, human_datetime(datetime) function to return a nicely formatted datetime string.

def human_datetime(datetime) do
  Calendar.strftime(datetime, "%b %d, %Y   %H:%M:%S")
end

Let’s use these helpers in the CryptoDashboardLive template.

defmodule PoeticoinsWeb.CryptoDashboardLive do
  ...
  import PoeticoinsWeb.ProductHelpers


  def render(assigns) do
    ...
  end

  ...
end
<form action="#" phx-submit="add-product">... </form>

<%= for product <- @products, trade = @trades[product] do%>

  <!-- We use the helpers inside here! -->

  <div class="product-component">
    <div class="currency-container">
      <img class="icon" src="<%= crypto_icon(@socket, product) %>" />
      <div class="crypto-name">
        <%= crypto_name(product) %>
      </div>
    </div>

    <div class="price-container">
      <ul class="fiat-symbols">
        <%= for fiat <- fiat_symbols() do %>
          <li class="
          <%= if fiat_symbol(product) == fiat, do: "active" %>
            "><%= fiat %></li>
        <% end %>
      </ul>

      <div class="price">
        <%= trade.price %>
        <%= fiat_character(product) %>
      </div>
    </div>

    <div class="exchange-name">
      <%= product.exchange_name %>
    </div>

    <div class="trade-time">
      <%= human_datetime(trade.traded_at) %>
    </div>
  </div>


<% end %>

Ok, now it looks much better! In the .price-container div, we also render the fiat EUR and USD symbols, activating the product’s one with the active class.

It’s now time to refactor the toolbar!

Let’s first define the grouped_products_by_exchange_name/0 function, which returns the products grouped by exchange name.

defp grouped_products_by_exchange_name do
  Poeticoins.available_products()
  |> Enum.group_by(& &1.exchange_name)
end

In this way, in the <select> tag, we can use the <optgroup> tag and render the product options grouped by exchange name.

<div class="poeticoins-toolbar">
  <div class="title">Poeticoins</div>


  <form action="#" phx-submit="add-product">
    <select name="product_id" class="select-product">

    <option selected disabled>Add a Crypto Product</option>

    <%= for {exchange_name, products} <- grouped_products_by_exchange_name() do %>
      <optgroup label="<%= exchange_name %>">
        <%= for product <- products do %>
          <option value="<%= to_string(product) %>">
              <%= crypto_name(product) %>
              -
              <%= fiat_character(product) %>
          </option>
        <% end %>
      </optgroup>
    <% end %>
    </select>

    <input type="submit" value="+" />
  </form>
</div>

<div class="product-components">
  ...
</div>

We wrap the <form> with a toolbar div, then we render the select tag, with a <optgroup> for each exchange and <option> for each of exchange’s product.

Final result

Our template is becoming quite long and probably it should be moved to a template file. In the next lesson we’ll see how to refactor this template using LiveComponents.