Skip to content

Commit

Permalink
Merge pull request #14 from sgobotta/feature/add-presence-header-to-p…
Browse files Browse the repository at this point in the history
…ages

feature/add presence header to pages
  • Loading branch information
sgobotta committed Mar 17, 2024
2 parents 9803afd + 28b07bf commit 5eb06ac
Show file tree
Hide file tree
Showing 12 changed files with 316 additions and 48 deletions.
2 changes: 1 addition & 1 deletion .credo.exs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@
## Refactoring Opportunities
#
{Credo.Check.Refactor.CondStatements, []},
{Credo.Check.Refactor.CyclomaticComplexity, []},
{Credo.Check.Refactor.CyclomaticComplexity, [max_complexity: 10]},
{Credo.Check.Refactor.FunctionArity, []},
{Credo.Check.Refactor.LongQuoteBlocks, []},
# {Credo.Check.Refactor.MapInto, []},
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ cp .env.example .env

+ `FINNHUB_API_KEY`: The Finnhub API key.

#### Cafecito username

+ `CAFECITO_USERNAME`: The Cafecito app username.

#### The assigned directory for uploads

+ `UPLOADS_PATH`: the assigned path for uploads.
Expand Down
5 changes: 4 additions & 1 deletion lib/ex_finance/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ defmodule ExFinance.Application do
),
# Start to serve requests, typically the last entry
ExFinanceWeb.Endpoint,
ExFinnhub.StockPrices
# Start StockPrices supervisor
ExFinnhub.StockPrices,
# Start Presence supervisor
ExFinance.Presence
# {ExFinnhub.StockPrices.Producer,
# [counter: 5, name: ExFinnhub.StockPrices.Producer]}
]
Expand Down
119 changes: 119 additions & 0 deletions lib/ex_finance/presence.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
defmodule ExFinance.Presence do
@moduledoc false
use Phoenix.Presence,
otp_app: :ex_finance,
pubsub_server: ExFinance.PubSub

@doc """
Convenience function to configure a #{Phoenix.Presence} tracker for liveviews.
The given topic is used to track & subscribe callers and list presences
through the `#{Phoenix.Presence}` API.
"""
@type topic_opt :: {:topic, Phoenix.Presence.topic()}
@type pubsub_server_opt :: {:pubsub_server, module()}
@type tracker_opt :: topic_opt | pubsub_server_opt
@spec tracker([tracker_opt()]) :: {:__block__, list(), list()}
def tracker(opts \\ []) do
quote do
# Configure topic
@presence_topic unquote(opts)[:topic] ||
raise(
"use ExFinance.Presence expects :topic to be given"
)
# Configure pubsub server
@presence_pubsub_server unquote(opts)[:pubsub_server] ||
raise(
"use ExFinance.Presence expects :pubsub_server to be given"
)

# ------------------------------------------------------------------------
# API
#

@doc """
Given a `pid`, a `presence_id` and `meta`, calls the Presence module to
track the process as a presence to the configured presence topic.
"""
@spec track_presence(pid(), String.t(), map()) ::
{:ok, ref :: binary()} | {:error, reason :: term()}
def track_presence(pid, presence_id, meta),
do: ExFinance.Presence.track(pid, @presence_topic, presence_id, meta)

@doc """
Convenience function to subscribe callers to the configured presence
topic.
"""
@spec subscribe_presence :: :ok | {:error, term()}
def subscribe_presence,
do: Phoenix.PubSub.subscribe(@presence_pubsub_server, @presence_topic)

@doc """
Returns presences for the configured presence topic.
"""
@spec list_presence :: Phoenix.Presence.presences()
def list_presence, do: ExFinance.Presence.list(@presence_topic)

# ------------------------------------------------------------------------
# Callbacks
#
@impl true
def handle_info(
%Phoenix.Socket.Broadcast{event: "presence_diff", payload: diff},
socket
) do
{:noreply,
socket
|> handle_leaves(diff.leaves)
|> handle_joins(diff.joins)}
end

@spec handle_joins(
Phoenix.LiveView.Socket.t(),
Phoenix.Presence.presences()
) :: Phoenix.LiveView.Socket.t()
defp handle_joins(socket, joins) do
Enum.reduce(joins, socket, fn {presence, %{metas: [meta | _]}},
socket ->
assign_presences(
socket,
Map.put(socket.assigns.presences, presence, meta)
)
end)
end

@spec handle_leaves(
Phoenix.LiveView.Socket.t(),
Phoenix.Presence.presences()
) :: Phoenix.LiveView.Socket.t()
defp handle_leaves(socket, leaves) do
Enum.reduce(leaves, socket, fn {presence, _}, socket ->
assign_presences(
socket,
Map.delete(socket.assigns.presences, presence)
)
end)
end

# ------------------------------------------------------------------------
# Assignment functions
#
@spec assign_presences(
Phoenix.LiveView.Socket.t(),
Phoenix.Presence.presences()
) :: Phoenix.LiveView.Socket.t()
defp assign_presences(socket, presences),
do: Phoenix.Component.assign(socket, :presences, presences)
end
end

@doc """
When used, dispatch to the appropriate feature.
"""
defmacro __using__({which, opts}) when is_atom(which) do
apply(__MODULE__, which, [opts])
end

