From 1baac66dda8eb79a782066143b1d4e79a1d8e11d Mon Sep 17 00:00:00 2001 From: Rui Oliveira Date: Sun, 22 Dec 2024 16:48:17 +0000 Subject: [PATCH] feat: live updates --- lib/safira/minigames.ex | 66 ++++++++++++++++--- .../live/app/wheel_live/components/awards.ex | 59 +++++++++++++++++ .../app/wheel_live/components/latest_wins.ex | 18 ++--- lib/safira_web/live/app/wheel_live/index.ex | 25 ++++++- .../live/app/wheel_live/index.html.heex | 15 ++++- 5 files changed, 164 insertions(+), 19 deletions(-) create mode 100644 lib/safira_web/live/app/wheel_live/components/awards.ex diff --git a/lib/safira/minigames.ex b/lib/safira/minigames.ex index f4d97db9..8358e353 100644 --- a/lib/safira/minigames.ex +++ b/lib/safira/minigames.ex @@ -155,7 +155,10 @@ defmodule Safira.Minigames do """ def list_wheel_drops do - Repo.all(WheelDrop) + WheelDrop + |> order_by([wd], asc: wd.probability) + |> Repo.all() + |> Repo.preload([:badge, :prize]) end @doc """ @@ -187,9 +190,13 @@ defmodule Safira.Minigames do """ def create_wheel_drop(attrs \\ %{}) do - %WheelDrop{} - |> WheelDrop.changeset(attrs) - |> Repo.insert() + result = + %WheelDrop{} + |> WheelDrop.changeset(attrs) + |> Repo.insert() + + broadcast_wheel_config_update("drops", list_wheel_drops()) + result end @doc """ @@ -205,9 +212,13 @@ defmodule Safira.Minigames do """ def update_wheel_drop(%WheelDrop{} = wheel_drop, attrs) do - wheel_drop - |> WheelDrop.changeset(attrs) - |> Repo.update() + result = + wheel_drop + |> WheelDrop.changeset(attrs) + |> Repo.update() + + broadcast_wheel_config_update("drops", list_wheel_drops()) + result end @doc """ @@ -223,7 +234,9 @@ defmodule Safira.Minigames do """ def delete_wheel_drop(%WheelDrop{} = wheel_drop) do - Repo.delete(wheel_drop) + result = Repo.delete(wheel_drop) + broadcast_wheel_config_update("drops", list_wheel_drops()) + result end @doc """ @@ -325,10 +338,24 @@ defmodule Safira.Minigames do |> Multi.merge(fn %{drop: drop, attendee: attendee} -> add_spin_action(drop, attendee) end) + |> Multi.run(:notify, fn _repo, params -> broadcast_spin_changes(params) end) # Execute the transaction |> Repo.transaction() end + defp broadcast_spin_changes(params) do + case broadcast_wheel_win(Map.get(params, :spin)) do + :ok -> + case broadcast_wheel_config_update("drops", list_wheel_drops()) do + :ok -> {:ok, :ok} + e -> e + end + + e -> + e + end + end + defp add_spin_action(drop, attendee) do if is_nil(drop) or (is_nil(drop.badge_id) and is_nil(drop.prize_id)) do # If there was no prize, or the prize was just tokens, don't insert it @@ -530,6 +557,29 @@ defmodule Safira.Minigames do Phoenix.PubSub.broadcast(@pubsub, wheel_config_topic(config), {config, value}) end + @doc """ + Subscribes the caller to the wheel's wins. + + ## Examples + + iex> subscribe_to_wheel_wins() + :ok + """ + def subscribe_to_wheel_wins() do + Phoenix.PubSub.subscribe(@pubsub, "wheel_win") + end + + defp broadcast_wheel_win(value) do + value = value |> Repo.preload(attendee: [:user], drop: [:prize, :badge]) + + if not is_nil(value) and not is_nil(value.drop) and + (not is_nil(value.drop.badge) or not is_nil(value.drop.prize)) do + Phoenix.PubSub.broadcast(@pubsub, "wheel_win", {"win", value}) + else + :ok + end + end + # Generates a random number using the Erlang crypto module defp strong_randomizer do <> = diff --git a/lib/safira_web/live/app/wheel_live/components/awards.ex b/lib/safira_web/live/app/wheel_live/components/awards.ex new file mode 100644 index 00000000..3e76ea56 --- /dev/null +++ b/lib/safira_web/live/app/wheel_live/components/awards.ex @@ -0,0 +1,59 @@ +defmodule SafiraWeb.App.WheelLive.Components.Awards do + @moduledoc """ + Lucky wheel awards component. + """ + use SafiraWeb, :component + + attr :entries, :list, default: [] + + def awards(assigns) do + ~H""" + + + + + + + + <%= for entry <- @entries do %> + + + + + + + <% end %> +
NameStockMax. / AttendeeProbability
<%= entry_name(entry) %><%= entry_stock(entry) %><%= entry.max_per_attendee %> + <%= format_probability(entry.probability) %> +
+ """ + end + + defp entry_stock(drop) do + if is_nil(drop.prize) do + "∞" + else + drop.prize.stock + end + end + + defp format_probability(probability) do + "#{probability * 100} %" + end + + defp entry_name(drop) do + cond do + not is_nil(drop.prize) -> + drop.prize.name + + not is_nil(drop.badge) -> + drop.badge.name + + drop.entries > 0 -> + "#{drop.entries} Entries" + + drop.tokens > 0 -> + "#{drop.tokens} Tokens" + end + end +end diff --git a/lib/safira_web/live/app/wheel_live/components/latest_wins.ex b/lib/safira_web/live/app/wheel_live/components/latest_wins.ex index 95e78bb9..55fd30d4 100644 --- a/lib/safira_web/live/app/wheel_live/components/latest_wins.ex +++ b/lib/safira_web/live/app/wheel_live/components/latest_wins.ex @@ -8,17 +8,19 @@ defmodule SafiraWeb.App.WheelLive.Components.LatestWins do def latest_wins(assigns) do ~H""" - - - - - +
AttendeePrizeWhen
+ + + + <%= for entry <- @entries do %> - - - + + + <% end %>
<%= gettext("Attendee") %><%= gettext("Prize") %><%= gettext("When") %>
<%= entry.attendee.user.name %><%= entry_name(entry) %><%= Timex.from_now(entry.inserted_at) %><%= entry.attendee.user.name %><%= entry_name(entry) %> + <%= Timex.from_now(entry.inserted_at) %> +
diff --git a/lib/safira_web/live/app/wheel_live/index.ex b/lib/safira_web/live/app/wheel_live/index.ex index fb20c7db..91ec682c 100644 --- a/lib/safira_web/live/app/wheel_live/index.ex +++ b/lib/safira_web/live/app/wheel_live/index.ex @@ -2,16 +2,21 @@ defmodule SafiraWeb.App.WheelLive.Index do use SafiraWeb, :app_view import SafiraWeb.App.WheelLive.Components.LatestWins + import SafiraWeb.App.WheelLive.Components.Awards import SafiraWeb.App.WheelLive.Components.ResultModal import SafiraWeb.App.WheelLive.Components.Wheel alias Safira.Minigames + @max_wins 6 + @impl true def mount(_params, _session, socket) do if connected?(socket) do Minigames.subscribe_to_wheel_config_update("price") Minigames.subscribe_to_wheel_config_update("is_active") + Minigames.subscribe_to_wheel_config_update("drops") + Minigames.subscribe_to_wheel_wins() end {:ok, @@ -22,7 +27,8 @@ defmodule SafiraWeb.App.WheelLive.Index do |> assign(:wheel_price, Minigames.get_wheel_price()) |> assign(:result, nil) |> assign(:wheel_active?, Minigames.wheel_active?()) - |> assign(:latest_wins, Minigames.wheel_latest_wins(5))} + |> assign(:latest_wins, Minigames.wheel_latest_wins(@max_wins)) + |> assign(:drops, Minigames.list_wheel_drops())} end @impl true @@ -84,6 +90,23 @@ defmodule SafiraWeb.App.WheelLive.Index do {:noreply, socket |> assign(:wheel_active?, value)} end + @impl true + def handle_info({"drops", value}, socket) do + {:noreply, socket |> assign(:drops, value)} + end + + @impl true + def handle_info({"win", value}, socket) do + {:noreply, + socket + |> assign(:latest_wins, merge_wins(socket.assigns.latest_wins, value))} + end + + defp merge_wins(latest_wins, new_win) do + ([new_win] ++ latest_wins) + |> Enum.take(@max_wins) + end + defp can_spin?(wheel_active?, tokens, price, in_spin?) do !in_spin? && wheel_active? && tokens >= price end diff --git a/lib/safira_web/live/app/wheel_live/index.html.heex b/lib/safira_web/live/app/wheel_live/index.html.heex index dcf58399..e89961fc 100644 --- a/lib/safira_web/live/app/wheel_live/index.html.heex +++ b/lib/safira_web/live/app/wheel_live/index.html.heex @@ -5,7 +5,12 @@
-
+
+
+

+ <%= gettext("Spin To Win!") %> +

+
<.wheel />
<.action_button @@ -18,8 +23,14 @@
-

<%= gettext("Latest Wins") %>

+

+ <%= gettext("Latest Wins") %> +

<.latest_wins entries={@latest_wins} /> +

+ <%= gettext("Awards") %> +

+ <.awards entries={@drops} />