Code
- dashboard-ui repo
assets/css/poeticoins.scss
assets/static/images/background.svg
assets/static/images/cryptos/btc.svg
assets/static/images/cryptos/eth.svg
assets/static/images/cryptos/ltc.svg
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.
Let’s copy some assets from the repo:
- the assets/css/poeticoins.scss and import it in app.scss.
- assets/static/images/background.svg, which is the blue background svg image.
- assets/static/images/cryptos svg icons for btc, eth and ltc.
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
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.
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.