defmacro __using__(which) when is_atom(which) do
apply(__MODULE__, which, [])
end
end
15 changes: 15 additions & 0 deletions lib/ex_finance_web/components/custom_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,19 @@ defmodule ExFinanceWeb.CustomComponents do
</a>
"""
end

attr :id, :string, default: "presence-disclaimer"
attr :current_presence, :string, required: true
attr :presences, :map, required: true

def presence_disclaimer(assigns) do
~H"""
<.live_component
id={@id}
module={ExFinanceWeb.CustomComponents.PresenceDisclaimer}
presences={@presences}
current_presence={@current_presence}
/>
"""
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
defmodule ExFinanceWeb.CustomComponents.PresenceDisclaimer do
@moduledoc false
use ExFinanceWeb, :live_component

@impl true
def update(assigns, socket) do
{:ok,
socket
|> assign(assigns)}
end

@impl true
def render(assigns) do
users =
Map.delete(assigns.presences, assigns.current_presence)
|> Map.keys()
|> length

assigns = assign(assigns, :users, users)

render_disclaimer(assigns)
end

defp render_disclaimer(%{users: 0} = assigns) do
~H"""
<div />
"""
end

defp render_disclaimer(%{users: _users} = assigns) do
~H"""
<div>
<%= if connected?(@socket) do %>
<div class="py-2 text-xs italic cursor-default">
<p>
<%= ngettext(
"You and other user are browsing this page",
"You and %{users} more users are browsing this page",
@users,
users: @users
) %>
</p>
</div>
<% else %>
<div />
<% end %>
</div>
"""
end
end
7 changes: 7 additions & 0 deletions lib/ex_finance_web/components/layouts/app.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@
</a>
</div>
</div>
<%= if assigns[:show_presence] do %>
<.presence_disclaimer
id="presence-disclaimer"
presences={@presences}
current_presence={@session_id}
/>
<% end %>
</header>
<main class="px-4 py-2 sm:px-6 lg:px-8">
<div class="">
Expand Down
76 changes: 52 additions & 24 deletions lib/ex_finance_web/live/public/currency_live/index.ex
Original file line number Diff line number Diff line change
@@ -1,17 +1,32 @@
defmodule ExFinanceWeb.Public.CurrencyLive.Index do
use ExFinanceWeb, :live_view

use ExFinance.Presence,
{:tracker, [pubsub_server: ExFinance.PubSub, topic: "currencies"]}

alias ExFinance.Currencies
alias ExFinance.Currencies.Currency
alias ExFinanceWeb.Utils.DatetimeUtils

@impl true
def mount(_params, _session, socket) do
def mount(_params, session, socket) do
:ok = Currencies.subscribe_currencies()

if connected?(socket) do
{:ok, _ref} =
track_presence(self(), session_id(session), %{
joined_at: inspect(System.system_time(:second))
})

:ok = subscribe_presence()
end

{:ok,
stream(
socket,
socket
|> assign(:show_presence, true)
|> assign_session_id(session_id(session))
|> assign_presences(list_presence())
|> stream(
:currencies,
Currencies.list_currencies() |> Currencies.sort_currencies()
)}
Expand All @@ -22,11 +37,28 @@ defmodule ExFinanceWeb.Public.CurrencyLive.Index do
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
end

defp apply_action(socket, :index, _params) do
socket
|> assign(:page_title, gettext("Dollar quotes"))
|> assign(:section_title, gettext("Dollar quotes"))
end

@impl true
def handle_info({:currency_updated, %Currency{} = currency}, socket) do
{:noreply, stream_insert(socket, :currencies, currency, at: -1)}
end

# ----------------------------------------------------------------------------
# Assignment functions
#
defp assign_session_id(socket, session_id),
do: assign(socket, :session_id, session_id)

defp session_id(session), do: session["_csrf_token"]

# ----------------------------------------------------------------------------
# Helper functions
#
defp get_color_by_currency_type(%Currency{type: "bna"}), do: "green"
defp get_color_by_currency_type(%Currency{type: "euro"}), do: "orange"
defp get_color_by_currency_type(%Currency{type: "blue"}), do: "blue"
Expand All @@ -39,13 +71,24 @@ defmodule ExFinanceWeb.Public.CurrencyLive.Index do
defp get_color_by_currency_type(%Currency{type: "wholesaler"}), do: "emerald"
defp get_color_by_currency_type(%Currency{type: "future"}), do: "emerald"

defp apply_action(socket, :index, _params) do
socket
|> assign(:page_title, "Cotizaciones")
|> assign(:section_title, "Cotizaciones de moneda")
|> assign(:currency, nil)
end
defp get_color_by_price_direction(%Currency{
variation_percent: %Decimal{coef: 0}
}),
do: "gray"

defp get_color_by_price_direction(%Currency{
variation_percent: %Decimal{sign: -1}
}),
do: "red"

defp get_color_by_price_direction(%Currency{
variation_percent: %Decimal{sign: 1}
}),
do: "green"

# ----------------------------------------------------------------------------
# Render functions
#
defp render_variation_percent(%Currency{variation_percent: variation_percent}),
do: "#{variation_percent}%"

Expand All @@ -65,19 +108,4 @@ defmodule ExFinanceWeb.Public.CurrencyLive.Index do
buy_price: buy_price
}),
do: "$#{Decimal.sub(sell_price, buy_price)}"

defp get_color_by_price_direction(%Currency{
variation_percent: %Decimal{coef: 0}
}),
do: "gray"

defp get_color_by_price_direction(%Currency{
variation_percent: %Decimal{sign: -1}
}),
do: "red"

defp get_color_by_price_direction(%Currency{
variation_percent: %Decimal{sign: 1}
}),
do: "green"
end
Loading

0 comments on commit 5eb06ac

Please sign in to comment.